Don't preload rarely seen large images
[chromium-blink-merge.git] / components / dom_distiller / content / dom_distiller_viewer_source.cc
blobb6b611cac83ed92551be0e8c9c0ebcb62c2d36b7
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"
7 #include <sstream>
8 #include <string>
9 #include <vector>
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 {
45 public:
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;
61 private:
62 // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't
63 // ready.
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
68 // cancelled.
69 void Cancel();
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
78 // distiller.
79 bool waiting_for_page_ready_;
81 // Temporary store of pending JavaScript if the page isn't ready to receive
82 // data from distillation.
83 std::string buffer_;
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_) {
106 buffer_ += buffer;
107 } else {
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.
123 return;
126 Cancel();
129 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
130 base::TerminationStatus status) {
131 Cancel();
134 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
135 Cancel();
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) {
150 if (IsErrorPage()) {
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.
160 return;
163 if (render_frame_host->GetParent()) {
164 return;
166 waiting_for_page_ready_ = false;
167 if (buffer_.empty()) {
168 return;
170 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_));
171 buffer_.clear();
174 DomDistillerViewerSource::DomDistillerViewerSource(
175 DomDistillerServiceInterface* dom_distiller_service,
176 const std::string& scheme,
177 scoped_ptr<ExternalFeedbackReporter> external_reporter)
178 : scheme_(scheme),
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,
193 int render_frame_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));
206 return;
207 } else if (kViewerJsPath == path) {
208 std::string js = viewer::GetJavaScript();
209 callback.Run(base::RefCountedString::TakeString(&js));
210 return;
211 } else if (kViewerViewOriginalPath == path) {
212 content::RecordAction(base::UserMetricsAction("DomDistiller_ViewOriginal"));
213 callback.Run(NULL);
214 return;
215 } else if (kFeedbackBad == path) {
216 FeedbackReporter::ReportQuality(false);
217 callback.Run(NULL);
218 if (!external_feedback_reporter_)
219 return;
220 content::WebContents* contents =
221 content::WebContents::FromRenderFrameHost(render_frame_host);
222 external_feedback_reporter_->ReportExternalFeedback(
223 contents, contents->GetURL(), false);
224 return;
225 } else if (kFeedbackGood == path) {
226 FeedbackReporter::ReportQuality(true);
227 callback.Run(NULL);
228 return;
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());
250 if (viewer_handle) {
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());
256 } else {
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) {
267 return "text/css";
269 if (kViewerJsPath == path) {
270 return "text/javascript";
272 return "text/html";
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()
287 const {
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