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/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