Convert remaining WebContentsObservers loading callbacks to use RFH.
[chromium-blink-merge.git] / components / dom_distiller / content / dom_distiller_viewer_source.cc
blob8f475cc52a951d3006b57e9faa9fa4da5cc9d7a2
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/task_tracker.h"
16 #include "components/dom_distiller/core/url_constants.h"
17 #include "components/dom_distiller/core/viewer.h"
18 #include "content/public/browser/navigation_details.h"
19 #include "content/public/browser/navigation_entry.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/render_view_host.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_observer.h"
24 #include "net/base/url_util.h"
25 #include "net/url_request/url_request.h"
27 namespace dom_distiller {
29 // Handles receiving data asynchronously for a specific entry, and passing
30 // it along to the data callback for the data source. Lifetime matches that of
31 // the current main frame's page in the Viewer instance.
32 class DomDistillerViewerSource::RequestViewerHandle
33 : public ViewRequestDelegate,
34 public content::WebContentsObserver {
35 public:
36 explicit RequestViewerHandle(
37 content::WebContents* web_contents,
38 const std::string& expected_scheme,
39 const std::string& expected_request_path,
40 const content::URLDataSource::GotDataCallback& callback);
41 virtual ~RequestViewerHandle();
43 // ViewRequestDelegate implementation.
44 virtual void OnArticleReady(
45 const DistilledArticleProto* article_proto) OVERRIDE;
47 virtual void OnArticleUpdated(
48 ArticleDistillationUpdate article_update) OVERRIDE;
50 void TakeViewerHandle(scoped_ptr<ViewerHandle> viewer_handle);
52 // WebContentsObserver:
53 virtual void DidNavigateMainFrame(
54 const content::LoadCommittedDetails& details,
55 const content::FrameNavigateParams& params) OVERRIDE;
56 virtual void RenderProcessGone(base::TerminationStatus status) OVERRIDE;
57 virtual void WebContentsDestroyed() OVERRIDE;
58 virtual 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);
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 handle to the view request towards the DomDistillerService. It
72 // needs to be kept around to ensure the distillation request finishes.
73 scoped_ptr<ViewerHandle> viewer_handle_;
75 // The scheme hosting the current view request;
76 std::string expected_scheme_;
78 // The query path for the current view request.
79 std::string expected_request_path_;
81 // Holds the callback to where the data retrieved is sent back.
82 content::URLDataSource::GotDataCallback callback_;
84 // Number of pages of the distilled article content that have been rendered by
85 // the viewer.
86 int page_count_;
88 // Whether the page is sufficiently initialized to handle updates from the
89 // distiller.
90 bool waiting_for_page_ready_;
92 // Temporary store of pending JavaScript if the page isn't ready to receive
93 // data from distillation.
94 std::string buffer_;
97 DomDistillerViewerSource::RequestViewerHandle::RequestViewerHandle(
98 content::WebContents* web_contents,
99 const std::string& expected_scheme,
100 const std::string& expected_request_path,
101 const content::URLDataSource::GotDataCallback& callback)
102 : expected_scheme_(expected_scheme),
103 expected_request_path_(expected_request_path),
104 callback_(callback),
105 page_count_(0),
106 waiting_for_page_ready_(true) {
107 content::WebContentsObserver::Observe(web_contents);
110 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
113 void DomDistillerViewerSource::RequestViewerHandle::SendJavaScript(
114 const std::string& buffer) {
115 if (waiting_for_page_ready_) {
116 buffer_ += buffer;
117 } else {
118 if (web_contents()) {
119 web_contents()->GetMainFrame()->ExecuteJavaScript(
120 base::UTF8ToUTF16(buffer));
125 void DomDistillerViewerSource::RequestViewerHandle::DidNavigateMainFrame(
126 const content::LoadCommittedDetails& details,
127 const content::FrameNavigateParams& params) {
128 const GURL& navigation = details.entry->GetURL();
129 if (details.is_in_page || (
130 navigation.SchemeIs(expected_scheme_.c_str()) &&
131 expected_request_path_ == navigation.query())) {
132 // In-page navigations, as well as the main view request can be ignored.
133 return;
136 Cancel();
140 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
141 base::TerminationStatus status) {
142 Cancel();
145 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
146 Cancel();
149 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
150 // No need to listen for notifications.
151 content::WebContentsObserver::Observe(NULL);
153 // Schedule the Viewer for deletion. Ensures distillation is cancelled, and
154 // any pending data stored in |buffer_| is released.
155 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
158 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
159 content::RenderFrameHost* render_frame_host,
160 const GURL& validated_url) {
161 if (render_frame_host->GetParent()) {
162 return;
164 waiting_for_page_ready_ = false;
165 if (buffer_.empty()) {
166 return;
168 web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_));
169 buffer_.clear();
172 void DomDistillerViewerSource::RequestViewerHandle::OnArticleReady(
173 const DistilledArticleProto* article_proto) {
174 if (page_count_ == 0) {
175 // This is a single-page article.
176 std::string unsafe_page_html = viewer::GetUnsafeArticleHtml(article_proto);
177 callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html));
178 } else if (page_count_ == article_proto->pages_size()) {
179 // We may still be showing the "Loading" indicator.
180 SendJavaScript(viewer::GetToggleLoadingIndicatorJs(true));
181 } else {
182 // It's possible that we didn't get some incremental updates from the
183 // distiller. Ensure all remaining pages are flushed to the viewer.
184 for (;page_count_ < article_proto->pages_size(); page_count_++) {
185 const DistilledPageProto& page = article_proto->pages(page_count_);
186 SendJavaScript(
187 viewer::GetUnsafeIncrementalDistilledPageJs(
188 &page,
189 page_count_ == article_proto->pages_size()));
192 // No need to hold on to the ViewerHandle now that distillation is complete.
193 viewer_handle_.reset();
196 void DomDistillerViewerSource::RequestViewerHandle::OnArticleUpdated(
197 ArticleDistillationUpdate article_update) {
198 for (;page_count_ < static_cast<int>(article_update.GetPagesSize());
199 page_count_++) {
200 const DistilledPageProto& page =
201 article_update.GetDistilledPage(page_count_);
202 if (page_count_ == 0) {
203 // This is the first page, so send Viewer page scaffolding too.
204 std::string unsafe_page_html = viewer::GetUnsafePartialArticleHtml(&page);
205 callback_.Run(base::RefCountedString::TakeString(&unsafe_page_html));
206 } else {
207 SendJavaScript(
208 viewer::GetUnsafeIncrementalDistilledPageJs(&page, false));
213 void DomDistillerViewerSource::RequestViewerHandle::TakeViewerHandle(
214 scoped_ptr<ViewerHandle> viewer_handle) {
215 viewer_handle_ = viewer_handle.Pass();
218 DomDistillerViewerSource::DomDistillerViewerSource(
219 DomDistillerServiceInterface* dom_distiller_service,
220 const std::string& scheme)
221 : scheme_(scheme), dom_distiller_service_(dom_distiller_service) {
224 DomDistillerViewerSource::~DomDistillerViewerSource() {
227 std::string DomDistillerViewerSource::GetSource() const {
228 return scheme_ + "://";
231 void DomDistillerViewerSource::StartDataRequest(
232 const std::string& path,
233 int render_process_id,
234 int render_frame_id,
235 const content::URLDataSource::GotDataCallback& callback) {
236 content::RenderFrameHost* render_frame_host =
237 content::RenderFrameHost::FromID(render_process_id, render_frame_id);
238 DCHECK(render_frame_host);
239 content::RenderViewHost* render_view_host =
240 render_frame_host->GetRenderViewHost();
241 DCHECK(render_view_host);
242 CHECK_EQ(0, render_view_host->GetEnabledBindings());
244 if (kViewerCssPath == path) {
245 std::string css = viewer::GetCss();
246 callback.Run(base::RefCountedString::TakeString(&css));
247 return;
249 if (kViewerJsPath == path) {
250 std::string js = viewer::GetJavaScript();
251 callback.Run(base::RefCountedString::TakeString(&js));
252 return;
254 content::WebContents* web_contents =
255 content::WebContents::FromRenderFrameHost(
256 content::RenderFrameHost::FromID(render_process_id,
257 render_frame_id));
258 DCHECK(web_contents);
259 // An empty |path| is invalid, but guard against it. If not empty, assume
260 // |path| starts with '?', which is stripped away.
261 const std::string path_after_query_separator =
262 path.size() > 0 ? path.substr(1) : "";
263 RequestViewerHandle* request_viewer_handle = new RequestViewerHandle(
264 web_contents, scheme_, path_after_query_separator, callback);
265 scoped_ptr<ViewerHandle> viewer_handle = viewer::CreateViewRequest(
266 dom_distiller_service_, path, request_viewer_handle);
268 if (viewer_handle) {
269 // The service returned a |ViewerHandle| and guarantees it will call
270 // the |RequestViewerHandle|, so passing ownership to it, to ensure the
271 // request is not cancelled. The |RequestViewerHandle| will delete itself
272 // after receiving the callback.
273 request_viewer_handle->TakeViewerHandle(viewer_handle.Pass());
274 } else {
275 // The service did not return a |ViewerHandle|, which means the
276 // |RequestViewerHandle| will never be called, so clean up now.
277 delete request_viewer_handle;
279 std::string error_page_html = viewer::GetErrorPageHtml();
280 callback.Run(base::RefCountedString::TakeString(&error_page_html));
284 std::string DomDistillerViewerSource::GetMimeType(
285 const std::string& path) const {
286 if (kViewerCssPath == path) {
287 return "text/css";
289 if (kViewerJsPath == path) {
290 return "text/javascript";
292 return "text/html";
295 bool DomDistillerViewerSource::ShouldServiceRequest(
296 const net::URLRequest* request) const {
297 return request->url().SchemeIs(scheme_.c_str());
300 // TODO(nyquist): Start tracking requests using this method.
301 void DomDistillerViewerSource::WillServiceRequest(
302 const net::URLRequest* request,
303 std::string* path) const {
306 std::string DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
307 const {
308 return "object-src 'none'; style-src 'self';";
311 } // namespace dom_distiller