Home | History | Annotate | Download | only in core
      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