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/viewer.h"
24 #include "content/public/browser/navigation_details.h"
25 #include "content/public/browser/navigation_entry.h"
26 #include "content/public/browser/render_frame_host.h"
27 #include "content/public/browser/render_view_host.h"
28 #include "content/public/browser/user_metrics.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/browser/web_contents_observer.h"
31 #include "net/base/url_util.h"
32 #include "net/url_request/url_request.h"
34 namespace dom_distiller
{
38 class ContentDataCallback
: public DistillerDataCallback
{
40 ContentDataCallback(const content::URLDataSource::GotDataCallback
& callback
);
42 void RunCallback(std::string
& data
) override
;
45 // The callback that actually gets run.
46 content::URLDataSource::GotDataCallback callback_
;
49 ContentDataCallback::ContentDataCallback(
50 const content::URLDataSource::GotDataCallback
& callback
) {
54 void ContentDataCallback::RunCallback(std::string
& data
) {
55 callback_
.Run(base::RefCountedString::TakeString(&data
));
60 // Handles receiving data asynchronously for a specific entry, and passing
61 // it along to the data callback for the data source. Lifetime matches that of
62 // the current main frame's page in the Viewer instance.
63 class DomDistillerViewerSource::RequestViewerHandle
64 : public DomDistillerRequestViewBase
,
65 public content::WebContentsObserver
{
67 RequestViewerHandle(content::WebContents
* web_contents
,
68 const std::string
& expected_scheme
,
69 const std::string
& expected_request_path
,
70 scoped_ptr
<ContentDataCallback
> callback
,
71 DistilledPagePrefs
* distilled_page_prefs
);
72 ~RequestViewerHandle() override
;
74 // content::WebContentsObserver implementation:
75 void DidNavigateMainFrame(
76 const content::LoadCommittedDetails
& details
,
77 const content::FrameNavigateParams
& params
) override
;
78 void RenderProcessGone(base::TerminationStatus status
) override
;
79 void WebContentsDestroyed() override
;
80 void DidFinishLoad(content::RenderFrameHost
* render_frame_host
,
81 const GURL
& validated_url
) override
;
84 // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't
86 void SendJavaScript(const std::string
& buffer
) override
;
88 // Cancels the current view request. Once called, no updates will be
89 // propagated to the view, and the request to DomDistillerService will be
93 // The scheme hosting the current view request;
94 std::string expected_scheme_
;
96 // The query path for the current view request.
97 std::string expected_request_path_
;
99 // Whether the page is sufficiently initialized to handle updates from the
101 bool waiting_for_page_ready_
;
103 // Temporary store of pending JavaScript if the page isn't ready to receive
104 // data from distillation.
108 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
109 content::WebContents
* web_contents
,
110 const std::string
& expected_scheme
,
111 const std::string
& expected_request_path
,
112 scoped_ptr
<ContentDataCallback
> callback
,
113 DistilledPagePrefs
* distilled_page_prefs
)
114 : DomDistillerRequestViewBase(callback
.Pass(), distilled_page_prefs
),
115 expected_scheme_(expected_scheme
),
116 expected_request_path_(expected_request_path
),
117 waiting_for_page_ready_(true) {
118 content::WebContentsObserver::Observe(web_contents
);
119 distilled_page_prefs_
->AddObserver(this);
122 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
123 distilled_page_prefs_
->RemoveObserver(this);
126 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
127 const std::string
& buffer
) {
128 if (waiting_for_page_ready_
) {
131 if (web_contents()) {
132 web_contents()->GetMainFrame()->ExecuteJavaScript(
133 base::UTF8ToUTF16(buffer
));
138 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
139 const content::LoadCommittedDetails
& details
,
140 const content::FrameNavigateParams
& params
) {
141 const GURL
& navigation
= details
.entry
->GetURL();
142 if (details
.is_in_page
|| (
143 navigation
.SchemeIs(expected_scheme_
.c_str()) &&
144 expected_request_path_
== navigation
.query())) {
145 // In-page navigations, as well as the main view request can be ignored.
152 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
153 base::TerminationStatus status
) {
157 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
161 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
162 // No need to listen for notifications.
163 content::WebContentsObserver::Observe(NULL
);
165 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
166 // any pending data stored in |buffer_| is released.
167 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, this);
170 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
171 content::RenderFrameHost
* render_frame_host
,
172 const GURL
& validated_url
) {
174 waiting_for_page_ready_
= false;
175 SendJavaScript(viewer::GetErrorPageJs());
176 SendJavaScript(viewer::GetShowFeedbackFormJs());
177 Cancel(); // This will cause the object to clean itself up.
181 if (render_frame_host
->GetParent()) {
184 waiting_for_page_ready_
= false;
185 if (buffer_
.empty()) {
188 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_
));
192 DomDistillerViewerSource::DomDistillerViewerSource(
193 DomDistillerServiceInterface
* dom_distiller_service
,
194 const std::string
& scheme
,
195 scoped_ptr
<ExternalFeedbackReporter
> external_reporter
)
197 dom_distiller_service_(dom_distiller_service
),
198 external_feedback_reporter_(external_reporter
.Pass()) {
201 DomDistillerViewerSource::~DomDistillerViewerSource() {
204 std::string
DomDistillerViewerSource::GetSource() const {
205 return scheme_
+ "://";
208 void DomDistillerViewerSource::StartDataRequest(
209 const std::string
& path
,
210 int render_process_id
,
212 const content::URLDataSource::GotDataCallback
& callback
) {
213 content::RenderFrameHost
* render_frame_host
=
214 content::RenderFrameHost::FromID(render_process_id
, render_frame_id
);
215 if (!render_frame_host
) return;
216 content::RenderViewHost
* render_view_host
=
217 render_frame_host
->GetRenderViewHost();
218 DCHECK(render_view_host
);
219 CHECK_EQ(0, render_view_host
->GetEnabledBindings());
221 if (kViewerCssPath
== path
) {
222 std::string css
= viewer::GetCss();
223 callback
.Run(base::RefCountedString::TakeString(&css
));
225 } else if (kViewerJsPath
== path
) {
226 std::string js
= viewer::GetJavaScript();
227 callback
.Run(base::RefCountedString::TakeString(&js
));
229 } else if (kViewerViewOriginalPath
== path
) {
230 content::RecordAction(base::UserMetricsAction("DomDistiller_ViewOriginal"));
233 } else if (kFeedbackBad
== path
) {
234 FeedbackReporter::ReportQuality(false);
236 if (!external_feedback_reporter_
)
238 content::WebContents
* contents
=
239 content::WebContents::FromRenderFrameHost(render_frame_host
);
240 external_feedback_reporter_
->ReportExternalFeedback(
241 contents
, contents
->GetURL(), false);
243 } else if (kFeedbackGood
== path
) {
244 FeedbackReporter::ReportQuality(true);
248 content::WebContents
* web_contents
=
249 content::WebContents::FromRenderFrameHost(render_frame_host
);
250 DCHECK(web_contents
);
251 // An empty |path| is invalid, but guard against it. If not empty, assume
252 // |path| starts with '?', which is stripped away.
253 scoped_ptr
<ContentDataCallback
> data_callback(
254 new ContentDataCallback(callback
));
255 const std::string path_after_query_separator
=
256 path
.size() > 0 ? path
.substr(1) : "";
257 RequestViewerHandle
* request_viewer_handle
= new RequestViewerHandle(
258 web_contents
, scheme_
, path_after_query_separator
, data_callback
.Pass(),
259 dom_distiller_service_
->GetDistilledPagePrefs());
260 scoped_ptr
<ViewerHandle
> viewer_handle
= viewer::CreateViewRequest(
261 dom_distiller_service_
, path
, request_viewer_handle
,
262 web_contents
->GetContainerBounds().size());
265 // The service returned a |ViewerHandle| and guarantees it will call
266 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
267 // request is not cancelled. The |RequestViewerHandle| will delete itself
268 // after receiving the callback.
269 request_viewer_handle
->TakeViewerHandle(viewer_handle
.Pass());
271 request_viewer_handle
->FlagAsErrorPage();
275 std::string
DomDistillerViewerSource::GetMimeType(
276 const std::string
& path
) const {
277 if (kViewerCssPath
== path
) {
280 if (kViewerJsPath
== path
) {
281 return "text/javascript";
286 bool DomDistillerViewerSource::ShouldServiceRequest(
287 const net::URLRequest
* request
) const {
288 return request
->url().SchemeIs(scheme_
.c_str());
291 // TODO(nyquist): Start tracking requests using this method.
292 void DomDistillerViewerSource::WillServiceRequest(
293 const net::URLRequest
* request
,
294 std::string
* path
) const {
297 std::string
DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
299 return "object-src 'none'; style-src 'self' https://fonts.googleapis.com;";
302 std::string
DomDistillerViewerSource::GetContentSecurityPolicyFrameSrc() const {
303 return "frame-src *;";
306 } // namespace dom_distiller