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/chrome_notification_types.h" 15 #include "chrome/browser/browser_process.h" 16 #include "chrome/browser/plugins/chrome_plugin_service_filter.h" 17 #include "chrome/browser/printing/print_view_manager.h" 18 #include "chrome/browser/profiles/profile.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/browser/web_contents_view.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_->GetView()->GetContainerSize()); 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 // static 296 bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents* contents) { 297 return IsPrintPreviewURL(contents->GetURL()); 298 } 299 300 // static 301 bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL& url) { 302 return (url.SchemeIs(chrome::kChromeUIScheme) && 303 url.host() == chrome::kChromeUIPrintHost); 304 } 305 306 void PrintPreviewDialogController::EraseInitiatorInfo( 307 WebContents* preview_dialog) { 308 PrintPreviewDialogMap::iterator it = preview_dialog_map_.find(preview_dialog); 309 if (it == preview_dialog_map_.end()) 310 return; 311 312 RemoveObservers(it->second); 313 preview_dialog_map_[preview_dialog] = NULL; 314 } 315 316 PrintPreviewDialogController::~PrintPreviewDialogController() {} 317 318 void PrintPreviewDialogController::OnRendererProcessClosed( 319 content::RenderProcessHost* rph) { 320 // Store contents in a vector and deal with them after iterating through 321 // |preview_dialog_map_| because RemoveFoo() can change |preview_dialog_map_|. 322 std::vector<WebContents*> closed_initiators; 323 std::vector<WebContents*> closed_preview_dialogs; 324 for (PrintPreviewDialogMap::iterator iter = preview_dialog_map_.begin(); 325 iter != preview_dialog_map_.end(); ++iter) { 326 WebContents* preview_dialog = iter->first; 327 WebContents* initiator = iter->second; 328 if (preview_dialog->GetRenderProcessHost() == rph) { 329 closed_preview_dialogs.push_back(preview_dialog); 330 } else if (initiator && 331 initiator->GetRenderProcessHost() == rph) { 332 closed_initiators.push_back(initiator); 333 } 334 } 335 336 for (size_t i = 0; i < closed_preview_dialogs.size(); ++i) { 337 RemovePreviewDialog(closed_preview_dialogs[i]); 338 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>( 339 closed_preview_dialogs[i]->GetWebUI()->GetController()); 340 if (print_preview_ui) 341 print_preview_ui->OnPrintPreviewDialogClosed(); 342 } 343 344 for (size_t i = 0; i < closed_initiators.size(); ++i) 345 RemoveInitiator(closed_initiators[i]); 346 } 347 348 void PrintPreviewDialogController::OnWebContentsDestroyed( 349 WebContents* contents) { 350 WebContents* preview_dialog = GetPrintPreviewForContents(contents); 351 if (!preview_dialog) { 352 NOTREACHED(); 353 return; 354 } 355 356 if (contents == preview_dialog) 357 RemovePreviewDialog(contents); 358 else 359 RemoveInitiator(contents); 360 } 361 362 void PrintPreviewDialogController::OnNavEntryCommitted( 363 WebContents* contents, content::LoadCommittedDetails* details) { 364 WebContents* preview_dialog = GetPrintPreviewForContents(contents); 365 if (!preview_dialog) { 366 NOTREACHED(); 367 return; 368 } 369 370 if (contents == preview_dialog) { 371 // Preview dialog navigated. 372 if (details) { 373 content::PageTransition transition_type = 374 details->entry->GetTransitionType(); 375 content::NavigationType nav_type = details->type; 376 377 // New |preview_dialog| is created. Don't update/erase map entry. 378 if (waiting_for_new_preview_page_ && 379 transition_type == content::PAGE_TRANSITION_AUTO_TOPLEVEL && 380 nav_type == content::NAVIGATION_TYPE_NEW_PAGE) { 381 waiting_for_new_preview_page_ = false; 382 SaveInitiatorTitle(preview_dialog); 383 return; 384 } 385 386 // Cloud print sign-in causes a reload. 387 if (!waiting_for_new_preview_page_ && 388 transition_type == content::PAGE_TRANSITION_RELOAD && 389 nav_type == content::NAVIGATION_TYPE_EXISTING_PAGE && 390 IsPrintPreviewURL(details->previous_url)) { 391 return; 392 } 393 } 394 NOTREACHED(); 395 return; 396 } 397 398 RemoveInitiator(contents); 399 } 400 401 WebContents* PrintPreviewDialogController::CreatePrintPreviewDialog( 402 WebContents* initiator) { 403 base::AutoReset<bool> auto_reset(&is_creating_print_preview_dialog_, true); 404 Profile* profile = 405 Profile::FromBrowserContext(initiator->GetBrowserContext()); 406 407 // |web_dialog_ui_delegate| deletes itself in 408 // PrintPreviewDialogDelegate::OnDialogClosed(). 409 WebDialogDelegate* web_dialog_delegate = 410 new PrintPreviewDialogDelegate(initiator); 411 // |web_dialog_delegate|'s owner is |constrained_delegate|. 412 PrintPreviewWebContentDelegate* pp_wcd = 413 new PrintPreviewWebContentDelegate(profile, initiator); 414 ConstrainedWebDialogDelegate* constrained_delegate = 415 CreateConstrainedWebDialog(profile, 416 web_dialog_delegate, 417 pp_wcd, 418 initiator); 419 WebContents* preview_dialog = constrained_delegate->GetWebContents(); 420 EnableInternalPDFPluginForContents(preview_dialog); 421 PrintViewManager::CreateForWebContents(preview_dialog); 422 423 // Add an entry to the map. 424 preview_dialog_map_[preview_dialog] = initiator; 425 waiting_for_new_preview_page_ = true; 426 427 AddObservers(initiator); 428 AddObservers(preview_dialog); 429 430 return preview_dialog; 431 } 432 433 void PrintPreviewDialogController::SaveInitiatorTitle( 434 WebContents* preview_dialog) { 435 WebContents* initiator = GetInitiator(preview_dialog); 436 if (initiator && preview_dialog->GetWebUI()) { 437 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>( 438 preview_dialog->GetWebUI()->GetController()); 439 print_preview_ui->SetInitiatorTitle( 440 PrintViewManager::FromWebContents(initiator)->RenderSourceName()); 441 } 442 } 443 444 void PrintPreviewDialogController::AddObservers(WebContents* contents) { 445 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 446 content::Source<WebContents>(contents)); 447 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 448 content::Source<NavigationController>(&contents->GetController())); 449 450 // Multiple sites may share the same RenderProcessHost, so check if this 451 // notification has already been added. 452 content::Source<content::RenderProcessHost> rph_source( 453 contents->GetRenderProcessHost()); 454 if (!registrar_.IsRegistered(this, 455 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) { 456 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 457 rph_source); 458 } 459 } 460 461 void PrintPreviewDialogController::RemoveObservers(WebContents* contents) { 462 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 463 content::Source<WebContents>(contents)); 464 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 465 content::Source<NavigationController>(&contents->GetController())); 466 467 // Multiple sites may share the same RenderProcessHost, so check if this 468 // notification has already been added. 469 content::Source<content::RenderProcessHost> rph_source( 470 contents->GetRenderProcessHost()); 471 if (registrar_.IsRegistered(this, 472 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) { 473 registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 474 rph_source); 475 } 476 } 477 478 void PrintPreviewDialogController::RemoveInitiator( 479 WebContents* initiator) { 480 WebContents* preview_dialog = GetPrintPreviewForContents(initiator); 481 DCHECK(preview_dialog); 482 // Update the map entry first, so when the print preview dialog gets destroyed 483 // and reaches RemovePreviewDialog(), it does not attempt to also remove the 484 // initiator's observers. 485 preview_dialog_map_[preview_dialog] = NULL; 486 RemoveObservers(initiator); 487 488 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 489 490 // initiator is closed. Close the print preview dialog too. 491 PrintPreviewUI* print_preview_ui = 492 static_cast<PrintPreviewUI*>(preview_dialog->GetWebUI()->GetController()); 493 if (print_preview_ui) 494 print_preview_ui->OnInitiatorClosed(); 495 } 496 497 void PrintPreviewDialogController::RemovePreviewDialog( 498 WebContents* preview_dialog) { 499 // Remove the initiator's observers before erasing the mapping. 500 WebContents* initiator = GetInitiator(preview_dialog); 501 if (initiator) { 502 RemoveObservers(initiator); 503 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 504 } 505 506 // Print preview WebContents is destroyed. Notify |PrintPreviewUI| to abort 507 // the initiator preview request. 508 PrintPreviewUI* print_preview_ui = 509 static_cast<PrintPreviewUI*>(preview_dialog->GetWebUI()->GetController()); 510 if (print_preview_ui) 511 print_preview_ui->OnPrintPreviewDialogDestroyed(); 512 513 preview_dialog_map_.erase(preview_dialog); 514 RemoveObservers(preview_dialog); 515 } 516 517 } // namespace printing 518