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 "content/public/browser/host_zoom_map.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/bind.h"
12#include "base/files/file_path.h"
13#include "base/files/file_util.h"
14#include "base/memory/ref_counted.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/path_service.h"
17#include "base/prefs/pref_service.h"
18#include "base/strings/stringprintf.h"
19#include "base/values.h"
20#include "chrome/browser/chrome_page_zoom.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/tabs/tab_strip_model.h"
24#include "chrome/common/chrome_constants.h"
25#include "chrome/common/chrome_paths.h"
26#include "chrome/common/pref_names.h"
27#include "chrome/test/base/in_process_browser_test.h"
28#include "chrome/test/base/testing_profile.h"
29#include "chrome/test/base/ui_test_utils.h"
30#include "content/public/test/test_utils.h"
31#include "net/dns/mock_host_resolver.h"
32#include "net/test/embedded_test_server/embedded_test_server.h"
33#include "net/test/embedded_test_server/http_response.h"
34#include "testing/gmock/include/gmock/gmock.h"
35#include "url/gurl.h"
36
37namespace {
38
39class ZoomLevelChangeObserver {
40 public:
41  explicit ZoomLevelChangeObserver(Profile* profile)
42      : message_loop_runner_(new content::MessageLoopRunner) {
43    content::HostZoomMap* host_zoom_map = static_cast<content::HostZoomMap*>(
44        content::HostZoomMap::GetDefaultForBrowserContext(profile));
45    subscription_ = host_zoom_map->AddZoomLevelChangedCallback(base::Bind(
46        &ZoomLevelChangeObserver::OnZoomLevelChanged, base::Unretained(this)));
47  }
48
49  void BlockUntilZoomLevelForHostHasChanged(const std::string& host) {
50    while (!std::count(changed_hosts_.begin(), changed_hosts_.end(), host)) {
51      message_loop_runner_->Run();
52      message_loop_runner_ = new content::MessageLoopRunner;
53    }
54    changed_hosts_.clear();
55  }
56
57 private:
58  void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change) {
59    changed_hosts_.push_back(change.host);
60    message_loop_runner_->Quit();
61  }
62
63  scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
64  std::vector<std::string> changed_hosts_;
65  scoped_ptr<content::HostZoomMap::Subscription> subscription_;
66
67  DISALLOW_COPY_AND_ASSIGN(ZoomLevelChangeObserver);
68};
69
70}  // namespace
71
72class HostZoomMapBrowserTest : public InProcessBrowserTest {
73 public:
74  HostZoomMapBrowserTest() {}
75
76 protected:
77  void SetDefaultZoomLevel(double level) {
78    browser()->profile()->GetPrefs()->SetDouble(
79        prefs::kDefaultZoomLevel, level);
80  }
81
82  double GetZoomLevel(const GURL& url) {
83    content::HostZoomMap* host_zoom_map = static_cast<content::HostZoomMap*>(
84        content::HostZoomMap::GetDefaultForBrowserContext(
85            browser()->profile()));
86    return host_zoom_map->GetZoomLevelForHostAndScheme(url.scheme(),
87                                                       url.host());
88  }
89
90  std::vector<std::string> GetHostsWithZoomLevels() {
91    typedef content::HostZoomMap::ZoomLevelVector ZoomLevelVector;
92    content::HostZoomMap* host_zoom_map = static_cast<content::HostZoomMap*>(
93        content::HostZoomMap::GetDefaultForBrowserContext(
94            browser()->profile()));
95    content::HostZoomMap::ZoomLevelVector zoom_levels =
96        host_zoom_map->GetAllZoomLevels();
97    std::vector<std::string> results;
98    for (ZoomLevelVector::const_iterator it = zoom_levels.begin();
99         it != zoom_levels.end(); ++it)
100      results.push_back(it->host);
101    return results;
102  }
103
104  std::vector<std::string> GetHostsWithZoomLevelsFromPrefs() {
105    PrefService* prefs = browser()->profile()->GetPrefs();
106    const base::DictionaryValue* values =
107        prefs->GetDictionary(prefs::kPerHostZoomLevels);
108    std::vector<std::string> results;
109    if (values) {
110      for (base::DictionaryValue::Iterator it(*values);
111           !it.IsAtEnd(); it.Advance())
112        results.push_back(it.key());
113    }
114    return results;
115  }
116
117  GURL ConstructTestServerURL(const char* url_template) {
118    return GURL(base::StringPrintf(
119        url_template, embedded_test_server()->port()));
120  }
121
122 private:
123  scoped_ptr<net::test_server::HttpResponse> HandleRequest(
124      const net::test_server::HttpRequest& request) {
125    return scoped_ptr<net::test_server::HttpResponse>(
126        new net::test_server::BasicHttpResponse);
127  }
128
129  // BrowserTestBase:
130  virtual void SetUpOnMainThread() OVERRIDE {
131    ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
132    embedded_test_server()->RegisterRequestHandler(base::Bind(
133        &HostZoomMapBrowserTest::HandleRequest, base::Unretained(this)));
134    host_resolver()->AddRule("*", "127.0.0.1");
135  }
136
137  DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTest);
138};
139
140class HostZoomMapSanitizationBrowserTest : public HostZoomMapBrowserTest {
141 public:
142  HostZoomMapSanitizationBrowserTest() {}
143
144 private:
145  // InProcessBrowserTest:
146  virtual bool SetUpUserDataDirectory() OVERRIDE {
147    // Zoom-related preferences demonstrating the two problems that could be
148    // caused by the bug. They incorrectly contain a per-host zoom level for the
149    // empty host; and a value for 'host1' that only differs from the default by
150    // epsilon. Neither should have been persisted.
151    const char kBrokenPrefs[] =
152        "{'profile': {"
153        "   'default_zoom_level': 1.2,"
154        "   'per_host_zoom_levels': {'': 1.1, 'host1': 1.20001, 'host2': 1.3}"
155        "}}";
156    std::string broken_prefs(kBrokenPrefs);
157    std::replace(broken_prefs.begin(), broken_prefs.end(), '\'', '\"');
158
159    base::FilePath user_data_directory, path_to_prefs;
160    PathService::Get(chrome::DIR_USER_DATA, &user_data_directory);
161    path_to_prefs = user_data_directory
162        .AppendASCII(TestingProfile::kTestUserProfileDir)
163        .Append(chrome::kPreferencesFilename);
164    base::CreateDirectory(path_to_prefs.DirName());
165    base::WriteFile(path_to_prefs, broken_prefs.c_str(), broken_prefs.size());
166    return true;
167  }
168
169  DISALLOW_COPY_AND_ASSIGN(HostZoomMapSanitizationBrowserTest);
170};
171
172// Regression test for crbug.com/364399.
173IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest, ToggleDefaultZoomLevel) {
174  const double default_zoom_level = content::ZoomFactorToZoomLevel(1.5);
175
176  const char kTestURLTemplate1[] = "http://host1:%d/";
177  const char kTestURLTemplate2[] = "http://host2:%d/";
178
179  ZoomLevelChangeObserver observer(browser()->profile());
180
181  GURL test_url1 = ConstructTestServerURL(kTestURLTemplate1);
182  ui_test_utils::NavigateToURL(browser(), test_url1);
183
184  SetDefaultZoomLevel(default_zoom_level);
185  observer.BlockUntilZoomLevelForHostHasChanged(test_url1.host());
186  EXPECT_TRUE(
187      content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url1)));
188
189  GURL test_url2 = ConstructTestServerURL(kTestURLTemplate2);
190  ui_test_utils::NavigateToURLWithDisposition(
191      browser(), test_url2, NEW_FOREGROUND_TAB,
192      ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
193  EXPECT_TRUE(
194      content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2)));
195
196  content::WebContents* web_contents =
197      browser()->tab_strip_model()->GetActiveWebContents();
198  chrome_page_zoom::Zoom(web_contents, content::PAGE_ZOOM_OUT);
199  observer.BlockUntilZoomLevelForHostHasChanged(test_url2.host());
200  EXPECT_FALSE(
201      content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2)));
202
203  chrome_page_zoom::Zoom(web_contents, content::PAGE_ZOOM_IN);
204  observer.BlockUntilZoomLevelForHostHasChanged(test_url2.host());
205  EXPECT_TRUE(
206      content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2)));
207
208  // Now both tabs should be at the default zoom level, so there should not be
209  // any per-host values saved either to Pref, or internally in HostZoomMap.
210  EXPECT_TRUE(GetHostsWithZoomLevels().empty());
211  EXPECT_TRUE(GetHostsWithZoomLevelsFromPrefs().empty());
212}
213
214// Test that garbage data from crbug.com/364399 is cleared up on startup.
215IN_PROC_BROWSER_TEST_F(HostZoomMapSanitizationBrowserTest, ClearOnStartup) {
216  EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2"));
217  EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2"));
218}
219