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/command_line.h" 13 #include "base/path_service.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/browser_process.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/chrome_switches.h" 31 #include "chrome/common/url_constants.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_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()->OverridePluginForTab( 68 preview_dialog->GetRenderProcessHost()->GetID(), 69 preview_dialog->GetRenderViewHost()->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 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 string16 PrintPreviewDialogDelegate::GetDialogTitle() const { 112 // Only used on Windows? UI folks prefer no title. 113 return 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 const int kConstrainedWindowOverlap = 3; 130 gfx::Rect rect; 131 initiator_->GetView()->GetContainerBounds(&rect); 132 size->set_width(std::max(rect.width(), kMinDialogSize.width()) - 2 * kBorder); 133 size->set_height(std::max(rect.height(), kMinDialogSize.height()) - kBorder + 134 kConstrainedWindowOverlap); 135 136 #if defined(OS_MACOSX) 137 // Limit the maximum size on MacOS X. 138 // http://crbug.com/105815 139 const gfx::Size kMaxDialogSize(1000, 660); 140 size->set_width(std::min(size->width(), kMaxDialogSize.width())); 141 size->set_height(std::min(size->height(), kMaxDialogSize.height())); 142 #endif 143 } 144 145 std::string PrintPreviewDialogDelegate::GetDialogArgs() const { 146 return std::string(); 147 } 148 149 void PrintPreviewDialogDelegate::OnDialogClosed( 150 const std::string& /* json_retval */) { 151 } 152 153 void PrintPreviewDialogDelegate::OnCloseContents(WebContents* /* source */, 154 bool* out_close_dialog) { 155 if (out_close_dialog) 156 *out_close_dialog = true; 157 } 158 159 bool PrintPreviewDialogDelegate::ShouldShowDialogTitle() const { 160 return false; 161 } 162 163 // WebContentsDelegate that forwards shortcut keys in the print preview 164 // renderer to the browser. 165 class PrintPreviewWebContentDelegate : public WebDialogWebContentsDelegate { 166 public: 167 PrintPreviewWebContentDelegate(Profile* profile, WebContents* initiator); 168 virtual ~PrintPreviewWebContentDelegate(); 169 170 // Overridden from WebDialogWebContentsDelegate: 171 virtual void HandleKeyboardEvent( 172 WebContents* source, 173 const NativeWebKeyboardEvent& event) OVERRIDE; 174 175 private: 176 WebContents* initiator_; 177 178 DISALLOW_COPY_AND_ASSIGN(PrintPreviewWebContentDelegate); 179 }; 180 181 PrintPreviewWebContentDelegate::PrintPreviewWebContentDelegate( 182 Profile* profile, 183 WebContents* initiator) 184 : WebDialogWebContentsDelegate(profile, new ChromeWebContentsHandler), 185 initiator_(initiator) {} 186 187 PrintPreviewWebContentDelegate::~PrintPreviewWebContentDelegate() {} 188 189 void PrintPreviewWebContentDelegate::HandleKeyboardEvent( 190 WebContents* source, 191 const NativeWebKeyboardEvent& event) { 192 // Disabled on Mac due to http://crbug.com/112173 193 #if !defined(OS_MACOSX) 194 Browser* current_browser = chrome::FindBrowserWithWebContents(initiator_); 195 if (!current_browser) 196 return; 197 current_browser->window()->HandleKeyboardEvent(event); 198 #endif 199 } 200 201 } // namespace 202 203 namespace printing { 204 205 PrintPreviewDialogController::PrintPreviewDialogController() 206 : waiting_for_new_preview_page_(false), 207 is_creating_print_preview_dialog_(false) { 208 } 209 210 // static 211 PrintPreviewDialogController* PrintPreviewDialogController::GetInstance() { 212 if (!g_browser_process) 213 return NULL; 214 return g_browser_process->print_preview_dialog_controller(); 215 } 216 217 // static 218 void PrintPreviewDialogController::PrintPreview(WebContents* initiator) { 219 if (initiator->ShowingInterstitialPage()) 220 return; 221 222 PrintPreviewDialogController* dialog_controller = GetInstance(); 223 if (!dialog_controller) 224 return; 225 if (!dialog_controller->GetOrCreatePreviewDialog(initiator)) 226 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 227 } 228 229 WebContents* PrintPreviewDialogController::GetOrCreatePreviewDialog( 230 WebContents* initiator) { 231 DCHECK(initiator); 232 233 // Get the print preview dialog for |initiator|. 234 WebContents* preview_dialog = GetPrintPreviewForContents(initiator); 235 if (!preview_dialog) 236 return CreatePrintPreviewDialog(initiator); 237 238 // Show the initiator holding the existing preview dialog. 239 initiator->GetDelegate()->ActivateContents(initiator); 240 return preview_dialog; 241 } 242 243 WebContents* PrintPreviewDialogController::GetPrintPreviewForContents( 244 WebContents* contents) const { 245 // |preview_dialog_map_| is keyed by the preview dialog, so if find() 246 // succeeds, then |contents| is the preview dialog. 247 PrintPreviewDialogMap::const_iterator it = preview_dialog_map_.find(contents); 248 if (it != preview_dialog_map_.end()) 249 return contents; 250 251 for (it = preview_dialog_map_.begin(); 252 it != preview_dialog_map_.end(); 253 ++it) { 254 // If |contents| is an initiator. 255 if (contents == it->second) { 256 // Return the associated preview dialog. 257 return it->first; 258 } 259 } 260 return NULL; 261 } 262 263 WebContents* PrintPreviewDialogController::GetInitiator( 264 WebContents* preview_dialog) { 265 PrintPreviewDialogMap::iterator it = preview_dialog_map_.find(preview_dialog); 266 return (it != preview_dialog_map_.end()) ? it->second : NULL; 267 } 268 269 void PrintPreviewDialogController::Observe( 270 int type, 271 const content::NotificationSource& source, 272 const content::NotificationDetails& details) { 273 if (type == content::NOTIFICATION_RENDERER_PROCESS_CLOSED) { 274 OnRendererProcessClosed( 275 content::Source<content::RenderProcessHost>(source).ptr()); 276 } else if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { 277 OnWebContentsDestroyed(content::Source<WebContents>(source).ptr()); 278 } else { 279 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type); 280 WebContents* contents = 281 content::Source<NavigationController>(source)->GetWebContents(); 282 OnNavEntryCommitted( 283 contents, 284 content::Details<content::LoadCommittedDetails>(details).ptr()); 285 } 286 } 287 288 // static 289 bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents* contents) { 290 return IsPrintPreviewURL(contents->GetURL()); 291 } 292 293 // static 294 bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL& url) { 295 return (url.SchemeIs(chrome::kChromeUIScheme) && 296 url.host() == chrome::kChromeUIPrintHost); 297 } 298 299 void PrintPreviewDialogController::EraseInitiatorInfo( 300 WebContents* preview_dialog) { 301 PrintPreviewDialogMap::iterator it = preview_dialog_map_.find(preview_dialog); 302 if (it == preview_dialog_map_.end()) 303 return; 304 305 RemoveObservers(it->second); 306 preview_dialog_map_[preview_dialog] = NULL; 307 } 308 309 PrintPreviewDialogController::~PrintPreviewDialogController() {} 310 311 void PrintPreviewDialogController::OnRendererProcessClosed( 312 content::RenderProcessHost* rph) { 313 // Store contents in a vector and deal with them after iterating through 314 // |preview_dialog_map_| because RemoveFoo() can change |preview_dialog_map_|. 315 std::vector<WebContents*> closed_initiators; 316 std::vector<WebContents*> closed_preview_dialogs; 317 for (PrintPreviewDialogMap::iterator iter = preview_dialog_map_.begin(); 318 iter != preview_dialog_map_.end(); ++iter) { 319 WebContents* preview_dialog = iter->first; 320 WebContents* initiator = iter->second; 321 if (preview_dialog->GetRenderProcessHost() == rph) { 322 closed_preview_dialogs.push_back(preview_dialog); 323 } else if (initiator && 324 initiator->GetRenderProcessHost() == rph) { 325 closed_initiators.push_back(initiator); 326 } 327 } 328 329 for (size_t i = 0; i < closed_preview_dialogs.size(); ++i) { 330 RemovePreviewDialog(closed_preview_dialogs[i]); 331 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>( 332 closed_preview_dialogs[i]->GetWebUI()->GetController()); 333 if (print_preview_ui) 334 print_preview_ui->OnPrintPreviewDialogClosed(); 335 } 336 337 for (size_t i = 0; i < closed_initiators.size(); ++i) 338 RemoveInitiator(closed_initiators[i]); 339 } 340 341 void PrintPreviewDialogController::OnWebContentsDestroyed( 342 WebContents* contents) { 343 WebContents* preview_dialog = GetPrintPreviewForContents(contents); 344 if (!preview_dialog) { 345 NOTREACHED(); 346 return; 347 } 348 349 if (contents == preview_dialog) 350 RemovePreviewDialog(contents); 351 else 352 RemoveInitiator(contents); 353 } 354 355 void PrintPreviewDialogController::OnNavEntryCommitted( 356 WebContents* contents, content::LoadCommittedDetails* details) { 357 WebContents* preview_dialog = GetPrintPreviewForContents(contents); 358 if (!preview_dialog) { 359 NOTREACHED(); 360 return; 361 } 362 363 if (contents == preview_dialog) { 364 // Preview dialog navigated. 365 if (details) { 366 content::PageTransition transition_type = 367 details->entry->GetTransitionType(); 368 content::NavigationType nav_type = details->type; 369 370 // New |preview_dialog| is created. Don't update/erase map entry. 371 if (waiting_for_new_preview_page_ && 372 transition_type == content::PAGE_TRANSITION_AUTO_TOPLEVEL && 373 nav_type == content::NAVIGATION_TYPE_NEW_PAGE) { 374 waiting_for_new_preview_page_ = false; 375 SaveInitiatorTitle(preview_dialog); 376 return; 377 } 378 379 // Cloud print sign-in causes a reload. 380 if (!waiting_for_new_preview_page_ && 381 transition_type == content::PAGE_TRANSITION_RELOAD && 382 nav_type == content::NAVIGATION_TYPE_EXISTING_PAGE && 383 IsPrintPreviewURL(details->previous_url)) { 384 return; 385 } 386 } 387 NOTREACHED(); 388 return; 389 } 390 391 RemoveInitiator(contents); 392 } 393 394 WebContents* PrintPreviewDialogController::CreatePrintPreviewDialog( 395 WebContents* initiator) { 396 base::AutoReset<bool> auto_reset(&is_creating_print_preview_dialog_, true); 397 Profile* profile = 398 Profile::FromBrowserContext(initiator->GetBrowserContext()); 399 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeFrame)) { 400 // Chrome Frame only ever runs on the native desktop, so it is safe to 401 // create the popup on the native desktop. 402 Browser* current_browser = new Browser( 403 Browser::CreateParams(Browser::TYPE_POPUP, profile, 404 chrome::GetActiveDesktop())); 405 if (!current_browser) { 406 NOTREACHED() << "Failed to create popup browser window"; 407 return NULL; 408 } 409 } 410 411 // |web_dialog_ui_delegate| deletes itself in 412 // PrintPreviewDialogDelegate::OnDialogClosed(). 413 WebDialogDelegate* web_dialog_delegate = 414 new PrintPreviewDialogDelegate(initiator); 415 // |web_dialog_delegate|'s owner is |constrained_delegate|. 416 PrintPreviewWebContentDelegate* pp_wcd = 417 new PrintPreviewWebContentDelegate(profile, initiator); 418 ConstrainedWebDialogDelegate* constrained_delegate = 419 CreateConstrainedWebDialog(profile, 420 web_dialog_delegate, 421 pp_wcd, 422 initiator); 423 WebContents* preview_dialog = constrained_delegate->GetWebContents(); 424 EnableInternalPDFPluginForContents(preview_dialog); 425 PrintViewManager::CreateForWebContents(preview_dialog); 426 427 // Add an entry to the map. 428 preview_dialog_map_[preview_dialog] = initiator; 429 waiting_for_new_preview_page_ = true; 430 431 AddObservers(initiator); 432 AddObservers(preview_dialog); 433 434 return preview_dialog; 435 } 436 437 void PrintPreviewDialogController::SaveInitiatorTitle( 438 WebContents* preview_dialog) { 439 WebContents* initiator = GetInitiator(preview_dialog); 440 if (initiator && preview_dialog->GetWebUI()) { 441 PrintPreviewUI* print_preview_ui = static_cast<PrintPreviewUI*>( 442 preview_dialog->GetWebUI()->GetController()); 443 print_preview_ui->SetInitiatorTitle( 444 PrintViewManager::FromWebContents(initiator)->RenderSourceName()); 445 } 446 } 447 448 void PrintPreviewDialogController::AddObservers(WebContents* contents) { 449 registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 450 content::Source<WebContents>(contents)); 451 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 452 content::Source<NavigationController>(&contents->GetController())); 453 454 // Multiple sites may share the same RenderProcessHost, so check if this 455 // notification has already been added. 456 content::Source<content::RenderProcessHost> rph_source( 457 contents->GetRenderProcessHost()); 458 if (!registrar_.IsRegistered(this, 459 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) { 460 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 461 rph_source); 462 } 463 } 464 465 void PrintPreviewDialogController::RemoveObservers(WebContents* contents) { 466 registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 467 content::Source<WebContents>(contents)); 468 registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, 469 content::Source<NavigationController>(&contents->GetController())); 470 471 // Multiple sites may share the same RenderProcessHost, so check if this 472 // notification has already been added. 473 content::Source<content::RenderProcessHost> rph_source( 474 contents->GetRenderProcessHost()); 475 if (registrar_.IsRegistered(this, 476 content::NOTIFICATION_RENDERER_PROCESS_CLOSED, rph_source)) { 477 registrar_.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED, 478 rph_source); 479 } 480 } 481 482 void PrintPreviewDialogController::RemoveInitiator( 483 WebContents* initiator) { 484 WebContents* preview_dialog = GetPrintPreviewForContents(initiator); 485 DCHECK(preview_dialog); 486 // Update the map entry first, so when the print preview dialog gets destroyed 487 // and reaches RemovePreviewDialog(), it does not attempt to also remove the 488 // initiator's observers. 489 preview_dialog_map_[preview_dialog] = NULL; 490 RemoveObservers(initiator); 491 492 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 493 494 // initiator is closed. Close the print preview dialog too. 495 PrintPreviewUI* print_preview_ui = 496 static_cast<PrintPreviewUI*>(preview_dialog->GetWebUI()->GetController()); 497 if (print_preview_ui) 498 print_preview_ui->OnInitiatorClosed(); 499 } 500 501 void PrintPreviewDialogController::RemovePreviewDialog( 502 WebContents* preview_dialog) { 503 // Remove the initiator's observers before erasing the mapping. 504 WebContents* initiator = GetInitiator(preview_dialog); 505 if (initiator) { 506 RemoveObservers(initiator); 507 PrintViewManager::FromWebContents(initiator)->PrintPreviewDone(); 508 } 509 510 // Print preview WebContents is destroyed. Notify |PrintPreviewUI| to abort 511 // the initiator preview request. 512 PrintPreviewUI* print_preview_ui = 513 static_cast<PrintPreviewUI*>(preview_dialog->GetWebUI()->GetController()); 514 if (print_preview_ui) 515 print_preview_ui->OnPrintPreviewDialogDestroyed(); 516 517 preview_dialog_map_.erase(preview_dialog); 518 RemoveObservers(preview_dialog); 519 } 520 521 } // namespace printing 522