Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / components / dom_distiller / content / dom_distiller_viewer_source.cc
blob3d26eb889426306f885df9b1b1dd52c839bf7873
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/strings/utf_string_conversions.h"
15 #include "components/dom_distiller/core/distilled_page_prefs.h"
16 #include "components/dom_distiller/core/dom_distiller_service.h"
17 #include "components/dom_distiller/core/task_tracker.h"
18 #include "components/dom_distiller/core/url_constants.h"
19 #include "components/dom_distiller/core/viewer.h"
20 #include "content/public/browser/navigation_details.h"
21 #include "content/public/browser/navigation_entry.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/browser/web_contents_observer.h"
26 #include "net/base/url_util.h"
27 #include "net/url_request/url_request.h"
29 namespace dom_distiller {
31 // Handles receiving data asynchronously for a specific entry, and passing
32 // it along to the data callback for the data source. Lifetime matches that of
33 // the current main frame's page in the Viewer instance.
34 class DomDistillerViewerSource::RequestViewerHandle
35 : public ViewRequestDelegate,
36 public content::WebContentsObserver,
37 public DistilledPagePrefs::Observer {
38 public:
39 explicit RequestViewerHandle(
40 content::WebContents* web_contents,
41 const std::string& expected_scheme,
42 const std::string& expected_request_path,
43 const content::URLDataSource::GotDataCallback& callback,
44 DistilledPagePrefs* distilled_page_prefs);
45 virtual ~RequestViewerHandle();
47 // ViewRequestDelegate implementation:
48 virtual void OnArticleReady(
49 const DistilledArticleProto* article_proto) OVERRIDE;
51 virtual void OnArticleUpdated(
52 ArticleDistillationUpdate article_update) OVERRIDE;
54 void TakeViewerHandle(scoped_ptr<ViewerHandle> viewer_handle);
56 // content::WebContentsObserver implementation:
57 virtual void DidNavigateMainFrame(
58 const content::LoadCommittedDetails& details,
59 const content::FrameNavigateParams& params) OVERRIDE;
60 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
61 virtual void WebContentsDestroyed() OVERRIDE;
62 virtual void DidFinishLoad(content::RenderFrameHost* render_frame_host,
63 const GURL& validated_url) OVERRIDE;
65 private:
66 // Sends JavaScript to the attached Viewer, buffering data if the viewer isn't
67 // ready.
68 void SendJavaScript(const std::string& buffer);
70 // Cancels the current view request. Once called, no updates will be
71 // propagated to the view, and the request to DomDistillerService will be
72 // cancelled.
73 void Cancel();
75 // DistilledPagePrefs::Observer implementation:
76 virtual void OnChangeFontFamily(
77 DistilledPagePrefs::FontFamily new_font_family) OVERRIDE;
78 virtual void OnChangeTheme(DistilledPagePrefs::Theme new_theme) OVERRIDE;
80 // The handle to the view request towards the DomDistillerService. It
81 // needs to be kept around to ensure the distillation request finishes.
82 scoped_ptr<ViewerHandle> viewer_handle_;
84 // The scheme hosting the current view request;
85 std::string expected_scheme_;
87 // The query path for the current view request.
88 std::string expected_request_path_;
90 // Holds the callback to where the data retrieved is sent back.
91 content::URLDataSource::GotDataCallback callback_;
93 // Number of pages of the distilled article content that have been rendered by
94 // the viewer.
95 int page_count_;
97 // Interface for accessing preferences for distilled pages.
98 DistilledPagePrefs* distilled_page_prefs_;
100 // Whether the page is sufficiently initialized to handle updates from the
101 // distiller.
102 bool waiting_for_page_ready_;
104 // Temporary store of pending JavaScript if the page isn't ready to receive
105 // data from distillation.
106 std::string buffer_;
109 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
110 content::WebContents* web_contents,
111 const std::string& expected_scheme,
112 const std::string& expected_request_path,
113 const content::URLDataSource::GotDataCallback& callback,
114 DistilledPagePrefs* distilled_page_prefs)
115 : expected_scheme_(expected_scheme),
116 expected_request_path_(expected_request_path),
117 callback_(callback),
118 page_count_(0),
119 distilled_page_prefs_(distilled_page_prefs),
120 waiting_for_page_ready_(true) {
121 content::WebContentsObserver::Observe(web_contents);
122 distilled_page_prefs_->AddObserver(this);
125 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
126 distilled_page_prefs_->RemoveObserver(this);
129 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
130 const std::string& buffer) {
131 if (waiting_for_page_ready_) {
132 buffer_ += buffer;
133 } else {
134 if (web_contents()) {
135 web_contents()->GetMainFrame()->ExecuteJavaScript(
136 base::UTF8ToUTF16(buffer));
141 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
142 const content::LoadCommittedDetails& details,
143 const content::FrameNavigateParams& params) {
144 const GURL& navigation = details.entry->GetURL();
145 if (details.is_in_page || (
146 navigation.SchemeIs(expected_scheme_.c_str()) &&
147 expected_request_path_ == navigation.query())) {
148 // In-page navigations, as well as the main view request can be ignored.
149 return;
152 Cancel();
156 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
157 base::TerminationStatus status) {
158 Cancel();
161 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
162 Cancel();
165 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
166 // No need to listen for notifications.
167 content::WebContentsObserver::Observe(NULL);
169 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
170 // any pending data stored in |buffer_| is released.
171 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
174 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
175 content::RenderFrameHost* render_frame_host,
176 const GURL& validated_url) {
177 if (render_frame_host->GetParent()) {
178 return;
180 waiting_for_page_ready_ = false;
181 if (buffer_.empty()) {
182 return;
184 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_));
185 buffer_.clear();
188 void DomDistillerViewerSource::RequestViewerHandle::OnArticleReady(
189 const DistilledArticleProto* article_proto) {
190 if (page_count_ == 0) {
191 // This is a single-page article.
192 std::string unsafe_page_html =
193 viewer::GetUnsafeArticleHtml(
194 article_proto,
195 distilled_page_prefs_->GetTheme(),
196 distilled_page_prefs_->GetFontFamily());
197 callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html));
198 } else if (page_count_ == article_proto->pages_size()) {
199 // We may still be showing the "Loading" indicator.
200 SendJavaScript(viewer::GetToggleLoadingIndicatorJs(true));
201 } else {
202 // It's possible that we didn't get some incremental updates from the
203 // distiller. Ensure all remaining pages are flushed to the viewer.
204 for (;page_count_ < article_proto->pages_size(); page_count_++) {
205 const DistilledPageProto& page = article_proto->pages(page_count_);
206 SendJavaScript(
207 viewer::GetUnsafeIncrementalDistilledPageJs(
208 &page,
209 page_count_ == article_proto->pages_size()));
212 // No need to hold on to the ViewerHandle now that distillation is complete.
213 viewer_handle_.reset();
216 void DomDistillerViewerSource::RequestViewerHandle::OnArticleUpdated(
217 ArticleDistillationUpdate article_update) {
218 for (;page_count_ < static_cast<int>(article_update.GetPagesSize());
219 page_count_++) {
220 const DistilledPageProto& page =
221 article_update.GetDistilledPage(page_count_);
222 if (page_count_ == 0) {
223 // This is the first page, so send Viewer page scaffolding too.
224 std::string unsafe_page_html = viewer::GetUnsafePartialArticleHtml(
225 &page,
226 distilled_page_prefs_->GetTheme(),
227 distilled_page_prefs_->GetFontFamily());
228 callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html));
229 } else {
230 SendJavaScript(
231 viewer::GetUnsafeIncrementalDistilledPageJs(&page, false));
236 void DomDistillerViewerSource::RequestViewerHandle::TakeViewerHandle(
237 scoped_ptr<ViewerHandle> viewer_handle) {
238 viewer_handle_ = viewer_handle.Pass();
241 void DomDistillerViewerSource::RequestViewerHandle::OnChangeTheme(
242 DistilledPagePrefs::Theme new_theme) {
243 SendJavaScript(viewer::GetDistilledPageThemeJs(new_theme));
246 void DomDistillerViewerSource::RequestViewerHandle::OnChangeFontFamily(
247 DistilledPagePrefs::FontFamily new_font) {
248 SendJavaScript(viewer::GetDistilledPageFontFamilyJs(new_font));
251 DomDistillerViewerSource::DomDistillerViewerSource(
252 DomDistillerServiceInterface* dom_distiller_service,
253 const std::string& scheme)
254 : scheme_(scheme), dom_distiller_service_(dom_distiller_service) {
257 DomDistillerViewerSource::~DomDistillerViewerSource() {
260 std::string DomDistillerViewerSource::GetSource() const {
261 return scheme_ + "://";
264 void DomDistillerViewerSource::StartDataRequest(
265 const std::string& path,
266 int render_process_id,
267 int render_frame_id,
268 const content::URLDataSource::GotDataCallback& callback) {
269 content::RenderFrameHost* render_frame_host =
270 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
271 DCHECK(render_frame_host);
272 content::RenderViewHost* render_view_host =
273 render_frame_host->GetRenderViewHost();
274 DCHECK(render_view_host);
275 CHECK_EQ(0, render_view_host->GetEnabledBindings());
277 if (kViewerCssPath == path) {
278 std::string css = viewer::GetCss();
279 callback.Run(base::RefCountedString::TakeString(&css));
280 return;
282 if (kViewerJsPath == path) {
283 std::string js = viewer::GetJavaScript();
284 callback.Run(base::RefCountedString::TakeString(&js));
285 return;
287 content::WebContents* web_contents =
288 content::WebContents::FromRenderFrameHost(
289 content::RenderFrameHost::FromID(render_process_id,
290 render_frame_id));
291 DCHECK(web_contents);
292 // An empty |path| is invalid, but guard against it. If not empty, assume
293 // |path| starts with '?', which is stripped away.
294 const std::string path_after_query_separator =
295 path.size() > 0 ? path.substr(1) : "";
296 RequestViewerHandle* request_viewer_handle = new RequestViewerHandle(
297 web_contents, scheme_, path_after_query_separator, callback,
298 dom_distiller_service_->GetDistilledPagePrefs());
299 scoped_ptr<ViewerHandle> viewer_handle = viewer::CreateViewRequest(
300 dom_distiller_service_, path, request_viewer_handle,
301 web_contents->GetContainerBounds().size());
303 if (viewer_handle) {
304 // The service returned a |ViewerHandle| and guarantees it will call
305 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
306 // request is not cancelled. The |RequestViewerHandle| will delete itself
307 // after receiving the callback.
308 request_viewer_handle->TakeViewerHandle(viewer_handle.Pass());
309 } else {
310 // The service did not return a |ViewerHandle|, which means the
311 // |RequestViewerHandle| will never be called, so clean up now.
312 delete request_viewer_handle;
314 std::string error_page_html = viewer::GetErrorPageHtml(
315 dom_distiller_service_->GetDistilledPagePrefs()->GetTheme(),
316 dom_distiller_service_->GetDistilledPagePrefs()->GetFontFamily());
317 callback.Run(base::RefCountedString::TakeString(&error_page_html));
321 std::string DomDistillerViewerSource::GetMimeType(
322 const std::string& path) const {
323 if (kViewerCssPath == path) {
324 return "text/css";
326 if (kViewerJsPath == path) {
327 return "text/javascript";
329 return "text/html";
332 bool DomDistillerViewerSource::ShouldServiceRequest(
333 const net::URLRequest* request) const {
334 return request->url().SchemeIs(scheme_.c_str());
337 // TODO(nyquist): Start tracking requests using this method.
338 void DomDistillerViewerSource::WillServiceRequest(
339 const net::URLRequest* request,
340 std::string* path) const {
343 std::string DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
344 const {
345 return "object-src 'none'; style-src 'self' https://fonts.googleapis.com;";
348 } // namespace dom_distiller