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/browser/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/content/browser/distiller_javascript_utils.h"
17 #include "components/dom_distiller/content/browser/external_feedback_reporter.h"
18 #include "components/dom_distiller/core/distilled_page_prefs.h"
19 #include "components/dom_distiller/core/dom_distiller_request_view_base.h"
20 #include "components/dom_distiller/core/dom_distiller_service.h"
21 #include "components/dom_distiller/core/experiments.h"
22 #include "components/dom_distiller/core/feedback_reporter.h"
23 #include "components/dom_distiller/core/task_tracker.h"
24 #include "components/dom_distiller/core/url_constants.h"
25 #include "components/dom_distiller/core/url_utils.h"
26 #include "components/dom_distiller/core/viewer.h"
27 #include "content/public/browser/navigation_details.h"
28 #include "content/public/browser/navigation_entry.h"
29 #include "content/public/browser/render_frame_host.h"
30 #include "content/public/browser/render_view_host.h"
31 #include "content/public/browser/user_metrics.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_contents_observer.h"
34 #include "grit/components_strings.h"
35 #include "net/base/url_util.h"
36 #include "net/url_request/url_request.h"
37 #include "ui/base/l10n/l10n_util.h"
39 namespace dom_distiller
{
41 // Handles receiving data asynchronously for a specific entry, and passing
42 // it along to the data callback for the data source. Lifetime matches that of
43 // the current main frame's page in the Viewer instance.
44 class DomDistillerViewerSource::RequestViewerHandle
45 : public DomDistillerRequestViewBase
,
46 public content::WebContentsObserver
{
48 RequestViewerHandle(content::WebContents
* web_contents
,
49 const std::string
& expected_scheme
,
50 const std::string
& expected_request_path
,
51 DistilledPagePrefs
* distilled_page_prefs
);
52 ~RequestViewerHandle() override
;
54 // content::WebContentsObserver implementation:
55 void DidNavigateMainFrame(
56 const content::LoadCommittedDetails
& details
,
57 const content::FrameNavigateParams
& params
) override
;
58 void RenderProcessGone(base::TerminationStatus status
) override
;
59 void WebContentsDestroyed() override
;
60 void DidFinishLoad(content::RenderFrameHost
* render_frame_host
,
61 const GURL
& validated_url
) override
;
64 // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't
66 void SendJavaScript(const std::string
& buffer
) override
;
68 // Cancels the current view request. Once called, no updates will be
69 // propagated to the view, and the request to DomDistillerService will be
73 // The scheme hosting the current view request;
74 std::string expected_scheme_
;
76 // The query path for the current view request.
77 std::string expected_request_path_
;
79 // Whether the page is sufficiently initialized to handle updates from the
81 bool waiting_for_page_ready_
;
83 // Temporary store of pending JavaScript if the page isn't ready to receive
84 // data from distillation.
88 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
89 content::WebContents
* web_contents
,
90 const std::string
& expected_scheme
,
91 const std::string
& expected_request_path
,
92 DistilledPagePrefs
* distilled_page_prefs
)
93 : DomDistillerRequestViewBase(distilled_page_prefs
),
94 expected_scheme_(expected_scheme
),
95 expected_request_path_(expected_request_path
),
96 waiting_for_page_ready_(true) {
97 content::WebContentsObserver::Observe(web_contents
);
98 distilled_page_prefs_
->AddObserver(this);
101 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
102 distilled_page_prefs_
->RemoveObserver(this);
105 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
106 const std::string
& buffer
) {
107 if (waiting_for_page_ready_
) {
110 if (web_contents()) {
111 RunIsolatedJavaScript(web_contents()->GetMainFrame(), buffer
);
116 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
117 const content::LoadCommittedDetails
& details
,
118 const content::FrameNavigateParams
& params
) {
119 const GURL
& navigation
= details
.entry
->GetURL();
120 if (details
.is_in_page
|| (
121 navigation
.SchemeIs(expected_scheme_
.c_str()) &&
122 expected_request_path_
== navigation
.query())) {
123 // In-page navigations, as well as the main view request can be ignored.
130 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
131 base::TerminationStatus status
) {
135 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
139 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
140 // No need to listen for notifications.
141 content::WebContentsObserver::Observe(NULL
);
143 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
144 // any pending data stored in |buffer_| is released.
145 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
148 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
149 content::RenderFrameHost
* render_frame_host
,
150 const GURL
& validated_url
) {
151 if (render_frame_host
->GetParent()) {
154 waiting_for_page_ready_
= false;
155 if (!buffer_
.empty()) {
156 RunIsolatedJavaScript(web_contents()->GetMainFrame(), buffer_
);
160 Cancel(); // This will cause the object to clean itself up.
164 DomDistillerViewerSource::DomDistillerViewerSource(
165 DomDistillerServiceInterface
* dom_distiller_service
,
166 const std::string
& scheme
,
167 scoped_ptr
<ExternalFeedbackReporter
> external_reporter
)
169 dom_distiller_service_(dom_distiller_service
),
170 external_feedback_reporter_(external_reporter
.Pass()) {
173 DomDistillerViewerSource::~DomDistillerViewerSource() {
176 std::string
DomDistillerViewerSource::GetSource() const {
177 return scheme_
+ "://";
180 void DomDistillerViewerSource::StartDataRequest(
181 const std::string
& path
,
182 int render_process_id
,
184 const content::URLDataSource::GotDataCallback
& callback
) {
185 content::RenderFrameHost
* render_frame_host
=
186 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
187 if (!render_frame_host
) return;
188 content::RenderViewHost
* render_view_host
=
189 render_frame_host
->GetRenderViewHost();
190 DCHECK(render_view_host
);
191 CHECK_EQ(0, render_view_host
->GetEnabledBindings());
193 if (kViewerCssPath
== path
) {
194 std::string css
= viewer::GetCss();
195 callback
.Run(base::RefCountedString::TakeString(&css
));
197 } else if (kViewerViewOriginalPath
== path
) {
198 content::RecordAction(base::UserMetricsAction("DomDistiller_ViewOriginal"));
201 } else if (kFeedbackBad
== path
) {
202 FeedbackReporter::ReportQuality(false);
204 if (!external_feedback_reporter_
)
206 content::WebContents
* contents
=
207 content::WebContents::FromRenderFrameHost(render_frame_host
);
208 external_feedback_reporter_
->ReportExternalFeedback(
209 contents
, contents
->GetURL(), false);
211 } else if (kFeedbackGood
== path
) {
212 FeedbackReporter::ReportQuality(true);
216 content::WebContents
* web_contents
=
217 content::WebContents::FromRenderFrameHost(render_frame_host
);
218 DCHECK(web_contents
);
219 // An empty |path| is invalid, but guard against it. If not empty, assume
220 // |path| starts with '?', which is stripped away.
221 const std::string path_after_query_separator
=
222 path
.size() > 0 ? path
.substr(1) : "";
223 RequestViewerHandle
* request_viewer_handle
=
224 new RequestViewerHandle(web_contents
, scheme_
, path_after_query_separator
,
225 dom_distiller_service_
->GetDistilledPagePrefs());
226 scoped_ptr
<ViewerHandle
> viewer_handle
= viewer::CreateViewRequest(
227 dom_distiller_service_
, path
, request_viewer_handle
,
228 web_contents
->GetContainerBounds().size());
230 GURL
current_url(url_utils::GetValueForKeyInUrlPathQuery(path
, kUrlKey
));
231 std::string unsafe_page_html
= viewer::GetUnsafeArticleTemplateHtml(
232 url_utils::GetOriginalUrlFromDistillerUrl(current_url
).spec(),
233 dom_distiller_service_
->GetDistilledPagePrefs()->GetTheme(),
234 dom_distiller_service_
->GetDistilledPagePrefs()->GetFontFamily());
237 // The service returned a |ViewerHandle| and guarantees it will call
238 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
239 // request is not cancelled. The |RequestViewerHandle| will delete itself
240 // after receiving the callback.
241 request_viewer_handle
->TakeViewerHandle(viewer_handle
.Pass());
243 request_viewer_handle
->FlagAsErrorPage();
246 // Place template on the page.
247 callback
.Run(base::RefCountedString::TakeString(&unsafe_page_html
));
250 std::string
DomDistillerViewerSource::GetMimeType(
251 const std::string
& path
) const {
252 if (kViewerCssPath
== path
) {
255 if (kViewerJsPath
== path
) {
256 return "text/javascript";
261 bool DomDistillerViewerSource::ShouldServiceRequest(
262 const net::URLRequest
* request
) const {
263 return request
->url().SchemeIs(scheme_
.c_str());
266 // TODO(nyquist): Start tracking requests using this method.
267 void DomDistillerViewerSource::WillServiceRequest(
268 const net::URLRequest
* request
,
269 std::string
* path
) const {
272 std::string
DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
274 return "object-src 'none'; style-src 'self' https://fonts.googleapis.com;";
277 std::string
DomDistillerViewerSource::GetContentSecurityPolicyFrameSrc() const {
278 return "frame-src *;";
281 } // namespace dom_distiller