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