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