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/task_tracker.h"
16 #include "components/dom_distiller/core/url_constants.h"
17 #include "components/dom_distiller/core/viewer.h"
18 #include "content/public/browser/navigation_details.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_observer.h"
24 #include "net/base/url_util.h"
25 #include "net/url_request/url_request.h"
27 namespace dom_distiller
{
29 // Handles receiving data asynchronously for a specific entry, and passing
30 // it along to the data callback for the data source. Lifetime matches that of
31 // the current main frame's page in the Viewer instance.
32 class DomDistillerViewerSource::RequestViewerHandle
33 : public ViewRequestDelegate
,
34 public content::WebContentsObserver
{
36 explicit RequestViewerHandle(
37 content::WebContents
* web_contents
,
38 const std::string
& expected_scheme
,
39 const std::string
& expected_request_path
,
40 const content::URLDataSource::GotDataCallback
& callback
);
41 virtual ~RequestViewerHandle();
43 // ViewRequestDelegate implementation.
44 virtual void OnArticleReady(
45 const DistilledArticleProto
* article_proto
) OVERRIDE
;
47 virtual void OnArticleUpdated(
48 ArticleDistillationUpdate article_update
) OVERRIDE
;
50 void TakeViewerHandle(scoped_ptr
<ViewerHandle
> viewer_handle
);
52 // WebContentsObserver:
53 virtual void DidNavigateMainFrame(
54 const content::LoadCommittedDetails
& details
,
55 const content::FrameNavigateParams
& params
) OVERRIDE
;
56 virtual void RenderProcessGone(base::TerminationStatus status
) OVERRIDE
;
57 virtual void WebContentsDestroyed() OVERRIDE
;
58 virtual void DidFinishLoad(content::RenderFrameHost
* render_frame_host
,
59 const GURL
& validated_url
) OVERRIDE
;
62 // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't
64 void SendJavaScript(const std::string
& buffer
);
66 // Cancels the current view request. Once called, no updates will be
67 // propagated to the view, and the request to DomDistillerService will be
71 // The handle to the view request towards the DomDistillerService. It
72 // needs to be kept around to ensure the distillation request finishes.
73 scoped_ptr
<ViewerHandle
> viewer_handle_
;
75 // The scheme hosting the current view request;
76 std::string expected_scheme_
;
78 // The query path for the current view request.
79 std::string expected_request_path_
;
81 // Holds the callback to where the data retrieved is sent back.
82 content::URLDataSource::GotDataCallback callback_
;
84 // Number of pages of the distilled article content that have been rendered by
88 // Whether the page is sufficiently initialized to handle updates from the
90 bool waiting_for_page_ready_
;
92 // Temporary store of pending JavaScript if the page isn't ready to receive
93 // data from distillation.
97 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
98 content::WebContents
* web_contents
,
99 const std::string
& expected_scheme
,
100 const std::string
& expected_request_path
,
101 const content::URLDataSource::GotDataCallback
& callback
)
102 : expected_scheme_(expected_scheme
),
103 expected_request_path_(expected_request_path
),
106 waiting_for_page_ready_(true) {
107 content::WebContentsObserver::Observe(web_contents
);
110 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
113 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
114 const std::string
& buffer
) {
115 if (waiting_for_page_ready_
) {
118 if (web_contents()) {
119 web_contents()->GetMainFrame()->ExecuteJavaScript(
120 base::UTF8ToUTF16(buffer
));
125 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
126 const content::LoadCommittedDetails
& details
,
127 const content::FrameNavigateParams
& params
) {
128 const GURL
& navigation
= details
.entry
->GetURL();
129 if (details
.is_in_page
|| (
130 navigation
.SchemeIs(expected_scheme_
.c_str()) &&
131 expected_request_path_
== navigation
.query())) {
132 // In-page navigations, as well as the main view request can be ignored.
140 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
141 base::TerminationStatus status
) {
145 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
149 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
150 // No need to listen for notifications.
151 content::WebContentsObserver::Observe(NULL
);
153 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
154 // any pending data stored in |buffer_| is released.
155 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
158 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
159 content::RenderFrameHost
* render_frame_host
,
160 const GURL
& validated_url
) {
161 if (render_frame_host
->GetParent()) {
164 waiting_for_page_ready_
= false;
165 if (buffer_
.empty()) {
168 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_
));
172 void DomDistillerViewerSource::RequestViewerHandle::OnArticleReady(
173 const DistilledArticleProto
* article_proto
) {
174 if (page_count_
== 0) {
175 // This is a single-page article.
176 std::string unsafe_page_html
= viewer::GetUnsafeArticleHtml(article_proto
);
177 callback_
.Run(base::RefCountedString::TakeString(&unsafe_page_html
));
178 } else if (page_count_
== article_proto
->pages_size()) {
179 // We may still be showing the "Loading" indicator.
180 SendJavaScript(viewer::GetToggleLoadingIndicatorJs(true));
182 // It's possible that we didn't get some incremental updates from the
183 // distiller. Ensure all remaining pages are flushed to the viewer.
184 for (;page_count_
< article_proto
->pages_size(); page_count_
++) {
185 const DistilledPageProto
& page
= article_proto
->pages(page_count_
);
187 viewer::GetUnsafeIncrementalDistilledPageJs(
189 page_count_
== article_proto
->pages_size()));
192 // No need to hold on to the ViewerHandle now that distillation is complete.
193 viewer_handle_
.reset();
196 void DomDistillerViewerSource::RequestViewerHandle::OnArticleUpdated(
197 ArticleDistillationUpdate article_update
) {
198 for (;page_count_
< static_cast<int>(article_update
.GetPagesSize());
200 const DistilledPageProto
& page
=
201 article_update
.GetDistilledPage(page_count_
);
202 if (page_count_
== 0) {
203 // This is the first page, so send Viewer page scaffolding too.
204 std::string unsafe_page_html
= viewer::GetUnsafePartialArticleHtml(&page
);
205 callback_
.Run(base::RefCountedString::TakeString(&unsafe_page_html
));
208 viewer::GetUnsafeIncrementalDistilledPageJs(&page
, false));
213 void DomDistillerViewerSource::RequestViewerHandle::TakeViewerHandle(
214 scoped_ptr
<ViewerHandle
> viewer_handle
) {
215 viewer_handle_
= viewer_handle
.Pass();
218 DomDistillerViewerSource::DomDistillerViewerSource(
219 DomDistillerServiceInterface
* dom_distiller_service
,
220 const std::string
& scheme
)
221 : scheme_(scheme
), dom_distiller_service_(dom_distiller_service
) {
224 DomDistillerViewerSource::~DomDistillerViewerSource() {
227 std::string
DomDistillerViewerSource::GetSource() const {
228 return scheme_
+ "://";
231 void DomDistillerViewerSource::StartDataRequest(
232 const std::string
& path
,
233 int render_process_id
,
235 const content::URLDataSource::GotDataCallback
& callback
) {
236 content::RenderFrameHost
* render_frame_host
=
237 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
238 DCHECK(render_frame_host
);
239 content::RenderViewHost
* render_view_host
=
240 render_frame_host
->GetRenderViewHost();
241 DCHECK(render_view_host
);
242 CHECK_EQ(0, render_view_host
->GetEnabledBindings());
244 if (kViewerCssPath
== path
) {
245 std::string css
= viewer::GetCss();
246 callback
.Run(base::RefCountedString::TakeString(&css
));
249 if (kViewerJsPath
== path
) {
250 std::string js
= viewer::GetJavaScript();
251 callback
.Run(base::RefCountedString::TakeString(&js
));
254 content::WebContents
* web_contents
=
255 content::WebContents::FromRenderFrameHost(
256 content::RenderFrameHost::FromID(render_process_id
,
258 DCHECK(web_contents
);
259 // An empty |path| is invalid, but guard against it. If not empty, assume
260 // |path| starts with '?', which is stripped away.
261 const std::string path_after_query_separator
=
262 path
.size() > 0 ? path
.substr(1) : "";
263 RequestViewerHandle
* request_viewer_handle
= new RequestViewerHandle(
264 web_contents
, scheme_
, path_after_query_separator
, callback
);
265 scoped_ptr
<ViewerHandle
> viewer_handle
= viewer::CreateViewRequest(
266 dom_distiller_service_
, path
, request_viewer_handle
);
269 // The service returned a |ViewerHandle| and guarantees it will call
270 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
271 // request is not cancelled. The |RequestViewerHandle| will delete itself
272 // after receiving the callback.
273 request_viewer_handle
->TakeViewerHandle(viewer_handle
.Pass());
275 // The service did not return a |ViewerHandle|, which means the
276 // |RequestViewerHandle| will never be called, so clean up now.
277 delete request_viewer_handle
;
279 std::string error_page_html
= viewer::GetErrorPageHtml();
280 callback
.Run(base::RefCountedString::TakeString(&error_page_html
));
284 std::string
DomDistillerViewerSource::GetMimeType(
285 const std::string
& path
) const {
286 if (kViewerCssPath
== path
) {
289 if (kViewerJsPath
== path
) {
290 return "text/javascript";
295 bool DomDistillerViewerSource::ShouldServiceRequest(
296 const net::URLRequest
* request
) const {
297 return request
->url().SchemeIs(scheme_
.c_str());
300 // TODO(nyquist): Start tracking requests using this method.
301 void DomDistillerViewerSource::WillServiceRequest(
302 const net::URLRequest
* request
,
303 std::string
* path
) const {
306 std::string
DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
308 return "object-src 'none'; style-src 'self';";
311 } // namespace dom_distiller