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/printing/print_view_manager.h"
18 #include "chrome/browser/task_management/web_contents_tags.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_paths.h"
28 #include "chrome/common/url_constants.h"
29 #include "components/guest_view/browser/guest_view_base.h"
30 #include "components/web_modal/web_contents_modal_dialog_host.h"
31 #include "content/public/browser/host_zoom_map.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/render_view_host.h"
38 #include "content/public/browser/web_contents.h"
39 #include "content/public/browser/web_contents_delegate.h"
40 #include "ui/web_dialogs/web_dialog_delegate.h"
42 using content::NavigationController
;
43 using content::WebContents
;
44 using content::WebUIMessageHandler
;
48 // A ui::WebDialogDelegate that specifies the print preview dialog appearance.
49 class PrintPreviewDialogDelegate
: public ui::WebDialogDelegate
{
51 explicit PrintPreviewDialogDelegate(WebContents
* initiator
);
52 ~PrintPreviewDialogDelegate() override
;
54 ui::ModalType
GetDialogModalType() const override
;
55 base::string16
GetDialogTitle() const override
;
56 GURL
GetDialogContentURL() const override
;
57 void GetWebUIMessageHandlers(
58 std::vector
<WebUIMessageHandler
*>* handlers
) const override
;
59 void GetDialogSize(gfx::Size
* size
) const override
;
60 std::string
GetDialogArgs() const override
;
61 void OnDialogClosed(const std::string
& json_retval
) override
;
62 void OnCloseContents(WebContents
* source
, bool* out_close_dialog
) override
;
63 bool ShouldShowDialogTitle() const override
;
66 WebContents
* initiator_
;
68 DISALLOW_COPY_AND_ASSIGN(PrintPreviewDialogDelegate
);
71 PrintPreviewDialogDelegate::PrintPreviewDialogDelegate(WebContents
* initiator
)
72 : initiator_(initiator
) {
75 PrintPreviewDialogDelegate::~PrintPreviewDialogDelegate() {
78 ui::ModalType
PrintPreviewDialogDelegate::GetDialogModalType() const {
79 // Not used, returning dummy value.
81 return ui::MODAL_TYPE_WINDOW
;
84 base::string16
PrintPreviewDialogDelegate::GetDialogTitle() const {
85 // Only used on Windows? UI folks prefer no title.
86 return base::string16();
89 GURL
PrintPreviewDialogDelegate::GetDialogContentURL() const {
90 return GURL(chrome::kChromeUIPrintURL
);
93 void PrintPreviewDialogDelegate::GetWebUIMessageHandlers(
94 std::vector
<WebUIMessageHandler
*>* /* handlers */) const {
95 // PrintPreviewUI adds its own message handlers.
98 void PrintPreviewDialogDelegate::GetDialogSize(gfx::Size
* size
) const {
100 const gfx::Size
kMinDialogSize(800, 480);
101 const int kBorder
= 25;
102 *size
= kMinDialogSize
;
104 web_modal::WebContentsModalDialogHost
* host
= nullptr;
105 content::WebContents
* outermost_web_contents
=
106 guest_view::GuestViewBase::GetTopLevelWebContents(initiator_
);
107 Browser
* browser
= chrome::FindBrowserWithWebContents(outermost_web_contents
);
109 host
= browser
->window()->GetWebContentsModalDialogHost();
112 size
->SetToMax(host
->GetMaximumDialogSize());
114 size
->SetToMax(outermost_web_contents
->GetContainerBounds().size());
115 size
->Enlarge(-2 * kBorder
, -kBorder
);
117 #if defined(OS_MACOSX)
118 // Limit the maximum size on MacOS X.
119 // http://crbug.com/105815
120 const gfx::Size
kMaxDialogSize(1000, 660);
121 size
->SetToMin(kMaxDialogSize
);
125 std::string
PrintPreviewDialogDelegate::GetDialogArgs() const {
126 return std::string();
129 void PrintPreviewDialogDelegate::OnDialogClosed(
130 const std::string
& /* json_retval */) {
133 void PrintPreviewDialogDelegate::OnCloseContents(WebContents
* /* source */,
134 bool* out_close_dialog
) {
135 *out_close_dialog
= true;
138 bool PrintPreviewDialogDelegate::ShouldShowDialogTitle() const {
146 PrintPreviewDialogController::PrintPreviewDialogController()
147 : waiting_for_new_preview_page_(false),
148 is_creating_print_preview_dialog_(false) {
152 PrintPreviewDialogController
* PrintPreviewDialogController::GetInstance() {
153 if (!g_browser_process
)
155 return g_browser_process
->print_preview_dialog_controller();
159 void PrintPreviewDialogController::PrintPreview(WebContents
* initiator
) {
160 if (initiator
->ShowingInterstitialPage())
163 PrintPreviewDialogController
* dialog_controller
= GetInstance();
164 if (!dialog_controller
)
166 if (!dialog_controller
->GetOrCreatePreviewDialog(initiator
))
167 PrintViewManager::FromWebContents(initiator
)->PrintPreviewDone();
170 WebContents
* PrintPreviewDialogController::GetOrCreatePreviewDialog(
171 WebContents
* initiator
) {
174 // Get the print preview dialog for |initiator|.
175 WebContents
* preview_dialog
= GetPrintPreviewForContents(initiator
);
177 return CreatePrintPreviewDialog(initiator
);
179 // Show the initiator holding the existing preview dialog.
180 initiator
->GetDelegate()->ActivateContents(initiator
);
181 return preview_dialog
;
184 WebContents
* PrintPreviewDialogController::GetPrintPreviewForContents(
185 WebContents
* contents
) const {
186 // If this WebContents relies on another for its preview dialog, we
187 // need to act as if we are looking for the proxied content's dialog.
188 PrintPreviewDialogMap::const_iterator proxied
=
189 proxied_dialog_map_
.find(contents
);
190 if (proxied
!= proxied_dialog_map_
.end()) {
191 contents
= proxied
->second
;
194 // |preview_dialog_map_| is keyed by the preview dialog, so if find()
195 // succeeds, then |contents| is the preview dialog.
196 PrintPreviewDialogMap::const_iterator it
= preview_dialog_map_
.find(contents
);
197 if (it
!= preview_dialog_map_
.end())
200 for (it
= preview_dialog_map_
.begin();
201 it
!= preview_dialog_map_
.end();
203 // If |contents| is an initiator.
204 if (contents
== it
->second
) {
205 // Return the associated preview dialog.
212 WebContents
* PrintPreviewDialogController::GetInitiator(
213 WebContents
* preview_dialog
) {
214 PrintPreviewDialogMap::iterator it
= preview_dialog_map_
.find(preview_dialog
);
215 return (it
!= preview_dialog_map_
.end()) ? it
->second
: nullptr;
218 void PrintPreviewDialogController::Observe(
220 const content::NotificationSource
& source
,
221 const content::NotificationDetails
& details
) {
222 if (type
== content::NOTIFICATION_RENDERER_PROCESS_CLOSED
) {
223 OnRendererProcessClosed(
224 content::Source
<content::RenderProcessHost
>(source
).ptr());
225 } else if (type
== content::NOTIFICATION_WEB_CONTENTS_DESTROYED
) {
226 OnWebContentsDestroyed(content::Source
<WebContents
>(source
).ptr());
228 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED
, type
);
229 WebContents
* contents
=
230 content::Source
<NavigationController
>(source
)->GetWebContents();
233 content::Details
<content::LoadCommittedDetails
>(details
).ptr());
237 void PrintPreviewDialogController::ForEachPreviewDialog(
238 base::Callback
<void(content::WebContents
*)> callback
) {
239 for (PrintPreviewDialogMap::const_iterator it
= preview_dialog_map_
.begin();
240 it
!= preview_dialog_map_
.end();
242 callback
.Run(it
->first
);
247 bool PrintPreviewDialogController::IsPrintPreviewDialog(WebContents
* contents
) {
248 return IsPrintPreviewURL(contents
->GetURL());
252 bool PrintPreviewDialogController::IsPrintPreviewURL(const GURL
& url
) {
253 return (url
.SchemeIs(content::kChromeUIScheme
) &&
254 url
.host() == chrome::kChromeUIPrintHost
);
257 void PrintPreviewDialogController::EraseInitiatorInfo(
258 WebContents
* preview_dialog
) {
259 PrintPreviewDialogMap::iterator it
= preview_dialog_map_
.find(preview_dialog
);
260 if (it
== preview_dialog_map_
.end())
263 RemoveObservers(it
->second
);
264 preview_dialog_map_
[preview_dialog
] = nullptr;
267 PrintPreviewDialogController::~PrintPreviewDialogController() {}
269 void PrintPreviewDialogController::OnRendererProcessClosed(
270 content::RenderProcessHost
* rph
) {
271 // Store contents in a vector and deal with them after iterating through
272 // |preview_dialog_map_| because RemoveFoo() can change |preview_dialog_map_|.
273 std::vector
<WebContents
*> closed_initiators
;
274 std::vector
<WebContents
*> closed_preview_dialogs
;
275 for (PrintPreviewDialogMap::iterator iter
= preview_dialog_map_
.begin();
276 iter
!= preview_dialog_map_
.end(); ++iter
) {
277 WebContents
* preview_dialog
= iter
->first
;
278 WebContents
* initiator
= iter
->second
;
279 if (preview_dialog
->GetRenderProcessHost() == rph
) {
280 closed_preview_dialogs
.push_back(preview_dialog
);
281 } else if (initiator
&&
282 initiator
->GetRenderProcessHost() == rph
) {
283 closed_initiators
.push_back(initiator
);
287 for (size_t i
= 0; i
< closed_preview_dialogs
.size(); ++i
) {
288 RemovePreviewDialog(closed_preview_dialogs
[i
]);
289 if (content::WebUI
* web_ui
= closed_preview_dialogs
[i
]->GetWebUI()) {
290 PrintPreviewUI
* print_preview_ui
=
291 static_cast<PrintPreviewUI
*>(web_ui
->GetController());
292 if (print_preview_ui
)
293 print_preview_ui
->OnPrintPreviewDialogClosed();
297 for (size_t i
= 0; i
< closed_initiators
.size(); ++i
)
298 RemoveInitiator(closed_initiators
[i
]);
301 void PrintPreviewDialogController::OnWebContentsDestroyed(
302 WebContents
* contents
) {
303 WebContents
* preview_dialog
= GetPrintPreviewForContents(contents
);
304 if (!preview_dialog
) {
309 if (contents
== preview_dialog
)
310 RemovePreviewDialog(contents
);
312 RemoveInitiator(contents
);
315 void PrintPreviewDialogController::OnNavEntryCommitted(
316 WebContents
* contents
, content::LoadCommittedDetails
* details
) {
317 WebContents
* preview_dialog
= GetPrintPreviewForContents(contents
);
318 if (!preview_dialog
) {
323 if (contents
== preview_dialog
) {
324 // Preview dialog navigated.
326 ui::PageTransition transition_type
=
327 details
->entry
->GetTransitionType();
328 content::NavigationType nav_type
= details
->type
;
330 // New |preview_dialog| is created. Don't update/erase map entry.
331 if (waiting_for_new_preview_page_
&&
332 transition_type
== ui::PAGE_TRANSITION_AUTO_TOPLEVEL
&&
333 nav_type
== content::NAVIGATION_TYPE_NEW_PAGE
) {
334 waiting_for_new_preview_page_
= false;
335 SaveInitiatorTitle(preview_dialog
);
339 // Cloud print sign-in causes a reload.
340 if (!waiting_for_new_preview_page_
&&
341 transition_type
== ui::PAGE_TRANSITION_RELOAD
&&
342 nav_type
== content::NAVIGATION_TYPE_EXISTING_PAGE
&&
343 IsPrintPreviewURL(details
->previous_url
)) {
351 RemoveInitiator(contents
);
354 WebContents
* PrintPreviewDialogController::CreatePrintPreviewDialog(
355 WebContents
* initiator
) {
356 base::AutoReset
<bool> auto_reset(&is_creating_print_preview_dialog_
, true);
358 // The dialog delegates are deleted when the dialog is closed.
359 ConstrainedWebDialogDelegate
* web_dialog_delegate
=
360 ShowConstrainedWebDialog(initiator
->GetBrowserContext(),
361 new PrintPreviewDialogDelegate(initiator
),
364 WebContents
* preview_dialog
= web_dialog_delegate
->GetWebContents();
366 // Clear the zoom level for the print preview dialog so it isn't affected by
367 // the default zoom level. This also controls the zoom level of the OOP PDF
368 // extension when iframed by the print preview dialog.
369 GURL
print_url(chrome::kChromeUIPrintURL
);
370 content::HostZoomMap::Get(preview_dialog
->GetSiteInstance())
371 ->SetZoomLevelForHostAndScheme(print_url
.scheme(), print_url
.host(), 0);
372 PrintViewManager::CreateForWebContents(preview_dialog
);
373 extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
376 // Add an entry to the map.
377 preview_dialog_map_
[preview_dialog
] = initiator
;
378 waiting_for_new_preview_page_
= true;
380 // Make the print preview WebContents show up in the task manager.
381 task_management::WebContentsTags::CreateForPrintingContents(preview_dialog
);
383 AddObservers(initiator
);
384 AddObservers(preview_dialog
);
386 return preview_dialog
;
389 void PrintPreviewDialogController::AddProxyDialogForWebContents(
391 WebContents
* target
) {
392 proxied_dialog_map_
[source
] = target
;
395 void PrintPreviewDialogController::RemoveProxyDialogForWebContents(
396 WebContents
* source
) {
397 proxied_dialog_map_
.erase(source
);
400 void PrintPreviewDialogController::SaveInitiatorTitle(
401 WebContents
* preview_dialog
) {
402 WebContents
* initiator
= GetInitiator(preview_dialog
);
403 if (initiator
&& preview_dialog
->GetWebUI()) {
404 PrintPreviewUI
* print_preview_ui
= static_cast<PrintPreviewUI
*>(
405 preview_dialog
->GetWebUI()->GetController());
406 print_preview_ui
->SetInitiatorTitle(
407 PrintViewManager::FromWebContents(initiator
)->RenderSourceName());
411 void PrintPreviewDialogController::AddObservers(WebContents
* contents
) {
412 registrar_
.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
413 content::Source
<WebContents
>(contents
));
414 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
415 content::Source
<NavigationController
>(&contents
->GetController()));
417 // Multiple sites may share the same RenderProcessHost, so check if this
418 // notification has already been added.
419 content::Source
<content::RenderProcessHost
> rph_source(
420 contents
->GetRenderProcessHost());
421 if (!registrar_
.IsRegistered(this,
422 content::NOTIFICATION_RENDERER_PROCESS_CLOSED
, rph_source
)) {
423 registrar_
.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED
,
428 void PrintPreviewDialogController::RemoveObservers(WebContents
* contents
) {
429 registrar_
.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
430 content::Source
<WebContents
>(contents
));
431 registrar_
.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
432 content::Source
<NavigationController
>(&contents
->GetController()));
434 // Multiple sites may share the same RenderProcessHost, so check if this
435 // notification has already been added.
436 content::Source
<content::RenderProcessHost
> rph_source(
437 contents
->GetRenderProcessHost());
438 if (registrar_
.IsRegistered(this,
439 content::NOTIFICATION_RENDERER_PROCESS_CLOSED
, rph_source
)) {
440 registrar_
.Remove(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSED
,
445 void PrintPreviewDialogController::RemoveInitiator(
446 WebContents
* initiator
) {
447 WebContents
* preview_dialog
= GetPrintPreviewForContents(initiator
);
448 DCHECK(preview_dialog
);
449 // Update the map entry first, so when the print preview dialog gets destroyed
450 // and reaches RemovePreviewDialog(), it does not attempt to also remove the
451 // initiator's observers.
452 preview_dialog_map_
[preview_dialog
] = nullptr;
453 RemoveObservers(initiator
);
455 PrintViewManager::FromWebContents(initiator
)->PrintPreviewDone();
457 // initiator is closed. Close the print preview dialog too.
458 if (content::WebUI
* web_ui
= preview_dialog
->GetWebUI()) {
459 PrintPreviewUI
* print_preview_ui
=
460 static_cast<PrintPreviewUI
*>(web_ui
->GetController());
461 if (print_preview_ui
)
462 print_preview_ui
->OnInitiatorClosed();
466 void PrintPreviewDialogController::RemovePreviewDialog(
467 WebContents
* preview_dialog
) {
468 // Remove the initiator's observers before erasing the mapping.
469 WebContents
* initiator
= GetInitiator(preview_dialog
);
471 RemoveObservers(initiator
);
472 PrintViewManager::FromWebContents(initiator
)->PrintPreviewDone();
475 // Print preview WebContents is destroyed. Notify |PrintPreviewUI| to abort
476 // the initiator preview request.
477 if (content::WebUI
* web_ui
= preview_dialog
->GetWebUI()) {
478 PrintPreviewUI
* print_preview_ui
=
479 static_cast<PrintPreviewUI
*>(web_ui
->GetController());
480 if (print_preview_ui
)
481 print_preview_ui
->OnPrintPreviewDialogDestroyed();
484 preview_dialog_map_
.erase(preview_dialog
);
485 RemoveObservers(preview_dialog
);
488 } // namespace printing