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
45using content::NavigationController;
46using content::WebContents;
47using content::WebUIMessageHandler;
48
49namespace {
50
51void 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.
69class 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
92PrintPreviewDialogDelegate::PrintPreviewDialogDelegate(WebContents* initiator)
93    : initiator_(initiator) {
94}
95
96PrintPreviewDialogDelegate::~PrintPreviewDialogDelegate() {
97}
98
99ui::ModalType PrintPreviewDialogDelegate::GetDialogModalType() const {
100  // Not used, returning dummy value.
101  NOTREACHED();
102  return ui::MODAL_TYPE_WINDOW;
103}
104
105base::string16 PrintPreviewDialogDelegate::GetDialogTitle() const {
106  // Only used on Windows? UI folks prefer no title.
107  return base::string16();
108}
109
110GURL PrintPreviewDialogDelegate::GetDialogContentURL() const {
111  return GURL(chrome::kChromeUIPrintURL);
112}
113
114void PrintPreviewDialogDelegate::GetWebUIMessageHandlers(
115    std::vector<WebUIMessageHandler*>* /* handlers */) const {
116  // PrintPreviewUI adds its own message handlers.
117}
118
119void 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
146std::string PrintPreviewDialogDelegate::GetDialogArgs() const {
147  return std::string();
148}
149
150void PrintPreviewDialogDelegate::OnDialogClosed(
151    const std::string& /* json_retval */) {
152}
153
154void PrintPreviewDialogDelegate::OnCloseContents(WebContents* /* source */,
155                                                 bool* out_close_dialog) {
156  if (out_close_dialog)
157    *out_close_dialog = true;
158}
159
160bool PrintPreviewDialogDelegate::ShouldShowDialogTitle() const {
161  return false;
162}
163
164}  // namespace
165
166namespace printing {
167
168PrintPreviewDialogController::PrintPreviewDialogController()
169    : waiting_for_new_preview_page_(false),
170      is_creating_print_preview_dialog_(false) {
171}
172
173// static
174PrintPreviewDialogController* PrintPreviewDialogController::GetInstance() {
175  if (!g_browser_process)
176    return NULL;
177  return g_browser_process->print_preview_dialog_controller();
178}
179
180// static
181void 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
192WebContents* 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
206WebContents* 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
226WebContents* 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
232void 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
251void 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
261bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents* contents) {
262  return IsPrintPreviewURL(contents->GetURL());
263}
264
265// static
266bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL& url) {
267  return (url.SchemeIs(content::kChromeUIScheme) &&
268          url.host() == chrome::kChromeUIPrintHost);
269}
270
271void 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
281PrintPreviewDialogController::~PrintPreviewDialogController() {}
282
283void 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
315void 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
329void 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
368WebContents* 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
394void 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
405void 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
422void 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
439void 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
460void 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