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
;
117 // On iOS the content is inlined as there is no API to detect those requests
118 // and return the local data once a page is loaded.
119 css
<< "<style>" << viewer::GetCss() << viewer::GetIOSCss() << "</style>";
121 css
<< "<link rel=\"stylesheet\" href=\"/" << kViewerCssPath
<< "\">";
122 #endif // defined(OS_IOS)
124 substitutions
.push_back(
125 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_LOADING_TITLE
)); // $1
127 substitutions
.push_back(css
.str()); // $2
128 substitutions
.push_back(GetThemeCssClass(theme
) + " " +
129 GetFontCssClass(font_family
)); // $3
131 substitutions
.push_back(
132 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE
)); // $4
133 substitutions
.push_back(
134 l10n_util::GetStringUTF8(
135 IDS_DOM_DISTILLER_JAVASCRIPT_DISABLED_CONTENT
)); // $5
137 substitutions
.push_back(original_url
); // $6
138 substitutions
.push_back(
139 l10n_util::GetStringUTF8(
140 IDS_DOM_DISTILLER_VIEWER_CLOSE_READER_VIEW
)); // $7
142 return base::ReplaceStringPlaceholders(html_template
, substitutions
, NULL
);
149 const std::string
GetShowFeedbackFormJs() {
150 base::StringValue
question_val(
151 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_QUESTION
));
152 base::StringValue
no_val(
153 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_ANSWER_NO
));
154 base::StringValue
yes_val(
155 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_ANSWER_YES
));
157 std::string question
;
161 base::JSONWriter::Write(question_val
, &question
);
162 base::JSONWriter::Write(yes_val
, &yes
);
163 base::JSONWriter::Write(no_val
, &no
);
165 return "showFeedbackForm(" + question
+ ", " + yes
+ ", " + no
+ ");";
168 const std::string
GetUnsafeIncrementalDistilledPageJs(
169 const DistilledPageProto
* page_proto
,
170 const bool is_last_page
) {
171 std::string
output(page_proto
->html());
172 EnsureNonEmptyContent(&output
);
173 base::StringValue
value(output
);
174 base::JSONWriter::Write(value
, &output
);
175 std::string
page_update("addToPage(");
176 page_update
+= output
+ ");";
177 return page_update
+ GetToggleLoadingIndicatorJs(
182 const std::string
GetErrorPageJs() {
183 base::StringValue
value(l10n_util::GetStringUTF8(
184 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT
));
186 base::JSONWriter::Write(value
, &output
);
187 std::string
page_update("addToPage(");
188 page_update
+= output
+ ");";
192 const std::string
GetSetTitleJs(std::string title
) {
193 EnsureNonEmptyTitle(&title
);
194 base::StringValue
value(title
);
196 base::JSONWriter::Write(value
, &output
);
197 return "setTitle(" + output
+ ");";
200 const std::string
GetSetTextDirectionJs(const std::string
& direction
) {
201 base::StringValue
value(direction
);
203 base::JSONWriter::Write(value
, &output
);
204 return "setTextDirection(" + output
+ ");";
207 const std::string
GetToggleLoadingIndicatorJs(const bool is_last_page
) {
209 return "showLoadingIndicator(true);";
211 return "showLoadingIndicator(false);";
214 const std::string
GetUnsafeArticleTemplateHtml(
215 const std::string original_url
,
216 const DistilledPagePrefs::Theme theme
,
217 const DistilledPagePrefs::FontFamily font_family
) {
218 return ReplaceHtmlTemplateValues(original_url
, theme
, font_family
);
221 const std::string
GetUnsafeArticleContentJs(
222 const DistilledArticleProto
* article_proto
) {
223 DCHECK(article_proto
);
224 std::ostringstream unsafe_output_stream
;
225 if (article_proto
->pages_size() > 0 && article_proto
->pages(0).has_html()) {
226 for (int page_num
= 0; page_num
< article_proto
->pages_size(); ++page_num
) {
227 unsafe_output_stream
<< article_proto
->pages(page_num
).html();
231 std::string
output(unsafe_output_stream
.str());
232 EnsureNonEmptyContent(&output
);
233 base::JSONWriter::Write(base::StringValue(output
), &output
);
234 std::string
page_update("addToPage(");
235 page_update
+= output
+ ");";
236 return page_update
+ GetToggleLoadingIndicatorJs(true);
239 const std::string
GetCss() {
240 return ResourceBundle::GetSharedInstance().GetRawDataResource(
241 IDR_DISTILLER_CSS
).as_string();
244 const std::string
GetIOSCss() {
245 return ResourceBundle::GetSharedInstance().GetRawDataResource(
246 IDR_DISTILLER_IOS_CSS
).as_string();
249 const std::string
GetJavaScript() {
250 return ResourceBundle::GetSharedInstance()
251 .GetRawDataResource(IDR_DOM_DISTILLER_VIEWER_JS
)
255 scoped_ptr
<ViewerHandle
> CreateViewRequest(
256 DomDistillerServiceInterface
* dom_distiller_service
,
257 const std::string
& path
,
258 ViewRequestDelegate
* view_request_delegate
,
259 const gfx::Size
& render_view_size
) {
260 std::string entry_id
=
261 url_utils::GetValueForKeyInUrlPathQuery(path
, kEntryIdKey
);
262 bool has_valid_entry_id
= !entry_id
.empty();
263 entry_id
= base::ToUpperASCII(entry_id
);
265 std::string requested_url_str
=
266 url_utils::GetValueForKeyInUrlPathQuery(path
, kUrlKey
);
267 GURL
requested_url(requested_url_str
);
268 bool has_valid_url
= url_utils::IsUrlDistillable(requested_url
);
270 if (has_valid_entry_id
&& has_valid_url
) {
271 // It is invalid to specify a query param for both |kEntryIdKey| and
273 return scoped_ptr
<ViewerHandle
>();
276 if (has_valid_entry_id
) {
277 return dom_distiller_service
->ViewEntry(
278 view_request_delegate
,
279 dom_distiller_service
->CreateDefaultDistillerPage(render_view_size
),
281 } else if (has_valid_url
) {
282 return dom_distiller_service
->ViewUrl(
283 view_request_delegate
,
284 dom_distiller_service
->CreateDefaultDistillerPage(render_view_size
),
285 requested_url
).Pass();
288 // It is invalid to not specify a query param for |kEntryIdKey| or |kUrlKey|.
289 return scoped_ptr
<ViewerHandle
>();
292 const std::string
GetDistilledPageThemeJs(DistilledPagePrefs::Theme theme
) {
293 return "useTheme('" + GetJsTheme(theme
) + "');";
296 const std::string
GetDistilledPageFontFamilyJs(
297 DistilledPagePrefs::FontFamily font_family
) {
298 return "useFontFamily('" + GetJsFontFamily(font_family
) + "');";
301 } // namespace viewer
303 } // namespace dom_distiller