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