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