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