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.
5 #include "content/public/browser/host_zoom_map.h"
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/browser/ui/zoom/chrome_zoom_level_prefs.h"
25 #include "chrome/common/chrome_constants.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/test/base/in_process_browser_test.h"
29 #include "chrome/test/base/testing_profile.h"
30 #include "chrome/test/base/ui_test_utils.h"
31 #include "content/public/test/test_utils.h"
32 #include "net/dns/mock_host_resolver.h"
33 #include "net/test/embedded_test_server/embedded_test_server.h"
34 #include "net/test/embedded_test_server/http_response.h"
35 #include "testing/gmock/include/gmock/gmock.h"
40 class ZoomLevelChangeObserver
{
42 explicit ZoomLevelChangeObserver(Profile
* profile
)
43 : message_loop_runner_(new content::MessageLoopRunner
) {
44 content::HostZoomMap
* host_zoom_map
= static_cast<content::HostZoomMap
*>(
45 content::HostZoomMap::GetDefaultForBrowserContext(profile
));
46 subscription_
= host_zoom_map
->AddZoomLevelChangedCallback(base::Bind(
47 &ZoomLevelChangeObserver::OnZoomLevelChanged
, base::Unretained(this)));
50 void BlockUntilZoomLevelForHostHasChanged(const std::string
& host
) {
51 while (!std::count(changed_hosts_
.begin(), changed_hosts_
.end(), host
)) {
52 message_loop_runner_
->Run();
53 message_loop_runner_
= new content::MessageLoopRunner
;
55 changed_hosts_
.clear();
59 void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange
& change
) {
60 changed_hosts_
.push_back(change
.host
);
61 message_loop_runner_
->Quit();
64 scoped_refptr
<content::MessageLoopRunner
> message_loop_runner_
;
65 std::vector
<std::string
> changed_hosts_
;
66 scoped_ptr
<content::HostZoomMap::Subscription
> subscription_
;
68 DISALLOW_COPY_AND_ASSIGN(ZoomLevelChangeObserver
);
73 class HostZoomMapBrowserTest
: public InProcessBrowserTest
{
75 HostZoomMapBrowserTest() {}
78 void SetDefaultZoomLevel(double level
) {
79 browser()->profile()->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(level
);
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(),
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
);
104 std::vector
<std::string
> GetHostsWithZoomLevelsFromPrefs() {
105 PrefService
* prefs
= browser()->profile()->GetPrefs();
106 const base::DictionaryValue
* dictionaries
=
107 prefs
->GetDictionary(prefs::kPartitionPerHostZoomLevels
);
108 const base::DictionaryValue
* values
= NULL
;
109 std::string partition_key
=
110 chrome::ChromeZoomLevelPrefs::GetHashForTesting(base::FilePath());
111 dictionaries
->GetDictionary(partition_key
, &values
);
112 std::vector
<std::string
> results
;
114 for (base::DictionaryValue::Iterator
it(*values
);
115 !it
.IsAtEnd(); it
.Advance())
116 results
.push_back(it
.key());
121 GURL
ConstructTestServerURL(const char* url_template
) {
122 return GURL(base::StringPrintf(
123 url_template
, embedded_test_server()->port()));
127 scoped_ptr
<net::test_server::HttpResponse
> HandleRequest(
128 const net::test_server::HttpRequest
& request
) {
129 return scoped_ptr
<net::test_server::HttpResponse
>(
130 new net::test_server::BasicHttpResponse
);
134 virtual void SetUpOnMainThread() override
{
135 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
136 embedded_test_server()->RegisterRequestHandler(base::Bind(
137 &HostZoomMapBrowserTest::HandleRequest
, base::Unretained(this)));
138 host_resolver()->AddRule("*", "127.0.0.1");
141 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTest
);
144 #define PARTITION_KEY_PLACEHOLDER "NNN"
146 class HostZoomMapBrowserTestWithPrefs
: public HostZoomMapBrowserTest
{
148 explicit HostZoomMapBrowserTestWithPrefs(const std::string
& prefs_data
)
149 : prefs_data_(prefs_data
) {}
152 // InProcessBrowserTest:
153 virtual bool SetUpUserDataDirectory() override
{
154 std::replace(prefs_data_
.begin(), prefs_data_
.end(), '\'', '\"');
155 // It seems the hash functions on different platforms can return different
156 // values for the same input, so make sure we test with the hash appropriate
158 std::string hash_string
=
159 chrome::ChromeZoomLevelPrefs::GetHashForTesting(base::FilePath());
160 std::string
partition_key_placeholder(PARTITION_KEY_PLACEHOLDER
);
162 while ((start_index
= prefs_data_
.find(partition_key_placeholder
)) !=
165 start_index
, partition_key_placeholder
.size(), hash_string
);
168 base::FilePath user_data_directory
, path_to_prefs
;
169 PathService::Get(chrome::DIR_USER_DATA
, &user_data_directory
);
170 path_to_prefs
= user_data_directory
171 .AppendASCII(TestingProfile::kTestUserProfileDir
)
172 .Append(chrome::kPreferencesFilename
);
173 base::CreateDirectory(path_to_prefs
.DirName());
175 path_to_prefs
, prefs_data_
.c_str(), prefs_data_
.size());
179 std::string prefs_data_
;
181 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTestWithPrefs
);
184 // Zoom-related preferences demonstrating the two problems that
185 // could be caused by the bug. They incorrectly contain a per-host
186 // zoom level for the empty host; and a value for 'host1' that only
187 // differs from the default by epsilon. Neither should have been
189 const char kSanitizationTestPrefs
[] =
191 " 'default_zoom_level': { '" PARTITION_KEY_PLACEHOLDER
"': 1.2 },"
192 " 'per_host_zoom_levels': {"
193 " '" PARTITION_KEY_PLACEHOLDER
"': {"
194 " '': 1.1, 'host1': 1.20001, 'host2': 1.3 }"
198 #undef PARTITION_KEY_PLACEHOLDER
200 class HostZoomMapSanitizationBrowserTest
201 : public HostZoomMapBrowserTestWithPrefs
{
203 HostZoomMapSanitizationBrowserTest()
204 : HostZoomMapBrowserTestWithPrefs(kSanitizationTestPrefs
) {}
207 DISALLOW_COPY_AND_ASSIGN(HostZoomMapSanitizationBrowserTest
);
210 // Regression test for crbug.com/364399.
211 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest
, ToggleDefaultZoomLevel
) {
212 const double default_zoom_level
= content::ZoomFactorToZoomLevel(1.5);
214 const char kTestURLTemplate1
[] = "http://host1:%d/";
215 const char kTestURLTemplate2
[] = "http://host2:%d/";
217 ZoomLevelChangeObserver
observer(browser()->profile());
219 GURL test_url1
= ConstructTestServerURL(kTestURLTemplate1
);
220 ui_test_utils::NavigateToURL(browser(), test_url1
);
222 SetDefaultZoomLevel(default_zoom_level
);
223 observer
.BlockUntilZoomLevelForHostHasChanged(test_url1
.host());
225 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url1
)));
227 GURL test_url2
= ConstructTestServerURL(kTestURLTemplate2
);
228 ui_test_utils::NavigateToURLWithDisposition(
229 browser(), test_url2
, NEW_FOREGROUND_TAB
,
230 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
232 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url2
)));
234 content::WebContents
* web_contents
=
235 browser()->tab_strip_model()->GetActiveWebContents();
236 chrome_page_zoom::Zoom(web_contents
, content::PAGE_ZOOM_OUT
);
237 observer
.BlockUntilZoomLevelForHostHasChanged(test_url2
.host());
239 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url2
)));
241 chrome_page_zoom::Zoom(web_contents
, content::PAGE_ZOOM_IN
);
242 observer
.BlockUntilZoomLevelForHostHasChanged(test_url2
.host());
244 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url2
)));
246 // Now both tabs should be at the default zoom level, so there should not be
247 // any per-host values saved either to Pref, or internally in HostZoomMap.
248 EXPECT_TRUE(GetHostsWithZoomLevels().empty());
249 EXPECT_TRUE(GetHostsWithZoomLevelsFromPrefs().empty());
252 // Test that garbage data from crbug.com/364399 is cleared up on startup.
253 IN_PROC_BROWSER_TEST_F(HostZoomMapSanitizationBrowserTest
, ClearOnStartup
) {
254 EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2"));
255 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2"));
258 // In this case we migrate the zoom level data from the profile prefs.
259 const char kMigrationTestPrefs
[] =
261 " 'default_zoom_level': 1.2,"
262 " 'per_host_zoom_levels': {'': 1.1, 'host1': 1.20001, 'host2': "
266 class HostZoomMapMigrationBrowserTest
: public HostZoomMapBrowserTestWithPrefs
{
268 HostZoomMapMigrationBrowserTest()
269 : HostZoomMapBrowserTestWithPrefs(kMigrationTestPrefs
) {}
271 static const double kOriginalDefaultZoomLevel
;
274 DISALLOW_COPY_AND_ASSIGN(HostZoomMapMigrationBrowserTest
);
277 const double HostZoomMapMigrationBrowserTest::kOriginalDefaultZoomLevel
= 1.2;
279 // This test is the same as HostZoomMapSanitizationBrowserTest, except that the
280 // zoom level data is loaded from the profile prefs, transfered to the
281 // zoom-level prefs, and we verify that the profile zoom level prefs are
282 // erased in the process. We also test that changes to the host zoom map and the
283 // default zoom level don't propagate back to the profile prefs.
284 IN_PROC_BROWSER_TEST_F(HostZoomMapMigrationBrowserTest
,
285 MigrateProfileZoomPreferences
) {
286 EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2"));
287 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2"));
289 PrefService
* profile_prefs
=
290 browser()->profile()->GetPrefs();
291 chrome::ChromeZoomLevelPrefs
* zoom_level_prefs
=
292 browser()->profile()->GetZoomLevelPrefs();
293 // Make sure that the profile pref for default zoom level has been set to
294 // its default value of 0.0.
295 EXPECT_EQ(0.0, profile_prefs
->GetDouble(prefs::kDefaultZoomLevelDeprecated
));
296 EXPECT_EQ(kOriginalDefaultZoomLevel
,
297 zoom_level_prefs
->GetDefaultZoomLevelPref());
299 // Make sure that the profile prefs for per-host zoom levels are erased.
301 const base::DictionaryValue
* profile_host_zoom_dictionary
=
302 profile_prefs
->GetDictionary(prefs::kPerHostZoomLevelsDeprecated
);
303 EXPECT_EQ(0UL, profile_host_zoom_dictionary
->size());
306 ZoomLevelChangeObserver
observer(browser()->profile());
307 content::HostZoomMap
* host_zoom_map
= static_cast<content::HostZoomMap
*>(
308 content::HostZoomMap::GetDefaultForBrowserContext(
309 browser()->profile()));
311 // Make sure that a change to a host zoom level doesn't propagate to the
313 std::string
host3("host3");
314 host_zoom_map
->SetZoomLevelForHost(host3
, 1.3);
315 observer
.BlockUntilZoomLevelForHostHasChanged(host3
);
316 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(),
317 testing::ElementsAre("host2", host3
));
319 const base::DictionaryValue
* profile_host_zoom_dictionary
=
320 profile_prefs
->GetDictionary(prefs::kPerHostZoomLevelsDeprecated
);
321 EXPECT_EQ(0UL, profile_host_zoom_dictionary
->size());
324 // Make sure a change to the default zoom level doesn't propagate to the
327 // First, we need a host at the default zoom level to respond when the
328 // default zoom level changes.
329 const double kNewDefaultZoomLevel
= 1.5;
330 GURL test_url
= ConstructTestServerURL("http://host4:%d/");
331 ui_test_utils::NavigateToURL(browser(), test_url
);
332 EXPECT_TRUE(content::ZoomValuesEqual(kOriginalDefaultZoomLevel
,
333 GetZoomLevel(test_url
)));
335 // Change the default zoom level and observe.
336 SetDefaultZoomLevel(kNewDefaultZoomLevel
);
337 observer
.BlockUntilZoomLevelForHostHasChanged(test_url
.host());
339 content::ZoomValuesEqual(kNewDefaultZoomLevel
, GetZoomLevel(test_url
)));
340 EXPECT_EQ(kNewDefaultZoomLevel
, zoom_level_prefs
->GetDefaultZoomLevelPref());
341 EXPECT_EQ(0.0, profile_prefs
->GetDouble(prefs::kDefaultZoomLevelDeprecated
));