1 // Copyright 2015 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/ui/webui/print_preview/print_preview_distiller.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/chrome_notification_types.h"
11 #include "chrome/browser/dom_distiller/tab_utils.h"
12 #include "chrome/browser/printing/print_preview_dialog_controller.h"
13 #include "chrome/browser/printing/print_preview_message_handler.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/web_contents_sizer.h"
16 #include "chrome/common/prerender_messages.h"
17 #include "components/dom_distiller/content/browser/distiller_javascript_utils.h"
18 #include "components/printing/common/print_messages.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/render_view_host.h"
23 #include "content/public/browser/session_storage_namespace.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/browser/web_contents_delegate.h"
26 #include "content/public/browser/web_contents_observer.h"
28 using content::OpenURLParams
;
29 using content::RenderViewHost
;
30 using content::SessionStorageNamespace
;
31 using content::WebContents
;
33 class PrintPreviewDistiller::WebContentsDelegateImpl
34 : public content::WebContentsDelegate
,
35 public content::NotificationObserver
,
36 public content::WebContentsObserver
{
38 explicit WebContentsDelegateImpl(WebContents
* web_contents
,
39 scoped_ptr
<base::DictionaryValue
> settings
,
40 const base::Closure on_failed_callback
)
41 : content::WebContentsObserver(web_contents
),
42 settings_(settings
.Pass()),
43 on_failed_callback_(on_failed_callback
) {
44 web_contents
->SetDelegate(this);
46 // Close ourselves when the application is shutting down.
47 notification_registrar_
.Add(this, chrome::NOTIFICATION_APP_TERMINATING
,
48 content::NotificationService::AllSources());
50 // Register to inform new RenderViews that we're rendering.
51 notification_registrar_
.Add(
52 this, content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED
,
53 content::Source
<WebContents
>(web_contents
));
56 ~WebContentsDelegateImpl() override
{ web_contents()->SetDelegate(nullptr); }
58 // content::WebContentsDelegate implementation.
59 WebContents
* OpenURLFromTab(WebContents
* source
,
60 const OpenURLParams
& params
) override
{
61 on_failed_callback_
.Run();
65 void CloseContents(content::WebContents
* contents
) override
{
66 on_failed_callback_
.Run();
69 void CanDownload(const GURL
& url
,
70 const std::string
& request_method
,
71 const base::Callback
<void(bool)>& callback
) override
{
72 on_failed_callback_
.Run();
73 // Cancel the download.
77 bool ShouldCreateWebContents(
78 WebContents
* web_contents
,
80 int main_frame_route_id
,
81 WindowContainerType window_container_type
,
82 const std::string
& frame_name
,
83 const GURL
& target_url
,
84 const std::string
& partition_id
,
85 SessionStorageNamespace
* session_storage_namespace
) override
{
86 // Since we don't want to permit child windows that would have a
87 // window.opener property, terminate rendering.
88 on_failed_callback_
.Run();
93 bool OnGoToEntryOffset(int offset
) override
{
94 // This isn't allowed because the history merge operation
95 // does not work if there are renderer issued challenges.
96 // TODO(cbentzel): Cancel in this case? May not need to do
97 // since render-issued offset navigations are not guaranteed,
98 // but indicates that the page cares about the history.
102 bool ShouldSuppressDialogs(WebContents
* source
) override
{
103 // We still want to show the user the message when they navigate to this
104 // page, so cancel this render.
105 on_failed_callback_
.Run();
106 // Always suppress JavaScript messages if they're triggered by a page being
111 void RegisterProtocolHandler(WebContents
* web_contents
,
112 const std::string
& protocol
,
114 bool user_gesture
) override
{
115 on_failed_callback_
.Run();
118 void RenderFrameCreated(
119 content::RenderFrameHost
* render_frame_host
) override
{
120 // When a new RenderFrame is created for a distilled rendering
121 // WebContents, tell the new RenderFrame it's being used for
122 // prerendering before any navigations occur. Note that this is
123 // always triggered before the first navigation, so there's no
124 // need to send the message just after the WebContents is created.
125 render_frame_host
->Send(new PrerenderMsg_SetIsPrerendering(
126 render_frame_host
->GetRoutingID(), true));
129 void DidFinishLoad(content::RenderFrameHost
* render_frame_host
,
130 const GURL
& validated_url
) override
{
131 // Ask the page to trigger an anchor navigation once the distilled
132 // contents are added to the page.
133 dom_distiller::RunIsolatedJavaScript(
134 web_contents()->GetMainFrame(),
135 "navigate_on_initial_content_load = true;");
138 void DidNavigateMainFrame(
139 const content::LoadCommittedDetails
& details
,
140 const content::FrameNavigateParams
& params
) override
{
141 // The second content loads signals that the distilled contents have
142 // been delivered to the page via inline JavaScript execution.
143 if (web_contents()->GetController().GetEntryCount() > 1) {
144 RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
145 rvh
->Send(new PrintMsg_InitiatePrintPreview(rvh
->GetRoutingID(), false));
146 rvh
->Send(new PrintMsg_PrintPreview(rvh
->GetRoutingID(), *settings_
));
150 void DidGetRedirectForResourceRequest(
151 content::RenderFrameHost
* render_frame_host
,
152 const content::ResourceRedirectDetails
& details
) override
{
153 // Redirects are unsupported for distilled content renderers.
154 on_failed_callback_
.Run();
157 void RenderProcessGone(base::TerminationStatus status
) override
{
158 on_failed_callback_
.Run();
161 void Observe(int type
,
162 const content::NotificationSource
& source
,
163 const content::NotificationDetails
& details
) override
{
165 // TODO(davidben): Try to remove this in favor of relying on
166 // FINAL_STATUS_PROFILE_DESTROYED.
167 case chrome::NOTIFICATION_APP_TERMINATING
:
168 on_failed_callback_
.Run();
171 case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED
: {
172 if (web_contents()) {
173 DCHECK_EQ(content::Source
<WebContents
>(source
).ptr(), web_contents());
175 // Make sure the size of the RenderViewHost has been passed to the new
176 // RenderView. Otherwise, the size may not be sent until the
177 // RenderViewReady event makes it from the render process to the UI
178 // thread of the browser process. When the RenderView receives its
179 // size, is also sets itself to be visible, which would then break the
181 content::Details
<RenderViewHost
> new_render_view_host(details
);
182 new_render_view_host
->WasResized();
183 web_contents()->WasHidden();
189 NOTREACHED() << "Unexpected notification sent.";
195 scoped_ptr
<base::DictionaryValue
> settings_
;
196 content::NotificationRegistrar notification_registrar_
;
198 // The callback called when the preview failed.
199 base::Closure on_failed_callback_
;
202 PrintPreviewDistiller::PrintPreviewDistiller(
203 WebContents
* source_web_contents
,
204 const base::Closure on_failed_callback
,
205 scoped_ptr
<base::DictionaryValue
> settings
) {
206 content::SessionStorageNamespace
* session_storage_namespace
=
207 source_web_contents
->GetController().GetDefaultSessionStorageNamespace();
208 CreateDestinationWebContents(session_storage_namespace
, source_web_contents
,
209 settings
.Pass(), on_failed_callback
);
211 DCHECK(web_contents_
);
212 ::DistillAndView(source_web_contents
, web_contents_
.get());
215 void PrintPreviewDistiller::CreateDestinationWebContents(
216 SessionStorageNamespace
* session_storage_namespace
,
217 WebContents
* source_web_contents
,
218 scoped_ptr
<base::DictionaryValue
> settings
,
219 const base::Closure on_failed_callback
) {
220 DCHECK(!web_contents_
);
223 CreateWebContents(session_storage_namespace
, source_web_contents
));
225 printing::PrintPreviewMessageHandler::CreateForWebContents(
226 web_contents_
.get());
228 web_contents_delegate_
.reset(new WebContentsDelegateImpl(
229 web_contents_
.get(), settings
.Pass(), on_failed_callback
));
231 // Set the size of the distilled WebContents.
232 ResizeWebContents(web_contents_
.get(), gfx::Size(1, 1));
234 printing::PrintPreviewDialogController
* dialog_controller
=
235 printing::PrintPreviewDialogController::GetInstance();
236 if (!dialog_controller
)
239 dialog_controller
->AddProxyDialogForWebContents(web_contents_
.get(),
240 source_web_contents
);
243 PrintPreviewDistiller::~PrintPreviewDistiller() {
245 printing::PrintPreviewDialogController
* dialog_controller
=
246 printing::PrintPreviewDialogController::GetInstance();
247 if (!dialog_controller
)
250 dialog_controller
->RemoveProxyDialogForWebContents(web_contents_
.get());
254 WebContents
* PrintPreviewDistiller::CreateWebContents(
255 SessionStorageNamespace
* session_storage_namespace
,
256 WebContents
* source_web_contents
) {
257 // TODO(ajwong): Remove the temporary map once prerendering is aware of
258 // multiple session storage namespaces per tab.
259 content::SessionStorageNamespaceMap session_storage_namespace_map
;
261 Profile::FromBrowserContext(source_web_contents
->GetBrowserContext());
262 session_storage_namespace_map
[std::string()] = session_storage_namespace
;
263 return WebContents::CreateWithSessionStorage(
264 WebContents::CreateParams(profile
), session_storage_namespace_map
);