Home | History | Annotate | Download | only in printing
      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