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.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 EnsureNonEmptyTitleAndContent(std::string
* title
, std::string
* content
) {
95 *title
= l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE
);
96 UMA_HISTOGRAM_BOOLEAN("DomDistiller.PageHasDistilledData", !content
->empty());
97 if (content
->empty()) {
98 *content
= l10n_util::GetStringUTF8(
99 IDS_DOM_DISTILLER_VIEWER_NO_DATA_CONTENT
);
103 std::string
ReplaceHtmlTemplateValues(
104 const std::string
& title
,
105 const std::string
& textDirection
,
106 const std::string
& content
,
107 const std::string
& loading_indicator_class
,
108 const std::string
& original_url
,
109 const DistilledPagePrefs::Theme theme
,
110 const DistilledPagePrefs::FontFamily font_family
) {
111 base::StringPiece html_template
=
112 ResourceBundle::GetSharedInstance().GetRawDataResource(
113 IDR_DOM_DISTILLER_VIEWER_HTML
);
114 std::vector
<std::string
> substitutions
;
115 substitutions
.push_back(title
); // $1
116 substitutions
.push_back(kViewerCssPath
); // $2
117 substitutions
.push_back(kViewerJsPath
); // $3
118 substitutions
.push_back(GetThemeCssClass(theme
) + " " +
119 GetFontCssClass(font_family
)); // $4
120 substitutions
.push_back(content
); // $5
121 substitutions
.push_back(loading_indicator_class
); // $6
122 substitutions
.push_back(original_url
); // $7
123 substitutions
.push_back(
124 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL
)); // $8
125 substitutions
.push_back(textDirection
); // $9
126 return ReplaceStringPlaceholders(html_template
, substitutions
, NULL
);
133 const std::string
GetUnsafeIncrementalDistilledPageJs(
134 const DistilledPageProto
* page_proto
,
135 const bool is_last_page
) {
137 base::StringValue
value(page_proto
->html());
138 base::JSONWriter::Write(&value
, &output
);
139 std::string
page_update("addToPage(");
140 page_update
+= output
+ ");";
141 return page_update
+ GetToggleLoadingIndicatorJs(
146 const std::string
GetToggleLoadingIndicatorJs(const bool is_last_page
) {
148 return "showLoadingIndicator(true);";
150 return "showLoadingIndicator(false);";
153 const std::string
GetUnsafePartialArticleHtml(
154 const DistilledPageProto
* page_proto
,
155 const DistilledPagePrefs::Theme theme
,
156 const DistilledPagePrefs::FontFamily font_family
) {
158 std::string title
= net::EscapeForHTML(page_proto
->title());
159 std::ostringstream unsafe_output_stream
;
160 unsafe_output_stream
<< page_proto
->html();
161 std::string unsafe_article_html
= unsafe_output_stream
.str();
162 EnsureNonEmptyTitleAndContent(&title
, &unsafe_article_html
);
163 std::string original_url
= page_proto
->url();
164 return ReplaceHtmlTemplateValues(
165 title
, page_proto
->text_direction(), unsafe_article_html
, "visible",
166 original_url
, theme
, font_family
);
169 const std::string
GetUnsafeArticleHtml(
170 const DistilledArticleProto
* article_proto
,
171 const DistilledPagePrefs::Theme theme
,
172 const DistilledPagePrefs::FontFamily font_family
) {
173 DCHECK(article_proto
);
175 std::string unsafe_article_html
;
176 std::string text_direction
= "";
177 if (article_proto
->has_title() && article_proto
->pages_size() > 0 &&
178 article_proto
->pages(0).has_html()) {
179 title
= net::EscapeForHTML(article_proto
->title());
180 std::ostringstream unsafe_output_stream
;
181 for (int page_num
= 0; page_num
< article_proto
->pages_size(); ++page_num
) {
182 unsafe_output_stream
<< article_proto
->pages(page_num
).html();
184 unsafe_article_html
= unsafe_output_stream
.str();
185 text_direction
= article_proto
->pages(0).text_direction();
188 EnsureNonEmptyTitleAndContent(&title
, &unsafe_article_html
);
190 std::string original_url
;
191 if (article_proto
->pages_size() > 0 && article_proto
->pages(0).has_url()) {
192 original_url
= article_proto
->pages(0).url();
195 return ReplaceHtmlTemplateValues(
196 title
, text_direction
, unsafe_article_html
, "hidden", original_url
,
200 const std::string
GetErrorPageHtml(
201 const DistilledPagePrefs::Theme theme
,
202 const DistilledPagePrefs::FontFamily font_family
) {
203 std::string title
= l10n_util::GetStringUTF8(
204 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_TITLE
);
205 std::string content
= l10n_util::GetStringUTF8(
206 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT
);
207 return ReplaceHtmlTemplateValues(
208 title
, "", content
, "hidden", "", theme
, font_family
);
211 const std::string
GetCss() {
212 return ResourceBundle::GetSharedInstance().GetRawDataResource(
213 IDR_DISTILLER_CSS
).as_string();
216 const std::string
GetJavaScript() {
217 return ResourceBundle::GetSharedInstance()
218 .GetRawDataResource(IDR_DOM_DISTILLER_VIEWER_JS
)
222 scoped_ptr
<ViewerHandle
> CreateViewRequest(
223 DomDistillerServiceInterface
* dom_distiller_service
,
224 const std::string
& path
,
225 ViewRequestDelegate
* view_request_delegate
,
226 const gfx::Size
& render_view_size
) {
227 std::string entry_id
=
228 url_utils::GetValueForKeyInUrlPathQuery(path
, kEntryIdKey
);
229 bool has_valid_entry_id
= !entry_id
.empty();
230 entry_id
= StringToUpperASCII(entry_id
);
232 std::string requested_url_str
=
233 url_utils::GetValueForKeyInUrlPathQuery(path
, kUrlKey
);
234 GURL
requested_url(requested_url_str
);
235 bool has_valid_url
= url_utils::IsUrlDistillable(requested_url
);
237 if (has_valid_entry_id
&& has_valid_url
) {
238 // It is invalid to specify a query param for both |kEntryIdKey| and
240 return scoped_ptr
<ViewerHandle
>();
243 if (has_valid_entry_id
) {
244 return dom_distiller_service
->ViewEntry(
245 view_request_delegate
,
246 dom_distiller_service
->CreateDefaultDistillerPage(render_view_size
),
248 } else if (has_valid_url
) {
249 return dom_distiller_service
->ViewUrl(
250 view_request_delegate
,
251 dom_distiller_service
->CreateDefaultDistillerPage(render_view_size
),
252 requested_url
).Pass();
255 // It is invalid to not specify a query param for |kEntryIdKey| or |kUrlKey|.
256 return scoped_ptr
<ViewerHandle
>();
259 const std::string
GetDistilledPageThemeJs(DistilledPagePrefs::Theme theme
) {
260 return "useTheme('" + GetJsTheme(theme
) + "');";
263 const std::string
GetDistilledPageFontFamilyJs(
264 DistilledPagePrefs::FontFamily font_family
) {
265 return "useFontFamily('" + GetJsFontFamily(font_family
) + "');";
268 } // namespace viewer
270 } // namespace dom_distiller