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 47using content::NativeWebKeyboardEvent; 48using content::NavigationController; 49using content::WebContents; 50using content::WebUIMessageHandler; 51using ui::WebDialogDelegate; 52using ui::WebDialogWebContentsDelegate; 53 54namespace { 55 56void 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. 75class 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 98PrintPreviewDialogDelegate::PrintPreviewDialogDelegate(WebContents* initiator) 99 : initiator_(initiator) { 100} 101 102PrintPreviewDialogDelegate::~PrintPreviewDialogDelegate() { 103} 104 105ui::ModalType PrintPreviewDialogDelegate::GetDialogModalType() const { 106 // Not used, returning dummy value. 107 NOTREACHED(); 108 return ui::MODAL_TYPE_WINDOW; 109} 110 111base::string16 PrintPreviewDialogDelegate::GetDialogTitle() const { 112 // Only used on Windows? UI folks prefer no title. 113 return base::string16(); 114} 115 116GURL PrintPreviewDialogDelegate::GetDialogContentURL() const { 117 return GURL(chrome::kChromeUIPrintURL); 118} 119 120void PrintPreviewDialogDelegate::GetWebUIMessageHandlers( 121 std::vector<WebUIMessageHandler*>* /* handlers */) const { 122 // PrintPreviewUI adds its own message handlers. 123} 124 125void 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 152std::string PrintPreviewDialogDelegate::GetDialogArgs() const { 153 return std::string(); 154} 155 156void PrintPreviewDialogDelegate::OnDialogClosed( 157 const std::string& /* json_retval */) { 158} 159 160void PrintPreviewDialogDelegate::OnCloseContents(WebContents* /* source */, 161 bool* out_close_dialog) { 162 if (out_close_dialog) 163 *out_close_dialog = true; 164} 165 166bool PrintPreviewDialogDelegate::ShouldShowDialogTitle() const { 167 return false; 168} 169 170// WebContentsDelegate that forwards shortcut keys in the print preview 171// renderer to the browser. 172class 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 188PrintPreviewWebContentDelegate::PrintPreviewWebContentDelegate( 189 Profile* profile, 190 WebContents* initiator) 191 : WebDialogWebContentsDelegate(profile, new ChromeWebContentsHandler), 192 initiator_(initiator) {} 193 194PrintPreviewWebContentDelegate::~PrintPreviewWebContentDelegate() {} 195 196void 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 210namespace printing { 211 212PrintPreviewDialogController::PrintPreviewDialogController() 213 : waiting_for_new_preview_page_(false), 214 is_creating_print_preview_dialog_(false) { 215} 216 217// static 218PrintPreviewDialogController* PrintPreviewDialogController::GetInstance() { 219 if (!g_browser_process) 220 return NULL; 221 return g_browser_process->print_preview_dialog_controller(); 222} 223 224// static 225void 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 236WebContents* 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 250WebContents* 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 270WebContents* 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 276void 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 296bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents* contents) { 297 return IsPrintPreviewURL(contents->GetURL()); 298} 299 300// static 301bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL& url) { 302 return (url.SchemeIs(chrome::kChromeUIScheme) && 303 url.host() == chrome::kChromeUIPrintHost); 304} 305 306void 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 316PrintPreviewDialogController::~PrintPreviewDialogController() {} 317 318void 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 348void 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 362void 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 401WebContents* 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 433void 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 444void 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 461void 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 478void 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 497void 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