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 <stddef.h>
6
7 #include <algorithm>
8 #include <memory>
9 #include <string>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/macros.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/path_service.h"
19 #include "base/run_loop.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/values.h"
22 #include "build/build_config.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/profiles/profile_impl.h"
25 #include "chrome/browser/signin/signin_promo.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/tabs/tab_strip_model.h"
28 #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h"
29 #include "chrome/common/chrome_constants.h"
30 #include "chrome/common/chrome_paths.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/common/url_constants.h"
33 #include "chrome/test/base/in_process_browser_test.h"
34 #include "chrome/test/base/testing_profile.h"
35 #include "chrome/test/base/ui_test_utils.h"
36 #include "components/prefs/pref_service.h"
37 #include "components/signin/public/base/signin_switches.h"
38 #include "components/zoom/page_zoom.h"
39 #include "components/zoom/zoom_event_manager.h"
40 #include "content/public/browser/host_zoom_map.h"
41 #include "content/public/test/browser_test.h"
42 #include "content/public/test/test_utils.h"
43 #include "net/dns/mock_host_resolver.h"
44 #include "net/test/embedded_test_server/embedded_test_server.h"
45 #include "net/test/embedded_test_server/http_response.h"
46 #include "testing/gmock/include/gmock/gmock.h"
47 #include "third_party/blink/public/common/page/page_zoom.h"
48 #include "url/gurl.h"
49
50 using content::HostZoomMap;
51
52 namespace {
53
54 class ZoomLevelChangeObserver {
55 public:
ZoomLevelChangeObserver(content::BrowserContext * context)56 explicit ZoomLevelChangeObserver(content::BrowserContext* context)
57 : message_loop_runner_(new content::MessageLoopRunner) {
58 subscription_ = zoom::ZoomEventManager::GetForBrowserContext(context)
59 ->AddZoomLevelChangedCallback(base::BindRepeating(
60 &ZoomLevelChangeObserver::OnZoomLevelChanged,
61 base::Unretained(this)));
62 }
63
BlockUntilZoomLevelForHostHasChanged(const std::string & host)64 void BlockUntilZoomLevelForHostHasChanged(const std::string& host) {
65 while (!std::count(changed_hosts_.begin(), changed_hosts_.end(), host)) {
66 message_loop_runner_->Run();
67 message_loop_runner_ = new content::MessageLoopRunner;
68 }
69 changed_hosts_.clear();
70 }
71
72 private:
OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange & change)73 void OnZoomLevelChanged(const content::HostZoomMap::ZoomLevelChange& change) {
74 changed_hosts_.push_back(change.host);
75 message_loop_runner_->Quit();
76 }
77
78 scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
79 std::vector<std::string> changed_hosts_;
80 std::unique_ptr<content::HostZoomMap::Subscription> subscription_;
81
82 DISALLOW_COPY_AND_ASSIGN(ZoomLevelChangeObserver);
83 };
84
85 } // namespace
86
87 class HostZoomMapBrowserTest : public InProcessBrowserTest {
88 public:
HostZoomMapBrowserTest()89 HostZoomMapBrowserTest() {}
90
91 protected:
SetDefaultZoomLevel(double level)92 void SetDefaultZoomLevel(double level) {
93 browser()->profile()->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(level);
94 }
95
GetZoomLevel(const GURL & url)96 double GetZoomLevel(const GURL& url) {
97 content::HostZoomMap* host_zoom_map = static_cast<content::HostZoomMap*>(
98 content::HostZoomMap::GetDefaultForBrowserContext(
99 browser()->profile()));
100 return host_zoom_map->GetZoomLevelForHostAndScheme(url.scheme(),
101 url.host());
102 }
103
GetHostsWithZoomLevels()104 std::vector<std::string> GetHostsWithZoomLevels() {
105 typedef content::HostZoomMap::ZoomLevelVector ZoomLevelVector;
106 content::HostZoomMap* host_zoom_map = static_cast<content::HostZoomMap*>(
107 content::HostZoomMap::GetDefaultForBrowserContext(
108 browser()->profile()));
109 content::HostZoomMap::ZoomLevelVector zoom_levels =
110 host_zoom_map->GetAllZoomLevels();
111 std::vector<std::string> results;
112 for (ZoomLevelVector::const_iterator it = zoom_levels.begin();
113 it != zoom_levels.end(); ++it)
114 results.push_back(it->host);
115 return results;
116 }
117
GetHostsWithZoomLevelsFromPrefs()118 std::vector<std::string> GetHostsWithZoomLevelsFromPrefs() {
119 PrefService* prefs = browser()->profile()->GetPrefs();
120 const base::DictionaryValue* dictionaries =
121 prefs->GetDictionary(prefs::kPartitionPerHostZoomLevels);
122 const base::DictionaryValue* values = NULL;
123 std::string partition_key =
124 ChromeZoomLevelPrefs::GetPartitionKeyForTesting(base::FilePath());
125 dictionaries->GetDictionary(partition_key, &values);
126 std::vector<std::string> results;
127 if (values) {
128 for (base::DictionaryValue::Iterator it(*values);
129 !it.IsAtEnd(); it.Advance())
130 results.push_back(it.key());
131 }
132 return results;
133 }
134
ConstructTestServerURL(const char * url_template)135 GURL ConstructTestServerURL(const char* url_template) {
136 return GURL(base::StringPrintf(
137 url_template, embedded_test_server()->port()));
138 }
139
140 private:
HandleRequest(const net::test_server::HttpRequest & request)141 std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
142 const net::test_server::HttpRequest& request) {
143 return std::unique_ptr<net::test_server::HttpResponse>(
144 new net::test_server::BasicHttpResponse);
145 }
146
147 // BrowserTestBase:
SetUpOnMainThread()148 void SetUpOnMainThread() override {
149 embedded_test_server()->RegisterRequestHandler(base::Bind(
150 &HostZoomMapBrowserTest::HandleRequest, base::Unretained(this)));
151 ASSERT_TRUE(embedded_test_server()->Start());
152 host_resolver()->AddRule("*", "127.0.0.1");
153 }
154
155 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTest);
156 };
157
158 #define PARTITION_KEY_PLACEHOLDER "NNN"
159
160 class HostZoomMapBrowserTestWithPrefs : public HostZoomMapBrowserTest {
161 public:
HostZoomMapBrowserTestWithPrefs(const std::string & prefs_data)162 explicit HostZoomMapBrowserTestWithPrefs(const std::string& prefs_data)
163 : prefs_data_(prefs_data) {}
164
165 private:
166 // InProcessBrowserTest:
SetUpUserDataDirectory()167 bool SetUpUserDataDirectory() override {
168 std::replace(prefs_data_.begin(), prefs_data_.end(), '\'', '\"');
169 // It seems the hash functions on different platforms can return different
170 // values for the same input, so make sure we test with the hash appropriate
171 // for the platform.
172 std::string partition_key =
173 ChromeZoomLevelPrefs::GetPartitionKeyForTesting(base::FilePath());
174 std::string partition_key_placeholder(PARTITION_KEY_PLACEHOLDER);
175 size_t start_index;
176 while ((start_index = prefs_data_.find(partition_key_placeholder)) !=
177 std::string::npos) {
178 prefs_data_.replace(start_index, partition_key_placeholder.size(),
179 partition_key);
180 }
181
182 base::FilePath user_data_directory, path_to_prefs;
183 base::PathService::Get(chrome::DIR_USER_DATA, &user_data_directory);
184 path_to_prefs = user_data_directory
185 .AppendASCII(TestingProfile::kTestUserProfileDir)
186 .Append(chrome::kPreferencesFilename);
187 base::CreateDirectory(path_to_prefs.DirName());
188 base::WriteFile(path_to_prefs, prefs_data_);
189 return true;
190 }
191
192 std::string prefs_data_;
193
194 DISALLOW_COPY_AND_ASSIGN(HostZoomMapBrowserTestWithPrefs);
195 };
196
197 // Zoom-related preferences demonstrating the two problems that
198 // could be caused by the bug. They incorrectly contain a per-host
199 // zoom level for the empty host; and a value for 'host1' that only
200 // differs from the default by epsilon. Neither should have been
201 // persisted.
202 const char kSanitizationTestPrefs[] =
203 "{'partition': {"
204 " 'default_zoom_level': { '" PARTITION_KEY_PLACEHOLDER "': 1.2 },"
205 " 'per_host_zoom_levels': {"
206 " '" PARTITION_KEY_PLACEHOLDER "': {"
207 " '': 1.1, 'host1': 1.20001, 'host2': 1.3 }"
208 " }"
209 "}}";
210
211 #undef PARTITION_KEY_PLACEHOLDER
212
213 class HostZoomMapSanitizationBrowserTest
214 : public HostZoomMapBrowserTestWithPrefs {
215 public:
HostZoomMapSanitizationBrowserTest()216 HostZoomMapSanitizationBrowserTest()
217 : HostZoomMapBrowserTestWithPrefs(kSanitizationTestPrefs) {}
218
219 private:
220 DISALLOW_COPY_AND_ASSIGN(HostZoomMapSanitizationBrowserTest);
221 };
222
223 // Regression test for crbug.com/437392
IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,ZoomEventsWorkForOffTheRecord)224 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest, ZoomEventsWorkForOffTheRecord) {
225 GURL test_url(url::kAboutBlankURL);
226 std::string test_host(test_url.host());
227 std::string test_scheme(test_url.scheme());
228 Browser* incognito_browser =
229 OpenURLOffTheRecord(browser()->profile(), test_url);
230
231 content::WebContents* web_contents =
232 incognito_browser->tab_strip_model()->GetActiveWebContents();
233
234 content::BrowserContext* context = web_contents->GetBrowserContext();
235 EXPECT_TRUE(context->IsOffTheRecord());
236 ZoomLevelChangeObserver observer(context);
237 HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents);
238
239 double new_zoom_level =
240 host_zoom_map->GetZoomLevelForHostAndScheme(test_scheme, test_host) + 0.5;
241 host_zoom_map->SetZoomLevelForHostAndScheme(test_scheme, test_host,
242 new_zoom_level);
243 observer.BlockUntilZoomLevelForHostHasChanged(test_host);
244 EXPECT_EQ(new_zoom_level, host_zoom_map->GetZoomLevelForHostAndScheme(
245 test_scheme, test_host));
246 }
247
248 #if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,WebviewBasedSigninUsesDefaultStoragePartitionForEmbedder)249 IN_PROC_BROWSER_TEST_F(
250 HostZoomMapBrowserTest,
251 WebviewBasedSigninUsesDefaultStoragePartitionForEmbedder) {
252 GURL signin_url = signin::GetEmbeddedPromoURL(
253 signin_metrics::AccessPoint::ACCESS_POINT_START_PAGE,
254 signin_metrics::Reason::REASON_FORCED_SIGNIN_PRIMARY_ACCOUNT, false);
255 GURL test_url = ConstructTestServerURL(signin_url.spec().c_str());
256 std::string test_host(test_url.host());
257 std::string test_scheme(test_url.scheme());
258 ui_test_utils::NavigateToURL(browser(), test_url);
259
260 content::WebContents* web_contents =
261 browser()->tab_strip_model()->GetActiveWebContents();
262
263 HostZoomMap* host_zoom_map = HostZoomMap::GetForWebContents(web_contents);
264
265 // For the webview based sign-in code, the sign in page uses the default host
266 // zoom map.
267 HostZoomMap* default_profile_host_zoom_map =
268 HostZoomMap::GetDefaultForBrowserContext(browser()->profile());
269 EXPECT_EQ(host_zoom_map, default_profile_host_zoom_map);
270 }
271 #endif
272
273 // Regression test for crbug.com/364399.
IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,ToggleDefaultZoomLevel)274 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest, ToggleDefaultZoomLevel) {
275 const double default_zoom_level = blink::PageZoomFactorToZoomLevel(1.5);
276
277 const char kTestURLTemplate1[] = "http://host1:%u/";
278 const char kTestURLTemplate2[] = "http://host2:%u/";
279
280 ZoomLevelChangeObserver observer(browser()->profile());
281
282 GURL test_url1 = ConstructTestServerURL(kTestURLTemplate1);
283 ui_test_utils::NavigateToURL(browser(), test_url1);
284
285 SetDefaultZoomLevel(default_zoom_level);
286 observer.BlockUntilZoomLevelForHostHasChanged(test_url1.host());
287 EXPECT_TRUE(
288 blink::PageZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url1)));
289
290 GURL test_url2 = ConstructTestServerURL(kTestURLTemplate2);
291 ui_test_utils::NavigateToURLWithDisposition(
292 browser(), test_url2, WindowOpenDisposition::NEW_FOREGROUND_TAB,
293 ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
294 EXPECT_TRUE(
295 blink::PageZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2)));
296
297 content::WebContents* web_contents =
298 browser()->tab_strip_model()->GetActiveWebContents();
299 zoom::PageZoom::Zoom(web_contents, content::PAGE_ZOOM_OUT);
300 observer.BlockUntilZoomLevelForHostHasChanged(test_url2.host());
301 EXPECT_FALSE(
302 blink::PageZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2)));
303
304 zoom::PageZoom::Zoom(web_contents, content::PAGE_ZOOM_IN);
305 observer.BlockUntilZoomLevelForHostHasChanged(test_url2.host());
306 EXPECT_TRUE(
307 blink::PageZoomValuesEqual(default_zoom_level, GetZoomLevel(test_url2)));
308
309 // Now both tabs should be at the default zoom level, so there should not be
310 // any per-host values saved either to Pref, or internally in HostZoomMap.
311 EXPECT_TRUE(GetHostsWithZoomLevels().empty());
312 EXPECT_TRUE(GetHostsWithZoomLevelsFromPrefs().empty());
313 }
314
315 // Test that garbage data from crbug.com/364399 is cleared up on startup.
IN_PROC_BROWSER_TEST_F(HostZoomMapSanitizationBrowserTest,ClearOnStartup)316 IN_PROC_BROWSER_TEST_F(HostZoomMapSanitizationBrowserTest, ClearOnStartup) {
317 EXPECT_THAT(GetHostsWithZoomLevels(), testing::ElementsAre("host2"));
318 EXPECT_THAT(GetHostsWithZoomLevelsFromPrefs(), testing::ElementsAre("host2"));
319 }
320
321 // Test four things:
322 // 1. Host zoom maps of parent profile and child profile are different.
323 // 2. Child host zoom map inherits zoom level at construction.
324 // 3. Change of zoom level doesn't propagate from child to parent.
325 // 4. Change of zoom level propagates from parent to child.
IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,OffTheRecordProfileHostZoomMap)326 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,
327 OffTheRecordProfileHostZoomMap) {
328 // Constants for test case.
329 const std::string host("example.com");
330 const double zoom_level_25 = 2.5;
331 const double zoom_level_30 = 3.0;
332 const double zoom_level_40 = 4.0;
333
334 Profile* parent_profile = browser()->profile();
335 Profile* child_profile =
336 static_cast<ProfileImpl*>(parent_profile)->GetPrimaryOTRProfile();
337 HostZoomMap* parent_zoom_map =
338 HostZoomMap::GetDefaultForBrowserContext(parent_profile);
339 ASSERT_TRUE(parent_zoom_map);
340
341 parent_zoom_map->SetZoomLevelForHost(host, zoom_level_25);
342 ASSERT_EQ(parent_zoom_map->GetZoomLevelForHostAndScheme("http", host),
343 zoom_level_25);
344
345 // Prepare child host zoom map.
346 HostZoomMap* child_zoom_map =
347 HostZoomMap::GetDefaultForBrowserContext(child_profile);
348 ASSERT_TRUE(child_zoom_map);
349
350 // Verify.
351 EXPECT_NE(parent_zoom_map, child_zoom_map);
352
353 EXPECT_EQ(parent_zoom_map->GetZoomLevelForHostAndScheme("http", host),
354 child_zoom_map->GetZoomLevelForHostAndScheme("http", host)) <<
355 "Child must inherit from parent.";
356
357 child_zoom_map->SetZoomLevelForHost(host, zoom_level_30);
358 ASSERT_EQ(
359 child_zoom_map->GetZoomLevelForHostAndScheme("http", host),
360 zoom_level_30);
361
362 EXPECT_NE(parent_zoom_map->GetZoomLevelForHostAndScheme("http", host),
363 child_zoom_map->GetZoomLevelForHostAndScheme("http", host)) <<
364 "Child change must not propagate to parent.";
365
366 parent_zoom_map->SetZoomLevelForHost(host, zoom_level_40);
367 ASSERT_EQ(
368 parent_zoom_map->GetZoomLevelForHostAndScheme("http", host),
369 zoom_level_40);
370
371 EXPECT_EQ(parent_zoom_map->GetZoomLevelForHostAndScheme("http", host),
372 child_zoom_map->GetZoomLevelForHostAndScheme("http", host)) <<
373 "Parent change should propagate to child.";
374 base::RunLoop().RunUntilIdle();
375 }
376
IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,ParentDefaultZoomPropagatesToIncognitoChild)377 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,
378 ParentDefaultZoomPropagatesToIncognitoChild) {
379 Profile* parent_profile = browser()->profile();
380 Profile* child_profile =
381 static_cast<ProfileImpl*>(parent_profile)->GetPrimaryOTRProfile();
382
383 double new_default_zoom_level =
384 parent_profile->GetZoomLevelPrefs()->GetDefaultZoomLevelPref() + 1.f;
385 HostZoomMap* parent_host_zoom_map =
386 HostZoomMap::GetDefaultForBrowserContext(parent_profile);
387 HostZoomMap* child_host_zoom_map =
388 HostZoomMap::GetDefaultForBrowserContext(child_profile);
389 ASSERT_TRUE(parent_host_zoom_map);
390 ASSERT_TRUE(child_host_zoom_map);
391 EXPECT_NE(parent_host_zoom_map, child_host_zoom_map);
392 EXPECT_NE(new_default_zoom_level, child_host_zoom_map->GetDefaultZoomLevel());
393
394 parent_profile->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(
395 new_default_zoom_level);
396 EXPECT_EQ(new_default_zoom_level, child_host_zoom_map->GetDefaultZoomLevel());
397 }
398
399 // TODO(1115597): Flaky on linux and cros.
400 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
401 #define MAYBE_PageScaleIsOneChanged DISABLED_PageScaleIsOneChanged
402 #else
403 #define MAYBE_PageScaleIsOneChanged PageScaleIsOneChanged
404 #endif
IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest,MAYBE_PageScaleIsOneChanged)405 IN_PROC_BROWSER_TEST_F(HostZoomMapBrowserTest, MAYBE_PageScaleIsOneChanged) {
406 GURL test_url(url::kAboutBlankURL);
407 std::string test_host(test_url.host());
408
409 ui_test_utils::NavigateToURL(browser(), test_url);
410 content::WebContents* web_contents =
411 browser()->tab_strip_model()->GetActiveWebContents();
412
413 ASSERT_TRUE(content::HostZoomMap::PageScaleFactorIsOne(web_contents));
414
415 ZoomLevelChangeObserver observer(browser()->profile());
416
417 web_contents->SetPageScale(1.5);
418 observer.BlockUntilZoomLevelForHostHasChanged(test_host);
419 EXPECT_FALSE(content::HostZoomMap::PageScaleFactorIsOne(web_contents));
420
421 web_contents->SetPageScale(1.f);
422 observer.BlockUntilZoomLevelForHostHasChanged(test_host);
423 EXPECT_TRUE(content::HostZoomMap::PageScaleFactorIsOne(web_contents));
424 }
425