1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/printing/print_preview_dialog_controller.h" 6 7 #include <algorithm> 8 #include <string> 9 #include <vector> 10 11 #include "base/auto_reset.h" 12 #include "base/path_service.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "chrome/browser/browser_process.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h" 17 #include "chrome/browser/plugins/chrome_plugin_service_filter.h" 18 #include "chrome/browser/printing/print_view_manager.h" 19 #include "chrome/browser/ui/browser.h" 20 #include "chrome/browser/ui/browser_finder.h" 21 #include "chrome/browser/ui/browser_navigator.h" 22 #include "chrome/browser/ui/browser_window.h" 23 #include "chrome/browser/ui/host_desktop.h" 24 #include "chrome/browser/ui/webui/chrome_web_contents_handler.h" 25 #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h" 26 #include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" 27 #include "chrome/common/chrome_content_client.h" 28 #include "chrome/common/chrome_paths.h" 29 #include "chrome/common/url_constants.h" 30 #include "components/web_modal/web_contents_modal_dialog_host.h" 31 #include "content/public/browser/navigation_controller.h" 32 #include "content/public/browser/navigation_details.h" 33 #include "content/public/browser/navigation_entry.h" 34 #include "content/public/browser/notification_details.h" 35 #include "content/public/browser/notification_source.h" 36 #include "content/public/browser/plugin_service.h" 37 #include "content/public/browser/render_frame_host.h" 38 #include "content/public/browser/render_process_host.h" 39 #include "content/public/browser/render_view_host.h" 40 #include "content/public/browser/web_contents.h" 41 #include "content/public/browser/web_contents_delegate.h" 42 #include "content/public/common/webplugininfo.h" 43 #include "ui/web_dialogs/web_dialog_delegate.h" 44 45 using content::NavigationController; 46 using content::WebContents; 47 using content::WebUIMessageHandler; 48 49 namespace { 50 51 void EnableInternalPDFPluginForContents(WebContents* preview_dialog) { 52 // Always enable the internal PDF plugin for the print preview page. 53 base::FilePath pdf_plugin_path; 54 if (!PathService::Get(chrome::FILE_PDF_PLUGIN, &pdf_plugin_path)) 55 return; 56 57 content::WebPluginInfo pdf_plugin; 58 if (!content::PluginService::GetInstance()->GetPluginInfoByPath( 59 pdf_plugin_path, &pdf_plugin)) 60 return; 61 62 ChromePluginServiceFilter::GetInstance()->OverridePluginForFrame( 63 preview_dialog->GetRenderProcessHost()->GetID(), 64 preview_dialog->GetMainFrame()->GetRoutingID(), 65 GURL(), pdf_plugin); 66 } 67 68 // A ui::WebDialogDelegate that specifies the print preview dialog appearance. 69 class PrintPreviewDialogDelegate : public ui::WebDialogDelegate { 70 public: 71 explicit PrintPreviewDialogDelegate(WebContents* initiator); 72 virtual ~PrintPreviewDialogDelegate(); 73 74 virtual ui::ModalType GetDialogModalType() const OVERRIDE; 75 virtual base::string16 GetDialogTitle() const OVERRIDE; 76 virtual GURL GetDialogContentURL() const OVERRIDE; 77 virtual void GetWebUIMessageHandlers( 78 std::vector<WebUIMessageHandler*>* handlers) const OVERRIDE; 79 virtual void GetDialogSize(gfx::Size* size) const OVERRIDE; 80 virtual std::string GetDialogArgs() const OVERRIDE; 81 virtual void OnDialogClosed(const std::string& json_retval) OVERRIDE; 82 virtual void OnCloseContents(WebContents* source, 83 bool* out_close_dialog) OVERRIDE; 84 virtual bool ShouldShowDialogTitle() const OVERRIDE; 85 86 private: 87 WebContents* initiator_; 88 89 DISALLOW_COPY_AND_ASSIGN(PrintPreviewDialogDelegate); 90 }; 91 92 PrintPreviewDialogDelegate::PrintPreviewDialogDelegate(WebContents* initiator) 93 : initiator_(initiator) { 94 } 95 96 PrintPreviewDialogDelegate::~PrintPreviewDialogDelegate() { 97 } 98 99 ui::ModalType PrintPreviewDialogDelegate::GetDialogModalType() const { 100 // Not used, returning dummy value. 101 NOTREACHED(); 102 return ui::MODAL_TYPE_WINDOW; 103 } 104 105 base::string16 PrintPreviewDialogDelegate::GetDialogTitle() const { 106 // Only used on Windows? UI folks prefer no title. 107 return base::string16(); 108 } 109 110 GURL PrintPreviewDialogDelegate::GetDialogContentURL() const { 111 return GURL(chrome::kChromeUIPrintURL); 112 } 113 114 void PrintPreviewDialogDelegate::GetWebUIMessageHandlers( 115 std::vector<WebUIMessageHandler*>* /* handlers */) const { 116 // PrintPreviewUI adds its own message handlers. 117 } 118 119 void PrintPreviewDialogDelegate::GetDialogSize(gfx::Size* size) const { 120 DCHECK(size); 121 const gfx::Size kMinDialogSize(800, 480); 122 const int kBorder = 25; 123 *size = kMinDialogSize; 124 125 web_modal::WebContentsModalDialogHost* host = NULL; 126 Browser* browser = chrome::FindBrowserWithWebContents(initiator_); 127 if (browser) 128 host = browser->window()->GetWebContentsModalDialogHost(); 129 130 if (host) { 131 size->SetToMax(host->GetMaximumDialogSize()); 132 size->Enlarge(-2 * kBorder, -kBorder); 133 } else { 134 size->SetToMax(initiator_->GetContainerBounds().size()); 135 size->Enlarge(-2 * kBorder, -2 * kBorder); 136 } 137 138 #if defined(OS_MACOSX) 139 // Limit the maximum size on MacOS X. 140 // http://crbug.com/105815 141 const gfx::Size kMaxDialogSize(1000, 660); 142 size->SetToMin(kMaxDialogSize); 143 #endif 144 } 145 146 std::string PrintPreviewDialogDelegate::GetDialogArgs() const { 147 return std::string(); 148 } 149 150 void PrintPreviewDialogDelegate::OnDialogClosed( 151 const std::string& /* json_retval */) { 152 } 153 154 void PrintPreviewDialogDelegate::OnCloseContents(WebContents* /* source */, 155 bool* out_close_dialog) { 156 if (out_close_dialog) 157 *out_close_dialog = true; 158 } 159 160 bool PrintPreviewDialogDelegate::ShouldShowDialogTitle() const { 161 return false; 162 } 163 164 } // namespace 165 166 namespace printing { 167 168 PrintPreviewDialogController::PrintPreviewDialogController() 169 : waiting_for_new_preview_page_(false), 170 is_creating_print_preview_dialog_(false) { 171 } 172 173 // static 174 PrintPreviewDialogController* PrintPreviewDialogController::GetInstance() { 175 if (!g_browser_process) 176 return NULL; 177 return g_browser_process->print_preview_dialog_controller(); 178 } 179 180 // static 181 void PrintPreviewDialogController::PrintPreview(WebContents* initiator) { 182 if (initiator->ShowingInterstitialPage()) 183 return; 184 185 PrintPreviewDialogController* dialog_controller = GetInstance(); 186 if (!dialog_controller) 187 return; 188 if (!dialog_controller->GetOrCreatePreviewDialog(initiator)) 189 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 190 } 191 192 WebContents* PrintPreviewDialogController::GetOrCreatePreviewDialog( 193 WebContents* initiator) { 194 DCHECK(initiator); 195 196 // Get the print preview dialog for |initiator|. 197 WebContents* preview_dialog = GetPrintPreviewForContents(initiator); 198 if (!preview_dialog) 199 return CreatePrintPreviewDialog(initiator); 200 201 // Show the initiator holding the existing preview dialog. 202 initiator->GetDelegate()->ActivateContents(initiator); 203 return preview_dialog; 204 } 205 206 WebContents* PrintPreviewDialogController::GetPrintPreviewForContents( 207 WebContents* contents) const { 208 // |preview_dialog_map_| is keyed by the preview dialog, so if find() 209 // succeeds, then |contents| is the preview dialog. 210 PrintPreviewDialogMap::const_iterator it = preview_dialog_map_.find(contents); 211 if (it != preview_dialog_map_.end()) 212 return contents; 213 214 for (it = preview_dialog_map_.begin(); 215 it != preview_dialog_map_.end(); 216 ++it) { 217 // If |contents| is an initiator. 218 if (contents == it->second) { 219 // Return the associated preview dialog. 220 return it->first; 221 } 222 } 223 return NULL; 224 } 225 226 WebContents* PrintPreviewDialogController::GetInitiator( 227 WebContents* preview_dialog) { 228 PrintPreviewDialogMap::iterator it = preview_dialog_map_.find(preview_dialog); 229 return (it != preview_dialog_map_.end()) ? it->second : NULL; 230 } 231 232 void PrintPreviewDialogController::Observe( 233 int type, 234 const content::NotificationSource& source, 235 const content::NotificationDetails& details) { 236 if (type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED) { 237 OnRendererProcessClosed( 238 content::Source<content::RenderProcessHost>(source).ptr()); 239 } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { 240 OnWebContentsDestroyed(content::Source<WebContents>(source).ptr()); 241 } else { 242 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type); 243 WebContents* contents = 244 content::Source<NavigationController>(source)->GetWebContents(); 245 OnNavEntryCommitted( 246 contents, 247 content::Details<content::LoadCommittedDetails>(details).ptr()); 248 } 249 } 250 251 void PrintPreviewDialogController::ForEachPreviewDialog( 252 base::Callback<void(content::WebContents*)> callback) { 253 for (PrintPreviewDialogMap::const_iterator it = preview_dialog_map_.begin(); 254 it != preview_dialog_map_.end(); 255 ++it) { 256 callback.Run(it->first); 257 } 258 } 259 260 // static 261 bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents* contents) { 262 return IsPrintPreviewURL(contents->GetURL()); 263 } 264 265 // static 266 bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL& url) { 267 return (url.SchemeIs(content::kChromeUIScheme) && 268 url.host() == chrome::kChromeUIPrintHost); 269 } 270 271 void PrintPreviewDialogController::EraseInitiatorInfo( 272 WebContents* preview_dialog) { 273 PrintPreviewDialogMap::iterator it = preview_dialog_map_.find(preview_dialog); 274 if (it == preview_dialog_map_.end()) 275 return; 276 277 RemoveObservers(it->second); 278 preview_dialog_map_[preview_dialog] = NULL; 279 } 280 281 PrintPreviewDialogController::~PrintPreviewDialogController() {} 282 283 void PrintPreviewDialogController::OnRendererProcessClosed( 284 content::RenderProcessHost* rph) { 285 // Store contents in a vector and deal with them after iterating through 286 // |preview_dialog_map_| because RemoveFoo() can change |preview_dialog_map_|. 287 std::vector<WebContents*> closed_initiators; 288 std::vector<WebContents*> closed_preview_dialogs; 289 for (PrintPreviewDialogMap::iterator iter = preview_dialog_map_.begin(); 290 iter != preview_dialog_map_.end(); ++iter) { 291 WebContents* preview_dialog = iter->first; 292 WebContents* initiator = iter->second; 293 if (preview_dialog->GetRenderProcessHost() == rph) { 294 closed_preview_dialogs.push_back(preview_dialog); 295 } else if (initiator && 296 initiator->GetRenderProcessHost() == rph) { 297 closed_initiators.push_back(initiator); 298 } 299 } 300 301 for (size_t i = 0; i < closed_preview_dialogs.size(); ++i) { 302 RemovePreviewDialog(closed_preview_dialogs[i]); 303 if (content::WebUI* web_ui = closed_preview_dialogs[i]->GetWebUI()) { 304 PrintPreviewUI* print_preview_ui = 305 static_cast<PrintPreviewUI*>(web_ui->GetController()); 306 if (print_preview_ui) 307 print_preview_ui->OnPrintPreviewDialogClosed(); 308 } 309 } 310 311 for (size_t i = 0; i < closed_initiators.size(); ++i) 312 RemoveInitiator(closed_initiators[i]); 313 } 314 315 void PrintPreviewDialogController::OnWebContentsDestroyed( 316 WebContents* contents) { 317 WebContents* preview_dialog = GetPrintPreviewForContents(contents); 318 if (!preview_dialog) { 319 NOTREACHED(); 320 return; 321 } 322 323 if (contents == preview_dialog) 324 RemovePreviewDialog(contents); 325 else 326 RemoveInitiator(contents); 327 } 328 329 void PrintPreviewDialogController::OnNavEntryCommitted( 330 WebContents* contents, content::LoadCommittedDetails* details) { 331 WebContents* preview_dialog = GetPrintPreviewForContents(contents); 332 if (!preview_dialog) { 333 NOTREACHED(); 334 return; 335 } 336 337 if (contents == preview_dialog) { 338 // Preview dialog navigated. 339 if (details) { 340 ui::PageTransition transition_type = 341 details->entry->GetTransitionType(); 342 content::NavigationType nav_type = details->type; 343 344 // New |preview_dialog| is created. Don't update/erase map entry. 345 if (waiting_for_new_preview_page_ && 346 transition_type == ui::PAGE_TRANSITION_AUTO_TOPLEVEL && 347 nav_type == content::NAVIGATION_TYPE_NEW_PAGE) { 348 waiting_for_new_preview_page_ = false; 349 SaveInitiatorTitle(preview_dialog); 350 return; 351 } 352 353 // Cloud print sign-in causes a reload. 354 if (!waiting_for_new_preview_page_ && 355 transition_type == ui::PAGE_TRANSITION_RELOAD && 356 nav_type == content::NAVIGATION_TYPE_EXISTING_PAGE && 357 IsPrintPreviewURL(details->previous_url)) { 358 return; 359 } 360 } 361 NOTREACHED(); 362 return; 363 } 364 365 RemoveInitiator(contents); 366 } 367 368 WebContents* PrintPreviewDialogController::CreatePrintPreviewDialog( 369 WebContents* initiator) { 370 base::AutoReset<bool> auto_reset(&is_creating_print_preview_dialog_, true); 371 372 // The dialog delegates are deleted when the dialog is closed. 373 ConstrainedWebDialogDelegate* web_dialog_delegate = 374 CreateConstrainedWebDialog(initiator->GetBrowserContext(), 375 new PrintPreviewDialogDelegate(initiator), 376 initiator); 377 378 WebContents* preview_dialog = web_dialog_delegate->GetWebContents(); 379 EnableInternalPDFPluginForContents(preview_dialog); 380 PrintViewManager::CreateForWebContents(preview_dialog); 381 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents( 382 preview_dialog); 383 384 // Add an entry to the map. 385 preview_dialog_map_[preview_dialog] = initiator; 386 waiting_for_new_preview_page_ = true; 387 388 AddObservers(initiator); 389 AddObservers(preview_dialog); 390 391 return preview_dialog; 392 } 393 394 void PrintPreviewDialogController::SaveInitiatorTitle( 395 WebContents* preview_dialog) { 396 WebContents* initiator = GetInitiator(preview_dialog); 397 if (initiator && preview_dialog->GetWebUI()) { 398 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>( 399 preview_dialog->GetWebUI()->GetController()); 400 print_preview_ui->SetInitiatorTitle( 401 PrintViewManager::FromWebContents(initiator)->RenderSourceName()); 402 } 403 } 404 405 void PrintPreviewDialogController::AddObservers(WebContents* contents) { 406 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 407 content::Source<WebContents>(contents)); 408 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 409 content::Source<NavigationController>(&contents->GetController())); 410 411 // Multiple sites may share the same RenderProcessHost, so check if this 412 // notification has already been added. 413 content::Source<content::RenderProcessHost> rph_source( 414 contents->GetRenderProcessHost()); 415 if (!registrar_.IsRegistered(this, 416 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) { 417 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 418 rph_source); 419 } 420 } 421 422 void PrintPreviewDialogController::RemoveObservers(WebContents* contents) { 423 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 424 content::Source<WebContents>(contents)); 425 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 426 content::Source<NavigationController>(&contents->GetController())); 427 428 // Multiple sites may share the same RenderProcessHost, so check if this 429 // notification has already been added. 430 content::Source<content::RenderProcessHost> rph_source( 431 contents->GetRenderProcessHost()); 432 if (registrar_.IsRegistered(this, 433 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) { 434 registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 435 rph_source); 436 } 437 } 438 439 void PrintPreviewDialogController::RemoveInitiator( 440 WebContents* initiator) { 441 WebContents* preview_dialog = GetPrintPreviewForContents(initiator); 442 DCHECK(preview_dialog); 443 // Update the map entry first, so when the print preview dialog gets destroyed 444 // and reaches RemovePreviewDialog(), it does not attempt to also remove the 445 // initiator's observers. 446 preview_dialog_map_[preview_dialog] = NULL; 447 RemoveObservers(initiator); 448 449 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 450 451 // initiator is closed. Close the print preview dialog too. 452 if (content::WebUI* web_ui = preview_dialog->GetWebUI()) { 453 PrintPreviewUI* print_preview_ui = 454 static_cast<PrintPreviewUI*>(web_ui->GetController()); 455 if (print_preview_ui) 456 print_preview_ui->OnInitiatorClosed(); 457 } 458 } 459 460 void PrintPreviewDialogController::RemovePreviewDialog( 461 WebContents* preview_dialog) { 462 // Remove the initiator's observers before erasing the mapping. 463 WebContents* initiator = GetInitiator(preview_dialog); 464 if (initiator) { 465 RemoveObservers(initiator); 466 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 467 } 468 469 // Print preview WebContents is destroyed. Notify |PrintPreviewUI| to abort 470 // the initiator preview request. 471 if (content::WebUI* web_ui = preview_dialog->GetWebUI()) { 472 PrintPreviewUI* print_preview_ui = 473 static_cast<PrintPreviewUI*>(web_ui->GetController()); 474 if (print_preview_ui) 475 print_preview_ui->OnPrintPreviewDialogDestroyed(); 476 } 477 478 preview_dialog_map_.erase(preview_dialog); 479 RemoveObservers(preview_dialog); 480 } 481 482 } // namespace printing 483