// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/dom_distiller/core/viewer.h" #include #include #include "base/json/json_writer.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string_util.h" #include "components/dom_distiller/core/distilled_page_prefs.h" #include "components/dom_distiller/core/dom_distiller_service.h" #include "components/dom_distiller/core/proto/distilled_article.pb.h" #include "components/dom_distiller/core/proto/distilled_page.pb.h" #include "components/dom_distiller/core/task_tracker.h" #include "components/dom_distiller/core/url_constants.h" #include "components/dom_distiller/core/url_utils.h" #include "grit/components_resources.h" #include "grit/components_strings.h" #include "net/base/escape.h" #include "net/url_request/url_request.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "url/gurl.h" namespace dom_distiller { namespace { // JS Themes. Must agree with useTheme() in dom_distiller_viewer.js. const char kDarkJsTheme[] = "dark"; const char kLightJsTheme[] = "light"; const char kSepiaJsTheme[] = "sepia"; // CSS Theme classes. Must agree with classes in distilledpage.css. const char kDarkCssClass[] = "dark"; const char kLightCssClass[] = "light"; const char kSepiaCssClass[] = "sepia"; // JS FontFamilies. Must agree with useFontFamily() in dom_distiller_viewer.js. const char kSerifJsFontFamily[] = "serif"; const char kSansSerifJsFontFamily[] = "sans-serif"; const char kMonospaceJsFontFamily[] = "monospace"; // CSS FontFamily classes. Must agree with classes in distilledpage.css. const char kSerifCssClass[] = "serif"; const char kSansSerifCssClass[] = "sans-serif"; const char kMonospaceCssClass[] = "monospace"; // Maps themes to JS themes. const std::string GetJsTheme(DistilledPagePrefs::Theme theme) { if (theme == DistilledPagePrefs::DARK) { return kDarkJsTheme; } else if (theme == DistilledPagePrefs::SEPIA) { return kSepiaJsTheme; } return kLightJsTheme; } // Maps themes to CSS classes. const std::string GetThemeCssClass(DistilledPagePrefs::Theme theme) { if (theme == DistilledPagePrefs::DARK) { return kDarkCssClass; } else if (theme == DistilledPagePrefs::SEPIA) { return kSepiaCssClass; } return kLightCssClass; } // Maps font families to JS font families. const std::string GetJsFontFamily(DistilledPagePrefs::FontFamily font_family) { if (font_family == DistilledPagePrefs::SERIF) { return kSerifJsFontFamily; } else if (font_family == DistilledPagePrefs::MONOSPACE) { return kMonospaceJsFontFamily; } return kSansSerifJsFontFamily; } // Maps fontFamilies to CSS fontFamily classes. const std::string GetFontCssClass(DistilledPagePrefs::FontFamily font_family) { if (font_family == DistilledPagePrefs::SERIF) { return kSerifCssClass; } else if (font_family == DistilledPagePrefs::MONOSPACE) { return kMonospaceCssClass; } return kSansSerifCssClass; } void EnsureNonEmptyTitle(std::string* title) { if (title->empty()) *title = l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE); } void EnsureNonEmptyContent(std::string* content) { UMA_HISTOGRAM_BOOLEAN("DomDistiller.PageHasDistilledData", !content->empty()); if (content->empty()) { *content = l10n_util::GetStringUTF8( IDS_DOM_DISTILLER_VIEWER_NO_DATA_CONTENT); } } std::string ReplaceHtmlTemplateValues( const std::string& original_url, const DistilledPagePrefs::Theme theme, const DistilledPagePrefs::FontFamily font_family) { base::StringPiece html_template = ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_DOM_DISTILLER_VIEWER_HTML); std::vector substitutions; std::ostringstream css; #if defined(OS_IOS) // On iOS the content is inlined as there is no API to detect those requests // and return the local data once a page is loaded. css << ""; #else css << ""; #endif // defined(OS_IOS) substitutions.push_back( l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_LOADING_TITLE)); // $1 substitutions.push_back(css.str()); // $2 substitutions.push_back(GetThemeCssClass(theme) + " " + GetFontCssClass(font_family)); // $3 substitutions.push_back( l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_VIEWER_NO_DATA_TITLE)); // $4 substitutions.push_back( l10n_util::GetStringUTF8( IDS_DOM_DISTILLER_JAVASCRIPT_DISABLED_CONTENT)); // $5 substitutions.push_back(original_url); // $6 substitutions.push_back( l10n_util::GetStringUTF8( IDS_DOM_DISTILLER_VIEWER_CLOSE_READER_VIEW)); // $7 return base::ReplaceStringPlaceholders(html_template, substitutions, NULL); } } // namespace namespace viewer { const std::string GetShowFeedbackFormJs() { base::StringValue question_val( l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_QUESTION)); base::StringValue no_val( l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_ANSWER_NO)); base::StringValue yes_val( l10n_util::GetStringUTF8(IDS_DOM_DISTILLER_QUALITY_ANSWER_YES)); std::string question; std::string yes; std::string no; base::JSONWriter::Write(question_val, &question); base::JSONWriter::Write(yes_val, &yes); base::JSONWriter::Write(no_val, &no); return "showFeedbackForm(" + question + ", " + yes + ", " + no + ");"; } const std::string GetUnsafeIncrementalDistilledPageJs( const DistilledPageProto* page_proto, const bool is_last_page) { std::string output(page_proto->html()); EnsureNonEmptyContent(&output); base::StringValue value(output); base::JSONWriter::Write(value, &output); std::string page_update("addToPage("); page_update += output + ");"; return page_update + GetToggleLoadingIndicatorJs( is_last_page); } const std::string GetErrorPageJs() { base::StringValue value(l10n_util::GetStringUTF8( IDS_DOM_DISTILLER_VIEWER_FAILED_TO_FIND_ARTICLE_CONTENT)); std::string output; base::JSONWriter::Write(value, &output); std::string page_update("addToPage("); page_update += output + ");"; return page_update; } const std::string GetSetTitleJs(std::string title) { EnsureNonEmptyTitle(&title); base::StringValue value(title); std::string output; base::JSONWriter::Write(value, &output); return "setTitle(" + output + ");"; } const std::string GetSetTextDirectionJs(const std::string& direction) { base::StringValue value(direction); std::string output; base::JSONWriter::Write(value, &output); return "setTextDirection(" + output + ");"; } const std::string GetToggleLoadingIndicatorJs(const bool is_last_page) { if (is_last_page) return "showLoadingIndicator(true);"; else return "showLoadingIndicator(false);"; } const std::string GetUnsafeArticleTemplateHtml( const std::string original_url, const DistilledPagePrefs::Theme theme, const DistilledPagePrefs::FontFamily font_family) { return ReplaceHtmlTemplateValues(original_url, theme, font_family); } const std::string GetUnsafeArticleContentJs( const DistilledArticleProto* article_proto) { DCHECK(article_proto); std::ostringstream unsafe_output_stream; if (article_proto->pages_size() > 0 && article_proto->pages(0).has_html()) { for (int page_num = 0; page_num < article_proto->pages_size(); ++page_num) { unsafe_output_stream << article_proto->pages(page_num).html(); } } std::string output(unsafe_output_stream.str()); EnsureNonEmptyContent(&output); base::JSONWriter::Write(base::StringValue(output), &output); std::string page_update("addToPage("); page_update += output + ");"; return page_update + GetToggleLoadingIndicatorJs(true); } const std::string GetCss() { return ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_DISTILLER_CSS).as_string(); } const std::string GetIOSCss() { return ResourceBundle::GetSharedInstance().GetRawDataResource( IDR_DISTILLER_IOS_CSS).as_string(); } const std::string GetJavaScript() { return ResourceBundle::GetSharedInstance() .GetRawDataResource(IDR_DOM_DISTILLER_VIEWER_JS) .as_string(); } scoped_ptr CreateViewRequest( DomDistillerServiceInterface* dom_distiller_service, const std::string& path, ViewRequestDelegate* view_request_delegate, const gfx::Size& render_view_size) { std::string entry_id = url_utils::GetValueForKeyInUrlPathQuery(path, kEntryIdKey); bool has_valid_entry_id = !entry_id.empty(); entry_id = base::StringToUpperASCII(entry_id); std::string requested_url_str = url_utils::GetValueForKeyInUrlPathQuery(path, kUrlKey); GURL requested_url(requested_url_str); bool has_valid_url = url_utils::IsUrlDistillable(requested_url); if (has_valid_entry_id && has_valid_url) { // It is invalid to specify a query param for both |kEntryIdKey| and // |kUrlKey|. return scoped_ptr(); } if (has_valid_entry_id) { return dom_distiller_service->ViewEntry( view_request_delegate, dom_distiller_service->CreateDefaultDistillerPage(render_view_size), entry_id).Pass(); } else if (has_valid_url) { return dom_distiller_service->ViewUrl( view_request_delegate, dom_distiller_service->CreateDefaultDistillerPage(render_view_size), requested_url).Pass(); } // It is invalid to not specify a query param for |kEntryIdKey| or |kUrlKey|. return scoped_ptr(); } const std::string GetDistilledPageThemeJs(DistilledPagePrefs::Theme theme) { return "useTheme('" + GetJsTheme(theme) + "');"; } const std::string GetDistilledPageFontFamilyJs( DistilledPagePrefs::FontFamily font_family) { return "useFontFamily('" + GetJsFontFamily(font_family) + "');"; } } // namespace viewer } // namespace dom_distiller