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/command_line.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/memory/ref_counted.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/path_service.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/values.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/profiles/profile_impl.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h"
26 #include "chrome/common/chrome_constants.h"
27 #include "chrome/common/chrome_paths.h"
28 #include "chrome/common/pref_names.h"
29 #include "chrome/common/url_constants.h"
30 #include "chrome/test/base/in_process_browser_test.h"
31 #include "chrome/test/base/testing_profile.h"
32 #include "chrome/test/base/ui_test_utils.h"
33 #include "components/signin/core/common/profile_management_switches.h"
34 #include "components/signin/core/common/signin_switches.h"
35 #include "components/ui/zoom/page_zoom.h"
36 #include "components/ui/zoom/zoom_event_manager.h"
37 #include "content/public/test/test_utils.h"
38 #include "net/dns/mock_host_resolver.h"
39 #include "net/test/embedded_test_server/embedded_test_server.h"
40 #include "net/test/embedded_test_server/http_response.h"
41 #include "testing/gmock/include/gmock/gmock.h"
44 using content::HostZoomMap
;
48 class ZoomLevelChangeObserver
{
50 explicit ZoomLevelChangeObserver(content::BrowserContext
* context
)
51 : message_loop_runner_(new content::MessageLoopRunner
) {
52 subscription_
= ui_zoom::ZoomEventManager::GetForBrowserContext(context
)
53 ->AddZoomLevelChangedCallback(base::Bind(
54 &ZoomLevelChangeObserver::OnZoomLevelChanged
,
55 base::Unretained(this)));
58 void BlockUntilZoomLevelForHostHasChanged(const std::string
& host
) {
59 while (!std::count(changed_hosts_
.begin(), changed_hosts_
.end(), host
)) {
60 message_loop_runner_
->Run();
61 message_loop_runner_
= new content::MessageLoopRunner
;
63 changed_hosts_
.clear();
67 void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange
& change
) {
68 changed_hosts_
.push_back(change
.host
);
69 message_loop_runner_
->Quit();
72 scoped_refptr
<content::MessageLoopRunner
> message_loop_runner_
;
73 std::vector
<std::string
> changed_hosts_
;
74 scoped_ptr
<content::HostZoomMap::Subscription
> subscription_
;
76 DISALLOW_COPY_AND_ASSIGN(ZoomLevelChangeObserver
);
81 class HostZoomMapBrowserTest
: public InProcessBrowserTest
{
83 HostZoomMapBrowserTest() {}
86 void SetDefaultZoomLevel(double level
) {
87 browser()->profile()->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(level
);
90 double GetZoomLevel(const GURL
& url
) {
91 content::HostZoomMap
* host_zoom_map
= static_cast<content::HostZoomMap
*>(
92 content::HostZoomMap::GetDefaultForBrowserContext(
93 browser()->profile()));
94 return host_zoom_map
->GetZoomLevelForHostAndScheme(url
.scheme(),
98 std::vector
<std::string
> GetHostsWithZoomLevels() {
99 typedef content::HostZoomMap::ZoomLevelVector ZoomLevelVector
;
100 content::HostZoomMap
* host_zoom_map
= static_cast<content::HostZoomMap
*>(
101 content::HostZoomMap::GetDefaultForBrowserContext(
102 browser()->profile()));
103 content::HostZoomMap::ZoomLevelVector zoom_levels
=
104 host_zoom_map
->GetAllZoomLevels();
105 std::vector
<std::string
> results
;
106 for (ZoomLevelVector::const_iterator it
= zoom_levels
.begin();
107 it
!= zoom_levels
.end(); ++it
)
108 results
.push_back(it
->host
);
112 std::vector
<std::string
> GetHostsWithZoomLevelsFromPrefs() {
113 PrefService
* prefs
= browser()->profile()->GetPrefs();
114 const base::DictionaryValue
* dictionaries
=
115 prefs
->GetDictionary(prefs::kPartitionPerHostZoomLevels
);
116 const base::DictionaryValue
* values
= NULL
;
117 std::string partition_key
=
118 chrome::ChromeZoomLevelPrefs::GetHashForTesting(base::FilePath());
119 dictionaries
->GetDictionary(partition_key
, &values
);
120 std::vector
<std::string
> results
;
122 for (base::DictionaryValue::Iterator
it(*values
);
123 !it
.IsAtEnd(); it
.Advance())
124 results
.push_back(it
.key());
129 GURL
ConstructTestServerURL(const char* url_template
) {
130 return GURL(base::StringPrintf(
131 url_template
, embedded_test_server()->port()));
135 scoped_ptr
<net::test_server::HttpResponse
> HandleRequest(
136 const net::test_server::HttpRequest
& request
) {
137 return scoped_ptr
<net::test_server::HttpResponse
>(
138 new net::test_server::BasicHttpResponse
);
142 void SetUpOnMainThread() override
{
143 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
144 embedded_test_server()->RegisterRequestHandler(base::Bind(
145 &HostZoomMapBrowserTest::HandleRequest
, base::Unretained(this)));
146 host_resolver()->AddRule("*", "127.0.0.1");
149 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTest
);
152 #define PARTITION_KEY_PLACEHOLDER "NNN"
154 class HostZoomMapBrowserTestWithPrefs
: public HostZoomMapBrowserTest
{
156 explicit HostZoomMapBrowserTestWithPrefs(const std::string
& prefs_data
)
157 : prefs_data_(prefs_data
) {}
160 // InProcessBrowserTest:
161 bool SetUpUserDataDirectory() override
{
162 std::replace(prefs_data_
.begin(), prefs_data_
.end(), '\'', '\"');
163 // It seems the hash functions on different platforms can return different
164 // values for the same input, so make sure we test with the hash appropriate
166 std::string hash_string
=
167 chrome::ChromeZoomLevelPrefs::GetHashForTesting(base::FilePath());
168 std::string
partition_key_placeholder(PARTITION_KEY_PLACEHOLDER
);
170 while ((start_index
= prefs_data_
.find(partition_key_placeholder
)) !=
173 start_index
, partition_key_placeholder
.size(), hash_string
);
176 base::FilePath user_data_directory
, path_to_prefs
;
177 PathService::Get(chrome::DIR_USER_DATA
, &user_data_directory
);
178 path_to_prefs
= user_data_directory
179 .AppendASCII(TestingProfile::kTestUserProfileDir
)
180 .Append(chrome::kPreferencesFilename
);
181 base::CreateDirectory(path_to_prefs
.DirName());
183 path_to_prefs
, prefs_data_
.c_str(), prefs_data_
.size());
187 std::string prefs_data_
;
189 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTestWithPrefs
);
192 // Zoom-related preferences demonstrating the two problems that
193 // could be caused by the bug. They incorrectly contain a per-host
194 // zoom level for the empty host; and a value for 'host1' that only
195 // differs from the default by epsilon. Neither should have been
197 const char kSanitizationTestPrefs
[] =
199 " 'default_zoom_level': { '" PARTITION_KEY_PLACEHOLDER
"': 1.2 },"
200 " 'per_host_zoom_levels': {"
201 " '" PARTITION_KEY_PLACEHOLDER
"': {"
202 " '': 1.1, 'host1': 1.20001, 'host2': 1.3 }"
206 #undef PARTITION_KEY_PLACEHOLDER
208 class HostZoomMapSanitizationBrowserTest
209 : public HostZoomMapBrowserTestWithPrefs
{
211 HostZoomMapSanitizationBrowserTest()
212 : HostZoomMapBrowserTestWithPrefs(kSanitizationTestPrefs
) {}
215 DISALLOW_COPY_AND_ASSIGN(HostZoomMapSanitizationBrowserTest
);
218 // Regression test for crbug.com/437392
219 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest
, ZoomEventsWorkForOffTheRecord
) {
220 GURL
test_url(url::kAboutBlankURL
);
221 std::string
test_host(test_url
.host());
222 std::string
test_scheme(test_url
.scheme());
223 Browser
* incognito_browser
=
224 ui_test_utils::OpenURLOffTheRecord(browser()->profile(), test_url
);
226 content::WebContents
* web_contents
=
227 incognito_browser
->tab_strip_model()->GetActiveWebContents();
229 content::BrowserContext
* context
= web_contents
->GetBrowserContext();
230 EXPECT_TRUE(context
->IsOffTheRecord());
231 ZoomLevelChangeObserver
observer(context
);
232 HostZoomMap
* host_zoom_map
= HostZoomMap::GetForWebContents(web_contents
);
234 double new_zoom_level
=
235 host_zoom_map
->GetZoomLevelForHostAndScheme(test_scheme
, test_host
) + 0.5;
236 host_zoom_map
->SetZoomLevelForHostAndScheme(test_scheme
, test_host
,
238 observer
.BlockUntilZoomLevelForHostHasChanged(test_host
);
239 EXPECT_EQ(new_zoom_level
, host_zoom_map
->GetZoomLevelForHostAndScheme(
240 test_scheme
, test_host
));
243 IN_PROC_BROWSER_TEST_F(
244 HostZoomMapBrowserTest
,
245 WebviewBasedSigninUsesDefaultStoragePartitionForEmbedder
) {
246 GURL test_url
= ConstructTestServerURL(chrome::kChromeUIChromeSigninURL
);
247 std::string
test_host(test_url
.host());
248 std::string
test_scheme(test_url
.scheme());
249 ui_test_utils::NavigateToURL(browser(), test_url
);
251 content::WebContents
* web_contents
=
252 browser()->tab_strip_model()->GetActiveWebContents();
254 HostZoomMap
* host_zoom_map
= HostZoomMap::GetForWebContents(web_contents
);
256 // For the webview based sign-in code, the sign in page uses the default host
258 HostZoomMap
* default_profile_host_zoom_map
=
259 HostZoomMap::GetDefaultForBrowserContext(browser()->profile());
260 // Since ChromeOS still uses IFrame-based signin, we should expect the
261 // storage partition to be different if Webview signin is not enabled.
262 if (switches::IsEnableWebviewBasedSignin())
263 EXPECT_EQ(host_zoom_map
, default_profile_host_zoom_map
);
265 EXPECT_NE(host_zoom_map
, default_profile_host_zoom_map
);
268 class HostZoomMapIframeSigninBrowserTest
: public HostZoomMapBrowserTest
{
270 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
271 command_line
->AppendSwitch(switches::kEnableIframeBasedSignin
);
275 // Regression test for crbug.com/435017.
276 IN_PROC_BROWSER_TEST_F(HostZoomMapIframeSigninBrowserTest
,
277 EventsForNonDefaultStoragePartition
) {
278 ZoomLevelChangeObserver
observer(browser()->profile());
279 // TODO(wjmaclean): Make this test more general by implementing a way to
280 // force a generic URL to be loaded in a non-default storage partition. This
281 // test currently relies on the signin page being loaded into a non-default
282 // storage partition (and verifies this is the case), but ultimately it would
283 // be better not to rely on what the signin page is doing.
284 GURL test_url
= ConstructTestServerURL(chrome::kChromeUIChromeSigninURL
);
285 std::string
test_host(test_url
.host());
286 std::string
test_scheme(test_url
.scheme());
287 ui_test_utils::NavigateToURL(browser(), test_url
);
289 content::WebContents
* web_contents
=
290 browser()->tab_strip_model()->GetActiveWebContents();
292 // We are forcing non-webview based signin, so we expect the signin page to
293 // be in a different storage partition, and hence a different HostZoomMap.
294 HostZoomMap
* host_zoom_map
= HostZoomMap::GetForWebContents(web_contents
);
296 EXPECT_FALSE(switches::IsEnableWebviewBasedSignin());
297 HostZoomMap
* default_profile_host_zoom_map
=
298 HostZoomMap::GetDefaultForBrowserContext(browser()->profile());
299 EXPECT_NE(host_zoom_map
, default_profile_host_zoom_map
);
301 double new_zoom_level
=
302 host_zoom_map
->GetZoomLevelForHostAndScheme(test_scheme
, test_host
) + 0.5;
303 host_zoom_map
->SetZoomLevelForHostAndScheme(test_scheme
, test_host
,
305 observer
.BlockUntilZoomLevelForHostHasChanged(test_host
);
306 EXPECT_EQ(new_zoom_level
, host_zoom_map
->GetZoomLevelForHostAndScheme(
307 test_scheme
, test_host
));
310 // Regression test for crbug.com/364399.
311 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest
, ToggleDefaultZoomLevel
) {
312 const double default_zoom_level
= content::ZoomFactorToZoomLevel(1.5);
314 const char kTestURLTemplate1
[] = "http://host1:%u/";
315 const char kTestURLTemplate2
[] = "http://host2:%u/";
317 ZoomLevelChangeObserver
observer(browser()->profile());
319 GURL test_url1
= ConstructTestServerURL(kTestURLTemplate1
);
320 ui_test_utils::NavigateToURL(browser(), test_url1
);
322 SetDefaultZoomLevel(default_zoom_level
);
323 observer
.BlockUntilZoomLevelForHostHasChanged(test_url1
.host());
325 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url1
)));
327 GURL test_url2
= ConstructTestServerURL(kTestURLTemplate2
);
328 ui_test_utils::NavigateToURLWithDisposition(
329 browser(), test_url2
, NEW_FOREGROUND_TAB
,
330 ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION
);
332 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url2
)));
334 content::WebContents
* web_contents
=
335 browser()->tab_strip_model()->GetActiveWebContents();
336 ui_zoom::PageZoom::Zoom(web_contents
, content::PAGE_ZOOM_OUT
);
337 observer
.BlockUntilZoomLevelForHostHasChanged(test_url2
.host());
339 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url2
)));
341 ui_zoom::PageZoom::Zoom(web_contents
, content::PAGE_ZOOM_IN
);
342 observer
.BlockUntilZoomLevelForHostHasChanged(test_url2
.host());
344 content::ZoomValuesEqual(default_zoom_level
, GetZoomLevel(test_url2
)));
346 // Now both tabs should be at the default zoom level, so there should not be
347 // any per-host values saved either to Pref, or internally in HostZoomMap.
348 EXPECT_TRUE(GetHostsWithZoomLevels().empty());
349 EXPECT_TRUE(GetHostsWithZoomLevelsFromPrefs().empty());
352 // Test that garbage data from crbug.com/364399 is cleared up on startup.
353 IN_PROC_BROWSER_TEST_F(HostZoomMapSanitizationBrowserTest
, ClearOnStartup
) {
354 EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2"));
355 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2"));
358 // In this case we migrate the zoom level data from the profile prefs.
359 const char kMigrationTestPrefs
[] =
361 " 'default_zoom_level': 1.2,"
362 " 'per_host_zoom_levels': {'': 1.1, 'host1': 1.20001, 'host2': "
366 class HostZoomMapMigrationBrowserTest
: public HostZoomMapBrowserTestWithPrefs
{
368 HostZoomMapMigrationBrowserTest()
369 : HostZoomMapBrowserTestWithPrefs(kMigrationTestPrefs
) {}
371 static const double kOriginalDefaultZoomLevel
;
374 DISALLOW_COPY_AND_ASSIGN(HostZoomMapMigrationBrowserTest
);
377 const double HostZoomMapMigrationBrowserTest::kOriginalDefaultZoomLevel
= 1.2;
379 // This test is the same as HostZoomMapSanitizationBrowserTest, except that the
380 // zoom level data is loaded from the profile prefs, transfered to the
381 // zoom-level prefs, and we verify that the profile zoom level prefs are
382 // erased in the process. We also test that changes to the host zoom map and the
383 // default zoom level don't propagate back to the profile prefs.
384 IN_PROC_BROWSER_TEST_F(HostZoomMapMigrationBrowserTest
,
385 MigrateProfileZoomPreferences
) {
386 EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2"));
387 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2"));
389 PrefService
* profile_prefs
=
390 browser()->profile()->GetPrefs();
391 chrome::ChromeZoomLevelPrefs
* zoom_level_prefs
=
392 browser()->profile()->GetZoomLevelPrefs();
393 // Make sure that the profile pref for default zoom level has been set to
394 // its default value of 0.0.
395 EXPECT_EQ(0.0, profile_prefs
->GetDouble(prefs::kDefaultZoomLevelDeprecated
));
396 EXPECT_EQ(kOriginalDefaultZoomLevel
,
397 zoom_level_prefs
->GetDefaultZoomLevelPref());
399 // Make sure that the profile prefs for per-host zoom levels are erased.
401 const base::DictionaryValue
* profile_host_zoom_dictionary
=
402 profile_prefs
->GetDictionary(prefs::kPerHostZoomLevelsDeprecated
);
403 EXPECT_EQ(0UL, profile_host_zoom_dictionary
->size());
406 ZoomLevelChangeObserver
observer(browser()->profile());
407 content::HostZoomMap
* host_zoom_map
= static_cast<content::HostZoomMap
*>(
408 content::HostZoomMap::GetDefaultForBrowserContext(
409 browser()->profile()));
411 // Make sure that a change to a host zoom level doesn't propagate to the
413 std::string
host3("host3");
414 host_zoom_map
->SetZoomLevelForHost(host3
, 1.3);
415 observer
.BlockUntilZoomLevelForHostHasChanged(host3
);
416 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(),
417 testing::ElementsAre("host2", host3
));
419 const base::DictionaryValue
* profile_host_zoom_dictionary
=
420 profile_prefs
->GetDictionary(prefs::kPerHostZoomLevelsDeprecated
);
421 EXPECT_EQ(0UL, profile_host_zoom_dictionary
->size());
424 // Make sure a change to the default zoom level doesn't propagate to the
427 // First, we need a host at the default zoom level to respond when the
428 // default zoom level changes.
429 const double kNewDefaultZoomLevel
= 1.5;
430 GURL test_url
= ConstructTestServerURL("http://host4:%u/");
431 ui_test_utils::NavigateToURL(browser(), test_url
);
432 EXPECT_TRUE(content::ZoomValuesEqual(kOriginalDefaultZoomLevel
,
433 GetZoomLevel(test_url
)));
435 // Change the default zoom level and observe.
436 SetDefaultZoomLevel(kNewDefaultZoomLevel
);
437 observer
.BlockUntilZoomLevelForHostHasChanged(test_url
.host());
439 content::ZoomValuesEqual(kNewDefaultZoomLevel
, GetZoomLevel(test_url
)));
440 EXPECT_EQ(kNewDefaultZoomLevel
, zoom_level_prefs
->GetDefaultZoomLevelPref());
441 EXPECT_EQ(0.0, profile_prefs
->GetDouble(prefs::kDefaultZoomLevelDeprecated
));
445 // 1. Host zoom maps of parent profile and child profile are different.
446 // 2. Child host zoom map inherits zoom level at construction.
447 // 3. Change of zoom level doesn't propagate from child to parent.
448 // 4. Change of zoom level propagates from parent to child.
449 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest
,
450 OffTheRecordProfileHostZoomMap
) {
451 // Constants for test case.
452 const std::string
host("example.com");
453 const double zoom_level_25
= 2.5;
454 const double zoom_level_30
= 3.0;
455 const double zoom_level_40
= 4.0;
457 Profile
* parent_profile
= browser()->profile();
458 Profile
* child_profile
=
459 static_cast<ProfileImpl
*>(parent_profile
)->GetOffTheRecordProfile();
460 HostZoomMap
* parent_zoom_map
=
461 HostZoomMap::GetDefaultForBrowserContext(parent_profile
);
462 ASSERT_TRUE(parent_zoom_map
);
464 parent_zoom_map
->SetZoomLevelForHost(host
, zoom_level_25
);
465 ASSERT_EQ(parent_zoom_map
->GetZoomLevelForHostAndScheme("http", host
),
468 // Prepare child host zoom map.
469 HostZoomMap
* child_zoom_map
=
470 HostZoomMap::GetDefaultForBrowserContext(child_profile
);
471 ASSERT_TRUE(child_zoom_map
);
474 EXPECT_NE(parent_zoom_map
, child_zoom_map
);
476 EXPECT_EQ(parent_zoom_map
->GetZoomLevelForHostAndScheme("http", host
),
477 child_zoom_map
->GetZoomLevelForHostAndScheme("http", host
)) <<
478 "Child must inherit from parent.";
480 child_zoom_map
->SetZoomLevelForHost(host
, zoom_level_30
);
482 child_zoom_map
->GetZoomLevelForHostAndScheme("http", host
),
485 EXPECT_NE(parent_zoom_map
->GetZoomLevelForHostAndScheme("http", host
),
486 child_zoom_map
->GetZoomLevelForHostAndScheme("http", host
)) <<
487 "Child change must not propagate to parent.";
489 parent_zoom_map
->SetZoomLevelForHost(host
, zoom_level_40
);
491 parent_zoom_map
->GetZoomLevelForHostAndScheme("http", host
),
494 EXPECT_EQ(parent_zoom_map
->GetZoomLevelForHostAndScheme("http", host
),
495 child_zoom_map
->GetZoomLevelForHostAndScheme("http", host
)) <<
496 "Parent change should propagate to child.";
497 base::RunLoop().RunUntilIdle();
500 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest
,
501 ParentDefaultZoomPropagatesToIncognitoChild
) {
502 Profile
* parent_profile
= browser()->profile();
503 Profile
* child_profile
=
504 static_cast<ProfileImpl
*>(parent_profile
)->GetOffTheRecordProfile();
506 double new_default_zoom_level
=
507 parent_profile
->GetZoomLevelPrefs()->GetDefaultZoomLevelPref() + 1.f
;
508 HostZoomMap
* parent_host_zoom_map
=
509 HostZoomMap::GetDefaultForBrowserContext(parent_profile
);
510 HostZoomMap
* child_host_zoom_map
=
511 HostZoomMap::GetDefaultForBrowserContext(child_profile
);
512 ASSERT_TRUE(parent_host_zoom_map
);
513 ASSERT_TRUE(child_host_zoom_map
);
514 EXPECT_NE(parent_host_zoom_map
, child_host_zoom_map
);
515 EXPECT_NE(new_default_zoom_level
, child_host_zoom_map
->GetDefaultZoomLevel());
517 parent_profile
->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(
518 new_default_zoom_level
);
519 EXPECT_EQ(new_default_zoom_level
, child_host_zoom_map
->GetDefaultZoomLevel());