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/core/viewer.h"
10 #include "base/json/json_writer.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/strings/string_util.h"
14 #include "components/dom_distiller/core/distilled_page_prefs.h"
15 #include "components/dom_distiller/core/dom_distiller_service.h"
16 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
17 #include "components/dom_distiller/core/proto/distilled_page.pb.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/url_utils.h"
21 #include "grit/components_resources.h"
22 #include "grit/components_strings.h"
23 #include "net/base/escape.h"
24 #include "net/url_request/url_request.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "ui/base/resource/resource_bundle.h"
29 namespace dom_distiller
{
33 // JS Themes. Must agree with useTheme() in dom_distiller_viewer.js.
34 const char kDarkJsTheme
[] = "dark";
35 const char kLightJsTheme
[] = "light";
36 const char kSepiaJsTheme
[] = "sepia";
38 // CSS Theme classes. Must agree with classes in distilledpage.css.
39 const char kDarkCssClass
[] = "dark";
40 const char kLightCssClass
[] = "light";
41 const char kSepiaCssClass
[] = "sepia";
43 // JS FontFamilies. Must agree with useFontFamily() in dom_distiller_viewer.js.
44 const char kSerifJsFontFamily
[] = "serif";
45 const char kSansSerifJsFontFamily
[] = "sans-serif";
46 const char kMonospaceJsFontFamily
[] = "monospace";
48 // CSS FontFamily classes. Must agree with classes in distilledpage.css.
49 const char kSerifCssClass
[] = "serif";
50 const char kSansSerifCssClass
[] = "sans-serif";
51 const char kMonospaceCssClass
[] = "monospace";
53 // Maps themes to JS themes.
54 const std::string
GetJsTheme(DistilledPagePrefs::Theme theme
) {
55 if (theme
== DistilledPagePrefs::DARK
) {
57 } else if (theme
== DistilledPagePrefs::SEPIA
) {
63 // Maps themes to CSS classes.
64 const std::string
GetThemeCssClass(DistilledPagePrefs::Theme theme
) {
65 if (theme
== DistilledPagePrefs::DARK
) {
67 } else if (theme
== DistilledPagePrefs::SEPIA
) {
68 return kSepiaCssClass
;
70 return kLightCssClass
;
73 // Maps font families to JS font families.
74 const std::string
GetJsFontFamily(DistilledPagePrefs::FontFamily font_family
) {
75 if (font_family
== DistilledPagePrefs::SERIF
) {
76 return kSerifJsFontFamily
;
77 } else if (font_family
== DistilledPagePrefs::MONOSPACE
) {
78 return kMonospaceJsFontFamily
;
80 return kSansSerifJsFontFamily
;
83 // Maps fontFamilies to CSS fontFamily classes.
84 const std::string
GetFontCssClass(DistilledPagePrefs::FontFamily font_family
) {
85 if (font_family
== DistilledPagePrefs::SERIF
) {
86 return kSerifCssClass
;
87 } else if (font_family
== DistilledPagePrefs::MONOSPACE
) {
88 return kMonospaceCssClass
;
90 return kSansSerifCssClass
;
93 void EnsureNonEmptyTitle(std::string
* title
) {
95 *title
= l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE
);
98 void EnsureNonEmptyContent(std::string
* content
) {
99 UMA_HISTOGRAM_BOOLEAN("DomDistiller.PageHasDistilledData", !content
->empty());
100 if (content
->empty()) {
101 *content
= l10n_util::GetStringUTF8(
102 IDS_DOM_DISTILLER_VIEWER_NO_DATA_CONTENT
);
106 std::string
ReplaceHtmlTemplateValues(
107 const std::string
& original_url
,
108 const DistilledPagePrefs::Theme theme
,
109 const DistilledPagePrefs::FontFamily font_family
) {
110 base::StringPiece html_template
=
111 ResourceBundle::GetSharedInstance().GetRawDataResource(
112 IDR_DOM_DISTILLER_VIEWER_HTML
);
113 std::vector
<std::string
> substitutions
;
115 std::ostringstream css
;
116 std::ostringstream script
;
118 // On iOS the content is inlined as there is no API to detect those requests
119 // and return the local data once a page is loaded.
120 css
<< "<style>" << viewer::GetCss() << viewer::GetIOSCss() << "</style>";
121 script
<< "<script>\n" << viewer::GetJavaScript() << "\n</script>";
123 css
<< "<link rel=\"stylesheet\" href=\"/" << kViewerCssPath
<< "\">";
124 script
<< "<script src=\"" << kViewerJsPath
<< "\"></script>";
125 #endif // defined(OS_IOS)
127 substitutions
.push_back(
128 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_LOADING_TITLE
)); // $1
130 substitutions
.push_back(css
.str()); // $2
131 substitutions
.push_back(GetThemeCssClass(theme
) + " " +
132 GetFontCssClass(font_family
)); // $3
134 substitutions
.push_back(
135 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE
)); // $4
136 substitutions
.push_back(
137 l10n_util::GetStringUTF8(
138 IDS_DOM_DISTILLER_JAVASCRIPT_DISABLED_CONTENT
)); // $5
140 substitutions
.push_back(original_url
); // $6
141 substitutions
.push_back(
142 l10n_util::GetStringUTF8(
143 IDS_DOM_DISTILLER_VIEWER_CLOSE_READER_VIEW
)); // $7
145 substitutions
.push_back(script
.str()); // $8
147 return ReplaceStringPlaceholders(html_template
, substitutions
, NULL
);
154 const std::string
GetShowFeedbackFormJs() {
155 base::StringValue
question_val(
156 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_QUESTION
));
157 base::StringValue
no_val(
158 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_ANSWER_NO
));
159 base::StringValue
yes_val(
160 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_ANSWER_YES
));
162 std::string question
;
166 base::JSONWriter::Write(question_val
, &question
);
167 base::JSONWriter::Write(yes_val
, &yes
);
168 base::JSONWriter::Write(no_val
, &no
);
170 return "showFeedbackForm(" + question
+ ", " + yes
+ ", " + no
+ ");";
173 const std::string
GetUnsafeIncrementalDistilledPageJs(
174 const DistilledPageProto
* page_proto
,
175 const bool is_last_page
) {
176 std::string
output(page_proto
->html());
177 EnsureNonEmptyContent(&output
);
178 base::StringValue
value(output
);
179 base::JSONWriter::Write(value
, &output
);
180 std::string
page_update("addToPage(");
181 page_update
+= output
+ ");";
182 return page_update
+ GetToggleLoadingIndicatorJs(
187 const std::string
GetErrorPageJs() {
188 base::StringValue
value(l10n_util::GetStringUTF8(
189 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT
));
191 base::JSONWriter::Write(value
, &output
);
192 std::string
page_update("addToPage(");
193 page_update
+= output
+ ");";
197 const std::string
GetSetTitleJs(std::string title
) {
198 EnsureNonEmptyTitle(&title
);
199 base::StringValue
value(title
);
201 base::JSONWriter::Write(value
, &output
);
202 return "setTitle(" + output
+ ");";
205 const std::string
GetSetTextDirectionJs(const std::string
& direction
) {
206 base::StringValue
value(direction
);
208 base::JSONWriter::Write(value
, &output
);
209 return "setTextDirection(" + output
+ ");";
212 const std::string
GetToggleLoadingIndicatorJs(const bool is_last_page
) {
214 return "showLoadingIndicator(true);";
216 return "showLoadingIndicator(false);";
219 const std::string
GetUnsafeArticleTemplateHtml(
220 const std::string original_url
,
221 const DistilledPagePrefs::Theme theme
,
222 const DistilledPagePrefs::FontFamily font_family
) {
223 return ReplaceHtmlTemplateValues(original_url
, theme
, font_family
);
226 const std::string
GetUnsafeArticleContentJs(
227 const DistilledArticleProto
* article_proto
) {
228 DCHECK(article_proto
);
229 std::ostringstream unsafe_output_stream
;
230 if (article_proto
->pages_size() > 0 && article_proto
->pages(0).has_html()) {
231 for (int page_num
= 0; page_num
< article_proto
->pages_size(); ++page_num
) {
232 unsafe_output_stream
<< article_proto
->pages(page_num
).html();
236 std::string
output(unsafe_output_stream
.str());
237 EnsureNonEmptyContent(&output
);
238 base::JSONWriter::Write(base::StringValue(output
), &output
);
239 std::string
page_update("addToPage(");
240 page_update
+= output
+ ");";
241 return page_update
+ GetToggleLoadingIndicatorJs(true);
244 const std::string
GetCss() {
245 return ResourceBundle::GetSharedInstance().GetRawDataResource(
246 IDR_DISTILLER_CSS
).as_string();
249 const std::string
GetIOSCss() {
250 return ResourceBundle::GetSharedInstance().GetRawDataResource(
251 IDR_DISTILLER_IOS_CSS
).as_string();
254 const std::string
GetJavaScript() {
255 return ResourceBundle::GetSharedInstance()
256 .GetRawDataResource(IDR_DOM_DISTILLER_VIEWER_JS
)
260 scoped_ptr
<ViewerHandle
> CreateViewRequest(
261 DomDistillerServiceInterface
* dom_distiller_service
,
262 const std::string
& path
,
263 ViewRequestDelegate
* view_request_delegate
,
264 const gfx::Size
& render_view_size
) {
265 std::string entry_id
=
266 url_utils::GetValueForKeyInUrlPathQuery(path
, kEntryIdKey
);
267 bool has_valid_entry_id
= !entry_id
.empty();
268 entry_id
= base::StringToUpperASCII(entry_id
);
270 std::string requested_url_str
=
271 url_utils::GetValueForKeyInUrlPathQuery(path
, kUrlKey
);
272 GURL
requested_url(requested_url_str
);
273 bool has_valid_url
= url_utils::IsUrlDistillable(requested_url
);
275 if (has_valid_entry_id
&& has_valid_url
) {
276 // It is invalid to specify a query param for both |kEntryIdKey| and
278 return scoped_ptr
<ViewerHandle
>();
281 if (has_valid_entry_id
) {
282 return dom_distiller_service
->ViewEntry(
283 view_request_delegate
,
284 dom_distiller_service
->CreateDefaultDistillerPage(render_view_size
),
286 } else if (has_valid_url
) {
287 return dom_distiller_service
->ViewUrl(
288 view_request_delegate
,
289 dom_distiller_service
->CreateDefaultDistillerPage(render_view_size
),
290 requested_url
).Pass();
293 // It is invalid to not specify a query param for |kEntryIdKey| or |kUrlKey|.
294 return scoped_ptr
<ViewerHandle
>();
297 const std::string
GetDistilledPageThemeJs(DistilledPagePrefs::Theme theme
) {
298 return "useTheme('" + GetJsTheme(theme
) + "');";
301 const std::string
GetDistilledPageFontFamilyJs(
302 DistilledPagePrefs::FontFamily font_family
) {
303 return "useFontFamily('" + GetJsFontFamily(font_family
) + "');";
306 } // namespace viewer
308 } // namespace dom_distiller