dom_distiller_viewer_source_browsertest.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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 <string.h>
6
7#include "base/command_line.h"
8#include "base/guid.h"
9#include "chrome/browser/dom_distiller/dom_distiller_service_factory.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/ui/browser.h"
12#include "chrome/browser/ui/tabs/tab_strip_model.h"
13#include "chrome/common/chrome_switches.h"
14#include "chrome/common/url_constants.h"
15#include "chrome/test/base/in_process_browser_test.h"
16#include "chrome/test/base/ui_test_utils.h"
17#include "components/dom_distiller/content/dom_distiller_viewer_source.h"
18#include "components/dom_distiller/core/distiller.h"
19#include "components/dom_distiller/core/dom_distiller_service.h"
20#include "components/dom_distiller/core/dom_distiller_store.h"
21#include "components/dom_distiller/core/dom_distiller_test_util.h"
22#include "components/dom_distiller/core/fake_db.h"
23#include "components/dom_distiller/core/fake_distiller.h"
24#include "components/dom_distiller/core/task_tracker.h"
25#include "components/dom_distiller/core/url_utils.h"
26#include "content/public/browser/render_view_host.h"
27#include "content/public/browser/url_data_source.h"
28#include "content/public/browser/web_contents.h"
29#include "content/public/browser/web_contents_observer.h"
30#include "testing/gtest/include/gtest/gtest.h"
31
32namespace dom_distiller {
33
34using test::FakeDB;
35using test::FakeDistiller;
36using test::MockDistillerFactory;
37using test::util::CreateStoreWithFakeDB;
38
39namespace {
40
41void AddEntry(const ArticleEntry& e, FakeDB::EntryMap* map) {
42  (*map)[e.entry_id()] = e;
43}
44
45ArticleEntry CreateEntry(std::string entry_id, std::string page_url) {
46  ArticleEntry entry;
47  entry.set_entry_id(entry_id);
48  if (!page_url.empty()) {
49    ArticleEntryPage* page = entry.add_pages();
50    page->set_url(page_url);
51  }
52  return entry;
53}
54
55}  // namespace
56
57// WebContents observer that stores reference to the current |RenderViewHost|.
58class LoadSuccessObserver : public content::WebContentsObserver {
59 public:
60  explicit LoadSuccessObserver(content::WebContents* contents)
61      : content::WebContentsObserver(contents),
62        validated_url_(GURL()),
63        finished_load_(false),
64        load_failed_(false),
65        web_contents_(contents),
66        render_view_host_(NULL) {}
67
68  virtual void DidFinishLoad(int64 frame_id,
69                             const GURL& validated_url,
70                             bool is_main_frame,
71                             content::RenderViewHost* render_view_host)
72      OVERRIDE {
73    validated_url_ = validated_url;
74    finished_load_ = true;
75    render_view_host_ = render_view_host;
76  }
77
78  virtual void DidFailProvisionalLoad(int64 frame_id,
79                                      const base::string16& frame_unique_name,
80                                      bool is_main_frame,
81                                      const GURL& validated_url,
82                                      int error_code,
83                                      const base::string16& error_description,
84                                      content::RenderViewHost* render_view_host)
85      OVERRIDE {
86    load_failed_ = true;
87  }
88
89  const GURL& validated_url() const { return validated_url_; }
90  bool finished_load() const { return finished_load_; }
91  bool load_failed() const { return load_failed_; }
92  content::WebContents* web_contents() const { return web_contents_; }
93
94  const content::RenderViewHost* render_view_host() const {
95    return render_view_host_;
96  }
97
98 private:
99  GURL validated_url_;
100  bool finished_load_;
101  bool load_failed_;
102  content::WebContents* web_contents_;
103  content::RenderViewHost* render_view_host_;
104
105  DISALLOW_COPY_AND_ASSIGN(LoadSuccessObserver);
106};
107
108class DomDistillerViewerSourceBrowserTest : public InProcessBrowserTest {
109 public:
110  DomDistillerViewerSourceBrowserTest() {}
111  virtual ~DomDistillerViewerSourceBrowserTest() {}
112
113  virtual void SetUpOnMainThread() OVERRIDE {
114    database_model_ = new FakeDB::EntryMap;
115  }
116
117  virtual void CleanUpOnMainThread() OVERRIDE { delete database_model_; }
118
119  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
120    command_line->AppendSwitch(switches::kEnableDomDistiller);
121  }
122
123  static KeyedService* Build(content::BrowserContext* context) {
124    FakeDB* fake_db = new FakeDB(database_model_);
125    MockDistillerFactory* factory = new MockDistillerFactory();
126    DomDistillerContextKeyedService* service =
127        new DomDistillerContextKeyedService(
128            scoped_ptr<DomDistillerStoreInterface>(
129                CreateStoreWithFakeDB(fake_db, FakeDB::EntryMap())),
130            scoped_ptr<DistillerFactory>(factory));
131    fake_db->InitCallback(true);
132    fake_db->LoadCallback(true);
133    if (expect_distillation_) {
134      // There will only be destillation of an article if the database contains
135      // the article.
136      FakeDistiller* distiller = new FakeDistiller(true);
137      EXPECT_CALL(*factory, CreateDistillerImpl())
138          .WillOnce(testing::Return(distiller));
139    }
140    return service;
141  }
142
143  void ViewSingleDistilledPage(const GURL& url);
144
145  // Database entries.
146  static FakeDB::EntryMap* database_model_;
147  static bool expect_distillation_;
148};
149
150FakeDB::EntryMap* DomDistillerViewerSourceBrowserTest::database_model_;
151bool DomDistillerViewerSourceBrowserTest::expect_distillation_ = false;
152
153// The DomDistillerViewerSource renders untrusted content, so ensure no bindings
154// are enabled when the article exists in the database.
155// Flakiness: crbug.com/356866
156IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
157                       FLAKY_NoWebUIBindingsArticleExists) {
158  // Ensure there is one item in the database, which will trigger distillation.
159  const ArticleEntry entry = CreateEntry("DISTILLED", "http://example.com/1");
160  AddEntry(entry, database_model_);
161  expect_distillation_ = true;
162  const GURL url = url_utils::GetDistillerViewUrlFromEntryId(
163      chrome::kDomDistillerScheme, entry.entry_id());
164  ViewSingleDistilledPage(url);
165}
166
167// The DomDistillerViewerSource renders untrusted content, so ensure no bindings
168// are enabled when the article is not found.
169IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
170                       NoWebUIBindingsArticleNotFound) {
171  // The article does not exist, so assume no distillation will happen.
172  expect_distillation_ = false;
173  const GURL url(std::string(chrome::kDomDistillerScheme) + "://" +
174                 base::GenerateGUID() + "/");
175  ViewSingleDistilledPage(url);
176}
177
178// The DomDistillerViewerSource renders untrusted content, so ensure no bindings
179// are enabled when requesting to view an arbitrary URL.
180// Flakiness: crbug.com/356866
181IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
182                       FLAKY_NoWebUIBindingsViewUrl) {
183  // We should expect distillation for any valid URL.
184  expect_distillation_ = true;
185  GURL view_url("http://www.example.com/1");
186  const GURL url = url_utils::GetDistillerViewUrlFromUrl(
187      chrome::kDomDistillerScheme, view_url);
188  ViewSingleDistilledPage(url);
189}
190
191void DomDistillerViewerSourceBrowserTest::ViewSingleDistilledPage(
192    const GURL& url) {
193  // Ensure the correct factory is used for the DomDistillerService.
194  dom_distiller::DomDistillerServiceFactory::GetInstance()
195      ->SetTestingFactoryAndUse(browser()->profile(), &Build);
196
197  // Setup observer to inspect the RenderViewHost after committed navigation.
198  content::WebContents* contents =
199      browser()->tab_strip_model()->GetActiveWebContents();
200  LoadSuccessObserver observer(contents);
201
202  // Navigate to a URL which the source should respond to.
203  ui_test_utils::NavigateToURL(browser(), url);
204
205  // A navigation should have succeeded to the correct URL.
206  ASSERT_FALSE(observer.load_failed());
207  ASSERT_TRUE(observer.finished_load());
208  ASSERT_EQ(url, observer.validated_url());
209  // Ensure no bindings.
210  const content::RenderViewHost* render_view_host = observer.render_view_host();
211  ASSERT_EQ(0, render_view_host->GetEnabledBindings());
212  // The MIME-type should always be text/html for the distilled articles.
213  EXPECT_EQ("text/html", observer.web_contents()->GetContentsMimeType());
214}
215
216// The DomDistillerViewerSource renders untrusted content, so ensure no bindings
217// are enabled when the CSS resource is loaded. This CSS might be bundle with
218// Chrome or provided by an extension.
219IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
220                       NoWebUIBindingsDisplayCSS) {
221  // Setup observer to inspect the RenderViewHost after committed navigation.
222  content::WebContents* contents =
223      browser()->tab_strip_model()->GetActiveWebContents();
224  LoadSuccessObserver observer(contents);
225
226  // Navigate to a URL which the source should respond to with CSS.
227  std::string url_without_scheme = "://foobar/readability.css";
228  GURL url(chrome::kDomDistillerScheme + url_without_scheme);
229  ui_test_utils::NavigateToURL(browser(), url);
230
231  // A navigation should have succeeded to the correct URL.
232  ASSERT_FALSE(observer.load_failed());
233  ASSERT_TRUE(observer.finished_load());
234  ASSERT_EQ(url, observer.validated_url());
235  // Ensure no bindings.
236  const content::RenderViewHost* render_view_host = observer.render_view_host();
237  ASSERT_EQ(0, render_view_host->GetEnabledBindings());
238  // The MIME-type should always be text/css for the CSS resources.
239  EXPECT_EQ("text/css", observer.web_contents()->GetContentsMimeType());
240}
241
242}  // namespace dom_distiller
243