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/file_util.h" 13 #include "base/files/file_path.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 37 namespace { 38 39 class 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::GetForBrowserContext(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 72 class 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::GetForBrowserContext(browser()->profile())); 85 return host_zoom_map->GetZoomLevelForHostAndScheme(url.scheme(), 86 url.host()); 87 } 88 89 std::vector<std::string> GetHostsWithZoomLevels() { 90 typedef content::HostZoomMap::ZoomLevelVector ZoomLevelVector; 91 content::HostZoomMap* host_zoom_map = static_cast<content::HostZoomMap*>( 92 content::HostZoomMap::GetForBrowserContext(browser()->profile())); 93 content::HostZoomMap::ZoomLevelVector zoom_levels = 94 host_zoom_map->GetAllZoomLevels(); 95 std::vector<std::string> results; 96 for (ZoomLevelVector::const_iterator it = zoom_levels.begin(); 97 it != zoom_levels.end(); ++it) 98 results.push_back(it->host); 99 return results; 100 } 101 102 std::vector<std::string> GetHostsWithZoomLevelsFromPrefs() { 103 PrefService* prefs = browser()->profile()->GetPrefs(); 104 const base::DictionaryValue* values = 105 prefs->GetDictionary(prefs::kPerHostZoomLevels); 106 std::vector<std::string> results; 107 if (values) { 108 for (base::DictionaryValue::Iterator it(*values); 109 !it.IsAtEnd(); it.Advance()) 110 results.push_back(it.key()); 111 } 112 return results; 113 } 114 115 GURL ConstructTestServerURL(const char* url_template) { 116 return GURL(base::StringPrintf( 117 url_template, embedded_test_server()->port())); 118 } 119 120 private: 121 scoped_ptr<net::test_server::HttpResponse> HandleRequest( 122 const net::test_server::HttpRequest& request) { 123 return scoped_ptr<net::test_server::HttpResponse>( 124 new net::test_server::BasicHttpResponse); 125 } 126 127 // BrowserTestBase: 128 virtual void SetUpOnMainThread() OVERRIDE { 129 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 130 embedded_test_server()->RegisterRequestHandler(base::Bind( 131 &HostZoomMapBrowserTest::HandleRequest, base::Unretained(this))); 132 host_resolver()->AddRule("*", "127.0.0.1"); 133 } 134 135 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTest); 136 }; 137 138 class HostZoomMapSanitizationBrowserTest : public HostZoomMapBrowserTest { 139 public: 140 HostZoomMapSanitizationBrowserTest() {} 141 142 private: 143 // InProcessBrowserTest: 144 virtual bool SetUpUserDataDirectory() OVERRIDE { 145 // Zoom-related preferences demonstrating the two problems that could be 146 // caused by the bug. They incorrectly contain a per-host zoom level for the 147 // empty host; and a value for 'host1' that only differs from the default by 148 // epsilon. Neither should have been persisted. 149 const char kBrokenPrefs[] = 150 "{'profile': {" 151 " 'default_zoom_level': 1.2," 152 " 'per_host_zoom_levels': {'': 1.1, 'host1': 1.20001, 'host2': 1.3}" 153 "}}"; 154 std::string broken_prefs(kBrokenPrefs); 155 std::replace(broken_prefs.begin(), broken_prefs.end(), '\'', '\"'); 156 157 base::FilePath user_data_directory, path_to_prefs; 158 PathService::Get(chrome::DIR_USER_DATA, &user_data_directory); 159 path_to_prefs = user_data_directory 160 .AppendASCII(TestingProfile::kTestUserProfileDir) 161 .Append(chrome::kPreferencesFilename); 162 base::CreateDirectory(path_to_prefs.DirName()); 163 base::WriteFile(path_to_prefs, broken_prefs.c_str(), broken_prefs.size()); 164 return true; 165 } 166 167 DISALLOW_COPY_AND_ASSIGN(HostZoomMapSanitizationBrowserTest); 168 }; 169 170 // Regression test for crbug.com/364399. 171 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest, ToggleDefaultZoomLevel) { 172 const double default_zoom_level = content::ZoomFactorToZoomLevel(1.5); 173 174 const char kTestURLTemplate1[] = "http://host1:%d/"; 175 const char kTestURLTemplate2[] = "http://host2:%d/"; 176 177 ZoomLevelChangeObserver observer(browser()->profile()); 178 179 GURL test_url1 = ConstructTestServerURL(kTestURLTemplate1); 180 ui_test_utils::NavigateToURL(browser(), test_url1); 181 182 SetDefaultZoomLevel(default_zoom_level); 183 observer.BlockUntilZoomLevelForHostHasChanged(test_url1.host()); 184 EXPECT_TRUE( 185 content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url1))); 186 187 GURL test_url2 = ConstructTestServerURL(kTestURLTemplate2); 188 ui_test_utils::NavigateToURLWithDisposition( 189 browser(), test_url2, NEW_FOREGROUND_TAB, 190 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); 191 EXPECT_TRUE( 192 content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2))); 193 194 content::WebContents* web_contents = 195 browser()->tab_strip_model()->GetActiveWebContents(); 196 chrome_page_zoom::Zoom(web_contents, content::PAGE_ZOOM_OUT); 197 observer.BlockUntilZoomLevelForHostHasChanged(test_url2.host()); 198 EXPECT_FALSE( 199 content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2))); 200 201 chrome_page_zoom::Zoom(web_contents, content::PAGE_ZOOM_IN); 202 observer.BlockUntilZoomLevelForHostHasChanged(test_url2.host()); 203 EXPECT_TRUE( 204 content::ZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2))); 205 206 // Now both tabs should be at the default zoom level, so there should not be 207 // any per-host values saved either to Pref, or internally in HostZoomMap. 208 EXPECT_TRUE(GetHostsWithZoomLevels().empty()); 209 EXPECT_TRUE(GetHostsWithZoomLevelsFromPrefs().empty()); 210 } 211 212 // Test that garbage data from crbug.com/364399 is cleared up on startup. 213 IN_PROC_BROWSER_TEST_F(HostZoomMapSanitizationBrowserTest, ClearOnStartup) { 214 EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2")); 215 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2")); 216 } 217