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.
5 #include "chrome/browser/printing/print_preview_dialog_controller.h"
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"
47 using content::NativeWebKeyboardEvent
;
48 using content::NavigationController
;
49 using content::WebContents
;
50 using content::WebUIMessageHandler
;
51 using ui::WebDialogDelegate
;
52 using ui::WebDialogWebContentsDelegate
;
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
))
62 content::WebPluginInfo pdf_plugin
;
63 if (!content::PluginService::GetInstance()->GetPluginInfoByPath(
64 pdf_plugin_path
, &pdf_plugin
))
67 ChromePluginServiceFilter::GetInstance()->OverridePluginForFrame(
68 preview_dialog
->GetRenderProcessHost()->GetID(),
69 preview_dialog
->GetMainFrame()->GetRoutingID(),
73 // WebDialogDelegate that specifies what the print preview dialog
75 class PrintPreviewDialogDelegate
: public WebDialogDelegate
{
77 explicit PrintPreviewDialogDelegate(WebContents
* initiator
);
78 virtual ~PrintPreviewDialogDelegate();
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
;
93 WebContents
* initiator_
;
95 DISALLOW_COPY_AND_ASSIGN(PrintPreviewDialogDelegate
);
98 PrintPreviewDialogDelegate::PrintPreviewDialogDelegate(WebContents
* initiator
)
99 : initiator_(initiator
) {
102 PrintPreviewDialogDelegate::~PrintPreviewDialogDelegate() {
105 ui::ModalType
PrintPreviewDialogDelegate::GetDialogModalType() const {
106 // Not used, returning dummy value.
108 return ui::MODAL_TYPE_WINDOW
;
111 base::string16
PrintPreviewDialogDelegate::GetDialogTitle() const {
112 // Only used on Windows? UI folks prefer no title.
113 return base::string16();
116 GURL
PrintPreviewDialogDelegate::GetDialogContentURL() const {
117 return GURL(chrome::kChromeUIPrintURL
);
120 void PrintPreviewDialogDelegate::GetWebUIMessageHandlers(
121 std::vector
<WebUIMessageHandler
*>* /* handlers */) const {
122 // PrintPreviewUI adds its own message handlers.
125 void PrintPreviewDialogDelegate::GetDialogSize(gfx::Size
* size
) const {
127 const gfx::Size
kMinDialogSize(800, 480);
128 const int kBorder
= 25;
129 *size
= kMinDialogSize
;
131 web_modal::WebContentsModalDialogHost
* host
= NULL
;
132 Browser
* browser
= chrome::FindBrowserWithWebContents(initiator_
);
134 host
= browser
->window()->GetWebContentsModalDialogHost();
137 size
->SetToMax(host
->GetMaximumDialogSize());
138 size
->Enlarge(-2 * kBorder
, -kBorder
);
140 size
->SetToMax(initiator_
->GetContainerBounds().size());
141 size
->Enlarge(-2 * kBorder
, -2 * kBorder
);
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
);
152 std::string
PrintPreviewDialogDelegate::GetDialogArgs() const {
153 return std::string();
156 void PrintPreviewDialogDelegate::OnDialogClosed(
157 const std::string
& /* json_retval */) {
160 void PrintPreviewDialogDelegate::OnCloseContents(WebContents
* /* source */,
161 bool* out_close_dialog
) {
162 if (out_close_dialog
)
163 *out_close_dialog
= true;
166 bool PrintPreviewDialogDelegate::ShouldShowDialogTitle() const {
170 // WebContentsDelegate that forwards shortcut keys in the print preview
171 // renderer to the browser.
172 class PrintPreviewWebContentDelegate
: public WebDialogWebContentsDelegate
{
174 PrintPreviewWebContentDelegate(Profile
* profile
, WebContents
* initiator
);
175 virtual ~PrintPreviewWebContentDelegate();
177 // Overridden from WebDialogWebContentsDelegate:
178 virtual void HandleKeyboardEvent(
180 const NativeWebKeyboardEvent
& event
) OVERRIDE
;
183 WebContents
* initiator_
;
185 DISALLOW_COPY_AND_ASSIGN(PrintPreviewWebContentDelegate
);
188 PrintPreviewWebContentDelegate::PrintPreviewWebContentDelegate(
190 WebContents
* initiator
)
191 : WebDialogWebContentsDelegate(profile
, new ChromeWebContentsHandler
),
192 initiator_(initiator
) {}
194 PrintPreviewWebContentDelegate::~PrintPreviewWebContentDelegate() {}
196 void PrintPreviewWebContentDelegate::HandleKeyboardEvent(
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
)
204 current_browser
->window()->HandleKeyboardEvent(event
);
212 PrintPreviewDialogController::PrintPreviewDialogController()
213 : waiting_for_new_preview_page_(false),
214 is_creating_print_preview_dialog_(false) {
218 PrintPreviewDialogController
* PrintPreviewDialogController::GetInstance() {
219 if (!g_browser_process
)
221 return g_browser_process
->print_preview_dialog_controller();
225 void PrintPreviewDialogController::PrintPreview(WebContents
* initiator
) {
226 if (initiator
->ShowingInterstitialPage())
229 PrintPreviewDialogController
* dialog_controller
= GetInstance();
230 if (!dialog_controller
)
232 if (!dialog_controller
->GetOrCreatePreviewDialog(initiator
))
233 PrintViewManager::FromWebContents(initiator
)->PrintPreviewDone();
236 WebContents
* PrintPreviewDialogController::GetOrCreatePreviewDialog(
237 WebContents
* initiator
) {
240 // Get the print preview dialog for |initiator|.
241 WebContents
* preview_dialog
= GetPrintPreviewForContents(initiator
);
243 return CreatePrintPreviewDialog(initiator
);
245 // Show the initiator holding the existing preview dialog.
246 initiator
->GetDelegate()->ActivateContents(initiator
);
247 return preview_dialog
;
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())
258 for (it
= preview_dialog_map_
.begin();
259 it
!= preview_dialog_map_
.end();
261 // If |contents| is an initiator.
262 if (contents
== it
->second
) {
263 // Return the associated preview dialog.
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
;
276 void PrintPreviewDialogController::Observe(
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());
286 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED
, type
);
287 WebContents
* contents
=
288 content::Source
<NavigationController
>(source
)->GetWebContents();
291 content::Details
<content::LoadCommittedDetails
>(details
).ptr());
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();
300 callback
.Run(it
->first
);
305 bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents
* contents
) {
306 return IsPrintPreviewURL(contents
->GetURL());
310 bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL
& url
) {
311 return (url
.SchemeIs(content::kChromeUIScheme
) &&
312 url
.host() == chrome::kChromeUIPrintHost
);
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())
321 RemoveObservers(it
->second
);
322 preview_dialog_map_
[preview_dialog
] = NULL
;
325 PrintPreviewDialogController::~PrintPreviewDialogController() {}
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
);
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();
355 for (size_t i
= 0; i
< closed_initiators
.size(); ++i
)
356 RemoveInitiator(closed_initiators
[i
]);
359 void PrintPreviewDialogController::OnWebContentsDestroyed(
360 WebContents
* contents
) {
361 WebContents
* preview_dialog
= GetPrintPreviewForContents(contents
);
362 if (!preview_dialog
) {
367 if (contents
== preview_dialog
)
368 RemovePreviewDialog(contents
);
370 RemoveInitiator(contents
);
373 void PrintPreviewDialogController::OnNavEntryCommitted(
374 WebContents
* contents
, content::LoadCommittedDetails
* details
) {
375 WebContents
* preview_dialog
= GetPrintPreviewForContents(contents
);
376 if (!preview_dialog
) {
381 if (contents
== preview_dialog
) {
382 // Preview dialog navigated.
384 content::PageTransition transition_type
=
385 details
->entry
->GetTransitionType();
386 content::NavigationType nav_type
= details
->type
;
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
);
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
)) {
409 RemoveInitiator(contents
);
412 WebContents
* PrintPreviewDialogController::CreatePrintPreviewDialog(
413 WebContents
* initiator
) {
414 base::AutoReset
<bool> auto_reset(&is_creating_print_preview_dialog_
, true);
416 Profile::FromBrowserContext(initiator
->GetBrowserContext());
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
,
430 WebContents
* preview_dialog
= constrained_delegate
->GetWebContents();
431 EnableInternalPDFPluginForContents(preview_dialog
);
432 PrintViewManager::CreateForWebContents(preview_dialog
);
433 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
436 // Add an entry to the map.
437 preview_dialog_map_
[preview_dialog
] = initiator
;
438 waiting_for_new_preview_page_
= true;
440 AddObservers(initiator
);
441 AddObservers(preview_dialog
);
443 return preview_dialog
;
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());
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()));
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
,
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()));
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
,
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
);
501 PrintViewManager::FromWebContents(initiator
)->PrintPreviewDone();
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();
512 void PrintPreviewDialogController::RemovePreviewDialog(
513 WebContents
* preview_dialog
) {
514 // Remove the initiator's observers before erasing the mapping.
515 WebContents
* initiator
= GetInitiator(preview_dialog
);
517 RemoveObservers(initiator
);
518 PrintViewManager::FromWebContents(initiator
)->PrintPreviewDone();
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();
530 preview_dialog_map_
.erase(preview_dialog
);
531 RemoveObservers(preview_dialog
);
534 } // namespace printing