1 // Copyright 2014 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 "components/dom_distiller/content/dom_distiller_viewer_source.h"
11 #include "base/memory/ref_counted_memory.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "components/dom_distiller/core/distilled_page_prefs.h"
16 #include "components/dom_distiller/core/dom_distiller_service.h"
17 #include "components/dom_distiller/core/task_tracker.h"
18 #include "components/dom_distiller/core/url_constants.h"
19 #include "components/dom_distiller/core/viewer.h"
20 #include "content/public/browser/navigation_details.h"
21 #include "content/public/browser/navigation_entry.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/browser/web_contents_observer.h"
26 #include "net/base/url_util.h"
27 #include "net/url_request/url_request.h"
29 namespace dom_distiller
{
31 // Handles receiving data asynchronously for a specific entry, and passing
32 // it along to the data callback for the data source. Lifetime matches that of
33 // the current main frame's page in the Viewer instance.
34 class DomDistillerViewerSource::RequestViewerHandle
35 : public ViewRequestDelegate
,
36 public content::WebContentsObserver
,
37 public DistilledPagePrefs::Observer
{
39 explicit RequestViewerHandle(
40 content::WebContents
* web_contents
,
41 const std::string
& expected_scheme
,
42 const std::string
& expected_request_path
,
43 const content::URLDataSource::GotDataCallback
& callback
,
44 DistilledPagePrefs
* distilled_page_prefs
);
45 virtual ~RequestViewerHandle();
47 // ViewRequestDelegate implementation:
48 virtual void OnArticleReady(
49 const DistilledArticleProto
* article_proto
) OVERRIDE
;
51 virtual void OnArticleUpdated(
52 ArticleDistillationUpdate article_update
) OVERRIDE
;
54 void TakeViewerHandle(scoped_ptr
<ViewerHandle
> viewer_handle
);
56 // content::WebContentsObserver implementation:
57 virtual void DidNavigateMainFrame(
58 const content::LoadCommittedDetails
& details
,
59 const content::FrameNavigateParams
& params
) OVERRIDE
;
60 virtual void RenderProcessGone(base::TerminationStatus status
) OVERRIDE
;
61 virtual void WebContentsDestroyed() OVERRIDE
;
62 virtual void DidFinishLoad(content::RenderFrameHost
* render_frame_host
,
63 const GURL
& validated_url
) OVERRIDE
;
66 // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't
68 void SendJavaScript(const std::string
& buffer
);
70 // Cancels the current view request. Once called, no updates will be
71 // propagated to the view, and the request to DomDistillerService will be
75 // DistilledPagePrefs::Observer implementation:
76 virtual void OnChangeFontFamily(
77 DistilledPagePrefs::FontFamily new_font_family
) OVERRIDE
;
78 virtual void OnChangeTheme(DistilledPagePrefs::Theme new_theme
) OVERRIDE
;
80 // The handle to the view request towards the DomDistillerService. It
81 // needs to be kept around to ensure the distillation request finishes.
82 scoped_ptr
<ViewerHandle
> viewer_handle_
;
84 // The scheme hosting the current view request;
85 std::string expected_scheme_
;
87 // The query path for the current view request.
88 std::string expected_request_path_
;
90 // Holds the callback to where the data retrieved is sent back.
91 content::URLDataSource::GotDataCallback callback_
;
93 // Number of pages of the distilled article content that have been rendered by
97 // Interface for accessing preferences for distilled pages.
98 DistilledPagePrefs
* distilled_page_prefs_
;
100 // Whether the page is sufficiently initialized to handle updates from the
102 bool waiting_for_page_ready_
;
104 // Temporary store of pending JavaScript if the page isn't ready to receive
105 // data from distillation.
109 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
110 content::WebContents
* web_contents
,
111 const std::string
& expected_scheme
,
112 const std::string
& expected_request_path
,
113 const content::URLDataSource::GotDataCallback
& callback
,
114 DistilledPagePrefs
* distilled_page_prefs
)
115 : expected_scheme_(expected_scheme
),
116 expected_request_path_(expected_request_path
),
119 distilled_page_prefs_(distilled_page_prefs
),
120 waiting_for_page_ready_(true) {
121 content::WebContentsObserver::Observe(web_contents
);
122 distilled_page_prefs_
->AddObserver(this);
125 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
126 distilled_page_prefs_
->RemoveObserver(this);
129 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
130 const std::string
& buffer
) {
131 if (waiting_for_page_ready_
) {
134 if (web_contents()) {
135 web_contents()->GetMainFrame()->ExecuteJavaScript(
136 base::UTF8ToUTF16(buffer
));
141 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
142 const content::LoadCommittedDetails
& details
,
143 const content::FrameNavigateParams
& params
) {
144 const GURL
& navigation
= details
.entry
->GetURL();
145 if (details
.is_in_page
|| (
146 navigation
.SchemeIs(expected_scheme_
.c_str()) &&
147 expected_request_path_
== navigation
.query())) {
148 // In-page navigations, as well as the main view request can be ignored.
156 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
157 base::TerminationStatus status
) {
161 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
165 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
166 // No need to listen for notifications.
167 content::WebContentsObserver::Observe(NULL
);
169 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
170 // any pending data stored in |buffer_| is released.
171 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
174 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
175 content::RenderFrameHost
* render_frame_host
,
176 const GURL
& validated_url
) {
177 if (render_frame_host
->GetParent()) {
180 waiting_for_page_ready_
= false;
181 if (buffer_
.empty()) {
184 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_
));
188 void DomDistillerViewerSource::RequestViewerHandle::OnArticleReady(
189 const DistilledArticleProto
* article_proto
) {
190 if (page_count_
== 0) {
191 // This is a single-page article.
192 std::string unsafe_page_html
=
193 viewer::GetUnsafeArticleHtml(
195 distilled_page_prefs_
->GetTheme(),
196 distilled_page_prefs_
->GetFontFamily());
197 callback_
.Run(base::RefCountedString::TakeString(&unsafe_page_html
));
198 } else if (page_count_
== article_proto
->pages_size()) {
199 // We may still be showing the "Loading" indicator.
200 SendJavaScript(viewer::GetToggleLoadingIndicatorJs(true));
202 // It's possible that we didn't get some incremental updates from the
203 // distiller. Ensure all remaining pages are flushed to the viewer.
204 for (;page_count_
< article_proto
->pages_size(); page_count_
++) {
205 const DistilledPageProto
& page
= article_proto
->pages(page_count_
);
207 viewer::GetUnsafeIncrementalDistilledPageJs(
209 page_count_
== article_proto
->pages_size()));
212 // No need to hold on to the ViewerHandle now that distillation is complete.
213 viewer_handle_
.reset();
216 void DomDistillerViewerSource::RequestViewerHandle::OnArticleUpdated(
217 ArticleDistillationUpdate article_update
) {
218 for (;page_count_
< static_cast<int>(article_update
.GetPagesSize());
220 const DistilledPageProto
& page
=
221 article_update
.GetDistilledPage(page_count_
);
222 if (page_count_
== 0) {
223 // This is the first page, so send Viewer page scaffolding too.
224 std::string unsafe_page_html
= viewer::GetUnsafePartialArticleHtml(
226 distilled_page_prefs_
->GetTheme(),
227 distilled_page_prefs_
->GetFontFamily());
228 callback_
.Run(base::RefCountedString::TakeString(&unsafe_page_html
));
231 viewer::GetUnsafeIncrementalDistilledPageJs(&page
, false));
236 void DomDistillerViewerSource::RequestViewerHandle::TakeViewerHandle(
237 scoped_ptr
<ViewerHandle
> viewer_handle
) {
238 viewer_handle_
= viewer_handle
.Pass();
241 void DomDistillerViewerSource::RequestViewerHandle::OnChangeTheme(
242 DistilledPagePrefs::Theme new_theme
) {
243 SendJavaScript(viewer::GetDistilledPageThemeJs(new_theme
));
246 void DomDistillerViewerSource::RequestViewerHandle::OnChangeFontFamily(
247 DistilledPagePrefs::FontFamily new_font
) {
248 SendJavaScript(viewer::GetDistilledPageFontFamilyJs(new_font
));
251 DomDistillerViewerSource::DomDistillerViewerSource(
252 DomDistillerServiceInterface
* dom_distiller_service
,
253 const std::string
& scheme
)
254 : scheme_(scheme
), dom_distiller_service_(dom_distiller_service
) {
257 DomDistillerViewerSource::~DomDistillerViewerSource() {
260 std::string
DomDistillerViewerSource::GetSource() const {
261 return scheme_
+ "://";
264 void DomDistillerViewerSource::StartDataRequest(
265 const std::string
& path
,
266 int render_process_id
,
268 const content::URLDataSource::GotDataCallback
& callback
) {
269 content::RenderFrameHost
* render_frame_host
=
270 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
271 DCHECK(render_frame_host
);
272 content::RenderViewHost
* render_view_host
=
273 render_frame_host
->GetRenderViewHost();
274 DCHECK(render_view_host
);
275 CHECK_EQ(0, render_view_host
->GetEnabledBindings());
277 if (kViewerCssPath
== path
) {
278 std::string css
= viewer::GetCss();
279 callback
.Run(base::RefCountedString::TakeString(&css
));
282 if (kViewerJsPath
== path
) {
283 std::string js
= viewer::GetJavaScript();
284 callback
.Run(base::RefCountedString::TakeString(&js
));
287 content::WebContents
* web_contents
=
288 content::WebContents::FromRenderFrameHost(
289 content::RenderFrameHost::FromID(render_process_id
,
291 DCHECK(web_contents
);
292 // An empty |path| is invalid, but guard against it. If not empty, assume
293 // |path| starts with '?', which is stripped away.
294 const std::string path_after_query_separator
=
295 path
.size() > 0 ? path
.substr(1) : "";
296 RequestViewerHandle
* request_viewer_handle
= new RequestViewerHandle(
297 web_contents
, scheme_
, path_after_query_separator
, callback
,
298 dom_distiller_service_
->GetDistilledPagePrefs());
299 scoped_ptr
<ViewerHandle
> viewer_handle
= viewer::CreateViewRequest(
300 dom_distiller_service_
, path
, request_viewer_handle
,
301 web_contents
->GetContainerBounds().size());
304 // The service returned a |ViewerHandle| and guarantees it will call
305 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
306 // request is not cancelled. The |RequestViewerHandle| will delete itself
307 // after receiving the callback.
308 request_viewer_handle
->TakeViewerHandle(viewer_handle
.Pass());
310 // The service did not return a |ViewerHandle|, which means the
311 // |RequestViewerHandle| will never be called, so clean up now.
312 delete request_viewer_handle
;
314 std::string error_page_html
= viewer::GetErrorPageHtml(
315 dom_distiller_service_
->GetDistilledPagePrefs()->GetTheme(),
316 dom_distiller_service_
->GetDistilledPagePrefs()->GetFontFamily());
317 callback
.Run(base::RefCountedString::TakeString(&error_page_html
));
321 std::string
DomDistillerViewerSource::GetMimeType(
322 const std::string
& path
) const {
323 if (kViewerCssPath
== path
) {
326 if (kViewerJsPath
== path
) {
327 return "text/javascript";
332 bool DomDistillerViewerSource::ShouldServiceRequest(
333 const net::URLRequest
* request
) const {
334 return request
->url().SchemeIs(scheme_
.c_str());
337 // TODO(nyquist): Start tracking requests using this method.
338 void DomDistillerViewerSource::WillServiceRequest(
339 const net::URLRequest
* request
,
340 std::string
* path
) const {
343 std::string
DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
345 return "object-src 'none'; style-src 'self' https://fonts.googleapis.com;";
348 } // namespace dom_distiller