Update To 11.40.268.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   ~RequestViewerHandle() override;
48
49   // ViewRequestDelegate implementation:
50   void OnArticleReady(const DistilledArticleProto* article_proto) override;
51
52   void OnArticleUpdated(ArticleDistillationUpdate article_update) override;
53
54   void TakeViewerHandle(scoped_ptr<ViewerHandle> viewer_handle);
55
56   // content::WebContentsObserver implementation:
57   void DidNavigateMainFrame(
58       const content::LoadCommittedDetails& details,
59       const content::FrameNavigateParams& params) override;
60   void RenderProcessGone(base::TerminationStatus status) override;
61   void WebContentsDestroyed() override;
62   void DidFinishLoad(content::RenderFrameHost* render_frame_host,
63                      const GURL& validated_url) override;
64
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);
69
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();
74
75   // DistilledPagePrefs::Observer implementation:
76   void OnChangeFontFamily(
77       DistilledPagePrefs::FontFamily new_font_family) override;
78   void OnChangeTheme(DistilledPagePrefs::Theme new_theme) override;
79
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_;
83
84   // The scheme hosting the current view request;
85   std::string expected_scheme_;
86
87   // The query path for the current view request.
88   std::string expected_request_path_;
89
90   // Holds the callback to where the data retrieved is sent back.
91   content::URLDataSource::GotDataCallback callback_;
92
93   // Number of pages of the distilled article content that have been rendered by
94   // the viewer.
95   int page_count_;
96
97   // Interface for accessing preferences for distilled pages.
98   DistilledPagePrefs* distilled_page_prefs_;
99
100   // Whether the page is sufficiently initialized to handle updates from the
101   // distiller.
102   bool waiting_for_page_ready_;
103
104   // Temporary store of pending JavaScript if the page isn't ready to receive
105   // data from distillation.
106   std::string buffer_;
107 };
108
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);
123 }
124
125 DomDistillerViewerSource::RequestViewerHandle::~RequestViewerHandle() {
126   distilled_page_prefs_->RemoveObserver(this);
127 }
128
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));
137     }
138   }
139 }
140
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;
150   }
151
152   Cancel();
153
154 }
155
156 void DomDistillerViewerSource::RequestViewerHandle::RenderProcessGone(
157     base::TerminationStatus status) {
158   Cancel();
159 }
160
161 void DomDistillerViewerSource::RequestViewerHandle::WebContentsDestroyed() {
162   Cancel();
163 }
164
165 void DomDistillerViewerSource::RequestViewerHandle::Cancel() {
166   // No need to listen for notifications.
167   content::WebContentsObserver::Observe(NULL);
168
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);
172 }
173
174 void DomDistillerViewerSource::RequestViewerHandle::DidFinishLoad(
175     content::RenderFrameHost* render_frame_host,
176     const GURL& validated_url) {
177   if (render_frame_host->GetParent()) {
178     return;
179   }
180   waiting_for_page_ready_ = false;
181   if (buffer_.empty()) {
182     return;
183   }
184   web_contents()->GetMainFrame()->ExecuteJavaScript(base::UTF8ToUTF16(buffer_));
185   buffer_.clear();
186 }
187
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()));
210     }
211   }
212   // No need to hold on to the ViewerHandle now that distillation is complete.
213   viewer_handle_.reset();
214 }
215
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));
232     }
233   }
234 }
235
236 void DomDistillerViewerSource::RequestViewerHandle::TakeViewerHandle(
237     scoped_ptr<ViewerHandle> viewer_handle) {
238   viewer_handle_ = viewer_handle.Pass();
239 }
240
241 void DomDistillerViewerSource::RequestViewerHandle::OnChangeTheme(
242     DistilledPagePrefs::Theme new_theme) {
243   SendJavaScript(viewer::GetDistilledPageThemeJs(new_theme));
244 }
245
246 void DomDistillerViewerSource::RequestViewerHandle::OnChangeFontFamily(
247     DistilledPagePrefs::FontFamily new_font) {
248   SendJavaScript(viewer::GetDistilledPageFontFamilyJs(new_font));
249 }
250
251 DomDistillerViewerSource::DomDistillerViewerSource(
252     DomDistillerServiceInterface* dom_distiller_service,
253     const std::string& scheme)
254     : scheme_(scheme), dom_distiller_service_(dom_distiller_service) {
255 }
256
257 DomDistillerViewerSource::~DomDistillerViewerSource() {
258 }
259
260 std::string DomDistillerViewerSource::GetSource() const {
261   return scheme_ + "://";
262 }
263
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   if (!render_frame_host) return;
272   content::RenderViewHost* render_view_host =
273       render_frame_host->GetRenderViewHost();
274   DCHECK(render_view_host);
275   CHECK_EQ(0, render_view_host->GetEnabledBindings());
276
277   if (kViewerCssPath == path) {
278     std::string css = viewer::GetCss();
279     callback.Run(base::RefCountedString::TakeString(&css));
280     return;
281   }
282   if (kViewerJsPath == path) {
283     std::string js = viewer::GetJavaScript();
284     callback.Run(base::RefCountedString::TakeString(&js));
285     return;
286   }
287   if (kViewerViewOriginalPath == path) {
288     content::RecordAction(base::UserMetricsAction("DomDistiller_ViewOriginal"));
289     callback.Run(NULL);
290     return;
291   }
292   content::WebContents* web_contents =
293       content::WebContents::FromRenderFrameHost(render_frame_host);
294   DCHECK(web_contents);
295   // An empty |path| is invalid, but guard against it. If not empty, assume
296   // |path| starts with '?', which is stripped away.
297   const std::string path_after_query_separator =
298       path.size() > 0 ? path.substr(1) : "";
299   RequestViewerHandle* request_viewer_handle = new RequestViewerHandle(
300       web_contents, scheme_, path_after_query_separator, callback,
301       dom_distiller_service_->GetDistilledPagePrefs());
302   scoped_ptr<ViewerHandle> viewer_handle = viewer::CreateViewRequest(
303       dom_distiller_service_, path, request_viewer_handle,
304       web_contents->GetContainerBounds().size());
305
306   if (viewer_handle) {
307     // The service returned a |ViewerHandle| and guarantees it will call
308     // the |RequestViewerHandle|, so passing ownership to it, to ensure the
309     // request is not cancelled. The |RequestViewerHandle| will delete itself
310     // after receiving the callback.
311     request_viewer_handle->TakeViewerHandle(viewer_handle.Pass());
312   } else {
313     // The service did not return a |ViewerHandle|, which means the
314     // |RequestViewerHandle| will never be called, so clean up now.
315     delete request_viewer_handle;
316
317     std::string error_page_html = viewer::GetErrorPageHtml(
318         dom_distiller_service_->GetDistilledPagePrefs()->GetTheme(),
319         dom_distiller_service_->GetDistilledPagePrefs()->GetFontFamily());
320     callback.Run(base::RefCountedString::TakeString(&error_page_html));
321   }
322 };
323
324 std::string DomDistillerViewerSource::GetMimeType(
325     const std::string& path) const {
326   if (kViewerCssPath == path) {
327     return "text/css";
328   }
329   if (kViewerJsPath == path) {
330     return "text/javascript";
331   }
332   return "text/html";
333 }
334
335 bool DomDistillerViewerSource::ShouldServiceRequest(
336     const net::URLRequest* request) const {
337   return request->url().SchemeIs(scheme_.c_str());
338 }
339
340 // TODO(nyquist): Start tracking requests using this method.
341 void DomDistillerViewerSource::WillServiceRequest(
342     const net::URLRequest* request,
343     std::string* path) const {
344 }
345
346 std::string DomDistillerViewerSource::GetContentSecurityPolicyObjectSrc()
347     const {
348   return "object-src 'none'; style-src 'self' https://fonts.googleapis.com;";
349 }
350
351 }  // namespace dom_distiller