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/core/viewer.h" 6 7 #include <string> 8 #include <vector> 9 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" 27 #include "url/gurl.h" 28 29 namespace dom_distiller { 30 31 namespace { 32 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"; 37 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"; 42 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"; 47 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"; 52 53 // Maps themes to JS themes. 54 const std::string GetJsTheme(DistilledPagePrefs::Theme theme) { 55 if (theme == DistilledPagePrefs::DARK) { 56 return kDarkJsTheme; 57 } else if (theme == DistilledPagePrefs::SEPIA) { 58 return kSepiaJsTheme; 59 } 60 return kLightJsTheme; 61 } 62 63 // Maps themes to CSS classes. 64 const std::string GetThemeCssClass(DistilledPagePrefs::Theme theme) { 65 if (theme == DistilledPagePrefs::DARK) { 66 return kDarkCssClass; 67 } else if (theme == DistilledPagePrefs::SEPIA) { 68 return kSepiaCssClass; 69 } 70 return kLightCssClass; 71 } 72 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; 79 } 80 return kSansSerifJsFontFamily; 81 } 82 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; 89 } 90 return kSansSerifCssClass; 91 } 92 93 void EnsureNonEmptyTitleAndContent(std::string* title, std::string* content) { 94 if (title->empty()) 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); 100 } 101 } 102 103 std::string ReplaceHtmlTemplateValues( 104 const std::string& title, 105 const std::string& content, 106 const std::string& loading_indicator_class, 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; 114 substitutions.push_back(title); // $1 115 substitutions.push_back(kViewerCssPath); // $2 116 substitutions.push_back(kViewerJsPath); // $3 117 substitutions.push_back(GetThemeCssClass(theme) + " " + 118 GetFontCssClass(font_family)); // $4 119 substitutions.push_back(content); // $5 120 substitutions.push_back(loading_indicator_class); // $6 121 substitutions.push_back(original_url); // $7 122 substitutions.push_back( 123 l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_VIEW_ORIGINAL)); // $8 124 return ReplaceStringPlaceholders(html_template, substitutions, NULL); 125 } 126 127 } // namespace 128 129 namespace viewer { 130 131 const std::string GetUnsafeIncrementalDistilledPageJs( 132 const DistilledPageProto* page_proto, 133 const bool is_last_page) { 134 std::string output; 135 base::StringValue value(page_proto->html()); 136 base::JSONWriter::Write(&value, &output); 137 std::string page_update("addToPage("); 138 page_update += output + ");"; 139 return page_update + GetToggleLoadingIndicatorJs( 140 is_last_page); 141 142 } 143 144 const std::string GetToggleLoadingIndicatorJs(const bool is_last_page) { 145 if (is_last_page) 146 return "showLoadingIndicator(true);"; 147 else 148 return "showLoadingIndicator(false);"; 149 } 150 151 const std::string GetUnsafePartialArticleHtml( 152 const DistilledPageProto* page_proto, 153 const DistilledPagePrefs::Theme theme, 154 const DistilledPagePrefs::FontFamily font_family) { 155 DCHECK(page_proto); 156 std::string title = net::EscapeForHTML(page_proto->title()); 157 std::ostringstream unsafe_output_stream; 158 unsafe_output_stream << page_proto->html(); 159 std::string unsafe_article_html = unsafe_output_stream.str(); 160 EnsureNonEmptyTitleAndContent(&title, &unsafe_article_html); 161 std::string original_url = page_proto->url(); 162 return ReplaceHtmlTemplateValues( 163 title, unsafe_article_html, "visible", original_url, theme, font_family); 164 } 165 166 const std::string GetUnsafeArticleHtml( 167 const DistilledArticleProto* article_proto, 168 const DistilledPagePrefs::Theme theme, 169 const DistilledPagePrefs::FontFamily font_family) { 170 DCHECK(article_proto); 171 std::string title; 172 std::string unsafe_article_html; 173 if (article_proto->has_title() && article_proto->pages_size() > 0 && 174 article_proto->pages(0).has_html()) { 175 title = net::EscapeForHTML(article_proto->title()); 176 std::ostringstream unsafe_output_stream; 177 for (int page_num = 0; page_num < article_proto->pages_size(); ++page_num) { 178 unsafe_output_stream << article_proto->pages(page_num).html(); 179 } 180 unsafe_article_html = unsafe_output_stream.str(); 181 } 182 183 EnsureNonEmptyTitleAndContent(&title, &unsafe_article_html); 184 185 std::string original_url; 186 if (article_proto->pages_size() > 0 && article_proto->pages(0).has_url()) { 187 original_url = article_proto->pages(0).url(); 188 } 189 190 return ReplaceHtmlTemplateValues( 191 title, unsafe_article_html, "hidden", original_url, theme, font_family); 192 } 193 194 const std::string GetErrorPageHtml( 195 const DistilledPagePrefs::Theme theme, 196 const DistilledPagePrefs::FontFamily font_family) { 197 std::string title = l10n_util::GetStringUTF8( 198 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_TITLE); 199 std::string content = l10n_util::GetStringUTF8( 200 IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT); 201 return ReplaceHtmlTemplateValues( 202 title, content, "hidden", "", theme, font_family); 203 } 204 205 const std::string GetCss() { 206 return ResourceBundle::GetSharedInstance().GetRawDataResource( 207 IDR_DISTILLER_CSS).as_string(); 208 } 209 210 const std::string GetJavaScript() { 211 return ResourceBundle::GetSharedInstance() 212 .GetRawDataResource(IDR_DOM_DISTILLER_VIEWER_JS) 213 .as_string(); 214 } 215 216 scoped_ptr<ViewerHandle> CreateViewRequest( 217 DomDistillerServiceInterface* dom_distiller_service, 218 const std::string& path, 219 ViewRequestDelegate* view_request_delegate, 220 const gfx::Size& render_view_size) { 221 std::string entry_id = 222 url_utils::GetValueForKeyInUrlPathQuery(path, kEntryIdKey); 223 bool has_valid_entry_id = !entry_id.empty(); 224 entry_id = StringToUpperASCII(entry_id); 225 226 std::string requested_url_str = 227 url_utils::GetValueForKeyInUrlPathQuery(path, kUrlKey); 228 GURL requested_url(requested_url_str); 229 bool has_valid_url = url_utils::IsUrlDistillable(requested_url); 230 231 if (has_valid_entry_id && has_valid_url) { 232 // It is invalid to specify a query param for both |kEntryIdKey| and 233 // |kUrlKey|. 234 return scoped_ptr<ViewerHandle>(); 235 } 236 237 if (has_valid_entry_id) { 238 return dom_distiller_service->ViewEntry( 239 view_request_delegate, 240 dom_distiller_service->CreateDefaultDistillerPage(render_view_size), 241 entry_id).Pass(); 242 } else if (has_valid_url) { 243 return dom_distiller_service->ViewUrl( 244 view_request_delegate, 245 dom_distiller_service->CreateDefaultDistillerPage(render_view_size), 246 requested_url).Pass(); 247 } 248 249 // It is invalid to not specify a query param for |kEntryIdKey| or |kUrlKey|. 250 return scoped_ptr<ViewerHandle>(); 251 } 252 253 const std::string GetDistilledPageThemeJs(DistilledPagePrefs::Theme theme) { 254 return "useTheme('" + GetJsTheme(theme) + "');"; 255 } 256 257 const std::string GetDistilledPageFontFamilyJs( 258 DistilledPagePrefs::FontFamily font_family) { 259 return "useFontFamily('" + GetJsFontFamily(font_family) + "');"; 260 } 261 262 } // namespace viewer 263 264 } // namespace dom_distiller 265