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/metrics/user_metrics.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "components/dom_distiller/core/distilled_page_prefs.h"
17 #include "components/dom_distiller/core/dom_distiller_request_view_base.h"
18 #include "components/dom_distiller/core/dom_distiller_service.h"
19 #include "components/dom_distiller/core/external_feedback_reporter.h"
20 #include "components/dom_distiller/core/feedback_reporter.h"
21 #include "components/dom_distiller/core/task_tracker.h"
22 #include "components/dom_distiller/core/url_constants.h"
23 #include "components/dom_distiller/core/url_utils.h"
24 #include "components/dom_distiller/core/viewer.h"
25 #include "content/public/browser/navigation_details.h"
26 #include "content/public/browser/navigation_entry.h"
27 #include "content/public/browser/render_frame_host.h"
28 #include "content/public/browser/render_view_host.h"
29 #include "content/public/browser/user_metrics.h"
30 #include "content/public/browser/web_contents.h"
31 #include "content/public/browser/web_contents_observer.h"
32 #include "grit/components_strings.h"
33 #include "net/base/url_util.h"
34 #include "net/url_request/url_request.h"
35 #include "ui/base/l10n/l10n_util.h"
37 namespace dom_distiller
{
39 // Handles receiving data asynchronously for a specific entry, and passing
40 // it along to the data callback for the data source. Lifetime matches that of
41 // the current main frame's page in the Viewer instance.
42 class DomDistillerViewerSource::RequestViewerHandle
43 : public DomDistillerRequestViewBase
,
44 public content::WebContentsObserver
{
46 RequestViewerHandle(content::WebContents
* web_contents
,
47 const std::string
& expected_scheme
,
48 const std::string
& expected_request_path
,
49 DistilledPagePrefs
* distilled_page_prefs
);
50 ~RequestViewerHandle() override
;
52 // content::WebContentsObserver implementation:
53 void DidNavigateMainFrame(
54 const content::LoadCommittedDetails
& details
,
55 const content::FrameNavigateParams
& params
) override
;
56 void RenderProcessGone(base::TerminationStatus status
) override
;
57 void WebContentsDestroyed() override
;
58 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
) override
;
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 scheme hosting the current view request;
72 std::string expected_scheme_
;
74 // The query path for the current view request.
75 std::string expected_request_path_
;
77 // Whether the page is sufficiently initialized to handle updates from the
79 bool waiting_for_page_ready_
;
81 // Temporary store of pending JavaScript if the page isn't ready to receive
82 // data from distillation.
86 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
87 content::WebContents
* web_contents
,
88 const std::string
& expected_scheme
,
89 const std::string
& expected_request_path
,
90 DistilledPagePrefs
* distilled_page_prefs
)
91 : DomDistillerRequestViewBase(distilled_page_prefs
),
92 expected_scheme_(expected_scheme
),
93 expected_request_path_(expected_request_path
),
94 waiting_for_page_ready_(true) {
95 content::WebContentsObserver::Observe(web_contents
);
96 distilled_page_prefs_
->AddObserver(this);
99 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
100 distilled_page_prefs_
->RemoveObserver(this);
103 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
104 const std::string
& buffer
) {
105 if (waiting_for_page_ready_
) {
108 if (web_contents()) {
109 web_contents()->GetMainFrame()->ExecuteJavaScript(
110 base::UTF8ToUTF16(buffer
));
115 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
116 const content::LoadCommittedDetails
& details
,
117 const content::FrameNavigateParams
& params
) {
118 const GURL
& navigation
= details
.entry
->GetURL();
119 if (details
.is_in_page
|| (
120 navigation
.SchemeIs(expected_scheme_
.c_str()) &&
121 expected_request_path_
== navigation
.query())) {
122 // In-page navigations, as well as the main view request can be ignored.
129 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
130 base::TerminationStatus status
) {
134 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
138 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
139 // No need to listen for notifications.
140 content::WebContentsObserver::Observe(NULL
);
142 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
143 // any pending data stored in |buffer_| is released.
144 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
147 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
148 content::RenderFrameHost
* render_frame_host
,
149 const GURL
& validated_url
) {
151 waiting_for_page_ready_
= false;
152 SendJavaScript(viewer::GetErrorPageJs());
153 std::string
title(l10n_util::GetStringUTF8(
154 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT
));
155 SendJavaScript(viewer::GetSetTitleJs(title
));
156 SendJavaScript(viewer::GetSetTextDirectionJs(std::string("auto")));
157 SendJavaScript(viewer::GetShowFeedbackFormJs());
159 Cancel(); // This will cause the object to clean itself up.
163 if (render_frame_host
->GetParent()) {
166 waiting_for_page_ready_
= false;
167 if (buffer_
.empty()) {
170 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_
));
174 DomDistillerViewerSource::DomDistillerViewerSource(
175 DomDistillerServiceInterface
* dom_distiller_service
,
176 const std::string
& scheme
,
177 scoped_ptr
<ExternalFeedbackReporter
> external_reporter
)
179 dom_distiller_service_(dom_distiller_service
),
180 external_feedback_reporter_(external_reporter
.Pass()) {
183 DomDistillerViewerSource::~DomDistillerViewerSource() {
186 std::string
DomDistillerViewerSource::GetSource() const {
187 return scheme_
+ "://";
190 void DomDistillerViewerSource::StartDataRequest(
191 const std::string
& path
,
192 int render_process_id
,
194 const content::URLDataSource::GotDataCallback
& callback
) {
195 content::RenderFrameHost
* render_frame_host
=
196 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
197 if (!render_frame_host
) return;
198 content::RenderViewHost
* render_view_host
=
199 render_frame_host
->GetRenderViewHost();
200 DCHECK(render_view_host
);
201 CHECK_EQ(0, render_view_host
->GetEnabledBindings());
203 if (kViewerCssPath
== path
) {
204 std::string css
= viewer::GetCss();
205 callback
.Run(base::RefCountedString::TakeString(&css
));
207 } else if (kViewerJsPath
== path
) {
208 std::string js
= viewer::GetJavaScript();
209 callback
.Run(base::RefCountedString::TakeString(&js
));
211 } else if (kViewerViewOriginalPath
== path
) {
212 content::RecordAction(base::UserMetricsAction("DomDistiller_ViewOriginal"));
215 } else if (kFeedbackBad
== path
) {
216 FeedbackReporter::ReportQuality(false);
218 if (!external_feedback_reporter_
)
220 content::WebContents
* contents
=
221 content::WebContents::FromRenderFrameHost(render_frame_host
);
222 external_feedback_reporter_
->ReportExternalFeedback(
223 contents
, contents
->GetURL(), false);
225 } else if (kFeedbackGood
== path
) {
226 FeedbackReporter::ReportQuality(true);
230 content::WebContents
* web_contents
=
231 content::WebContents::FromRenderFrameHost(render_frame_host
);
232 DCHECK(web_contents
);
233 // An empty |path| is invalid, but guard against it. If not empty, assume
234 // |path| starts with '?', which is stripped away.
235 const std::string path_after_query_separator
=
236 path
.size() > 0 ? path
.substr(1) : "";
237 RequestViewerHandle
* request_viewer_handle
=
238 new RequestViewerHandle(web_contents
, scheme_
, path_after_query_separator
,
239 dom_distiller_service_
->GetDistilledPagePrefs());
240 scoped_ptr
<ViewerHandle
> viewer_handle
= viewer::CreateViewRequest(
241 dom_distiller_service_
, path
, request_viewer_handle
,
242 web_contents
->GetContainerBounds().size());
244 GURL current_url
= web_contents
->GetLastCommittedURL();
245 std::string unsafe_page_html
= viewer::GetUnsafeArticleTemplateHtml(
246 url_utils::GetOriginalUrlFromDistillerUrl(current_url
).spec(),
247 dom_distiller_service_
->GetDistilledPagePrefs()->GetTheme(),
248 dom_distiller_service_
->GetDistilledPagePrefs()->GetFontFamily());
251 // The service returned a |ViewerHandle| and guarantees it will call
252 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
253 // request is not cancelled. The |RequestViewerHandle| will delete itself
254 // after receiving the callback.
255 request_viewer_handle
->TakeViewerHandle(viewer_handle
.Pass());
257 request_viewer_handle
->FlagAsErrorPage();
260 // Place template on the page.
261 callback
.Run(base::RefCountedString::TakeString(&unsafe_page_html
));
264 std::string
DomDistillerViewerSource::GetMimeType(
265 const std::string
& path
) const {
266 if (kViewerCssPath
== path
) {
269 if (kViewerJsPath
== path
) {
270 return "text/javascript";
275 bool DomDistillerViewerSource::ShouldServiceRequest(
276 const net::URLRequest
* request
) const {
277 return request
->url().SchemeIs(scheme_
.c_str());
280 // TODO(nyquist): Start tracking requests using this method.
281 void DomDistillerViewerSource::WillServiceRequest(
282 const net::URLRequest
* request
,
283 std::string
* path
) const {
286 std::string
DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
288 return "object-src 'none'; style-src 'self' https://fonts.googleapis.com;";
291 std::string
DomDistillerViewerSource::GetContentSecurityPolicyFrameSrc() const {
292 return "frame-src *;";
295 } // namespace dom_distiller