1 // Copyright 2018 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 <memory>
6 #include <string>
7 
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/path_service.h"
11 #include "base/run_loop.h"
12 #include "base/test/bind.h"
13 #include "base/test/metrics/histogram_tester.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "base/time/time.h"
16 #include "base/util/values/values_util.h"
17 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
18 #include "chrome/browser/devtools/devtools_window_testing.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/hats/hats_service.h"
21 #include "chrome/browser/ui/hats/hats_service_factory.h"
22 #include "chrome/browser/ui/hats/mock_hats_service.h"
23 #include "chrome/browser/ui/test/test_browser_dialog.h"
24 #include "chrome/browser/ui/views/frame/browser_view.h"
25 #include "chrome/browser/ui/views/hats/hats_bubble_view.h"
26 #include "chrome/browser/ui/views/hats/hats_next_web_dialog.h"
27 #include "chrome/browser/ui/views/hats/hats_web_dialog.h"
28 #include "chrome/common/chrome_features.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/test/base/ui_test_utils.h"
32 #include "components/content_settings/core/browser/host_content_settings_map.h"
33 #include "components/version_info/version_info.h"
34 #include "content/public/test/browser_test.h"
35 #include "testing/gmock/include/gmock/gmock.h"
36 #include "url/gurl.h"
37 
38 class HatsBubbleTest : public DialogBrowserTest {
39  public:
HatsBubbleTest()40   HatsBubbleTest() {}
41 
42   // DialogBrowserTest:
ShowUi(const std::string & name)43   void ShowUi(const std::string& name) override {
44     ASSERT_TRUE(browser()->is_type_normal());
45     BrowserView::GetBrowserViewForBrowser(InProcessBrowserTest::browser())
46         ->ShowHatsBubble("test_site_id", base::DoNothing(), base::DoNothing());
47   }
48 
49  private:
50   DISALLOW_COPY_AND_ASSIGN(HatsBubbleTest);
51 };
52 
53 class TestHatsWebDialog : public HatsWebDialog {
54  public:
TestHatsWebDialog(Browser * browser,const base::TimeDelta & timeout,const GURL & url)55   TestHatsWebDialog(Browser* browser,
56                     const base::TimeDelta& timeout,
57                     const GURL& url)
58       : HatsWebDialog(browser, "fake_id_not_used"),
59         loading_timeout_(timeout),
60         content_url_(url) {}
61 
62   // ui::WebDialogDelegate implementation.
GetDialogContentURL() const63   GURL GetDialogContentURL() const override {
64     if (content_url_.is_valid()) {
65       // When we have a valid overridden url, use it instead.
66       return content_url_;
67     }
68     return HatsWebDialog::GetDialogContentURL();
69   }
70 
OnMainFrameResourceLoadComplete(const blink::mojom::ResourceLoadInfo & resource_load_info)71   void OnMainFrameResourceLoadComplete(
72       const blink::mojom::ResourceLoadInfo& resource_load_info) {
73     if (resource_load_info.net_error == net::Error::OK &&
74         resource_load_info.original_url == resource_url_) {
75       // The resource is loaded successfully.
76       resource_loaded_ = true;
77     }
78   }
79 
set_resource_url(const GURL & url)80   void set_resource_url(const GURL& url) { resource_url_ = url; }
resource_loaded()81   bool resource_loaded() { return resource_loaded_; }
82 
83   MOCK_METHOD0(OnWebContentsFinishedLoad, void());
84   MOCK_METHOD0(OnLoadTimedOut, void());
85 
86  private:
ContentLoadingTimeout() const87   const base::TimeDelta ContentLoadingTimeout() const override {
88     return loading_timeout_;
89   }
90 
91   base::TimeDelta loading_timeout_;
92   GURL content_url_;
93   GURL resource_url_;
94 };
95 
96 class HatsWebDialogBrowserTest : public InProcessBrowserTest {
97  public:
HatsWebDialogBrowserTest()98   HatsWebDialogBrowserTest() {
99     feature_list_.InitAndDisableFeature(
100         features::kHappinessTrackingSurveysForDesktopMigration);
101   }
102 
Create(Browser * browser,const base::TimeDelta & timeout,const GURL & url=GURL ())103   TestHatsWebDialog* Create(Browser* browser,
104                             const base::TimeDelta& timeout,
105                             const GURL& url = GURL()) {
106     auto* hats_dialog = new TestHatsWebDialog(browser, timeout, url);
107     hats_dialog->CreateWebDialog(browser);
108     return hats_dialog;
109   }
110 
111  private:
112   base::test::ScopedFeatureList feature_list_;
113 };
114 
115 // Test that calls ShowUi("default").
IN_PROC_BROWSER_TEST_F(HatsBubbleTest,InvokeUi_Default)116 IN_PROC_BROWSER_TEST_F(HatsBubbleTest, InvokeUi_Default) {
117   ShowAndVerifyUi();
118 }
119 
120 // Test time out of preloading works.
IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest,Timeout)121 IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest, Timeout) {
122   TestHatsWebDialog* dialog = Create(browser(), base::TimeDelta());
123   EXPECT_CALL(*dialog, OnLoadTimedOut).Times(1);
124 }
125 
126 // Test preloading content works.
IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest,ContentPreloading)127 IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest, ContentPreloading) {
128   base::FilePath test_data_dir;
129   base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
130   std::string contents;
131   {
132     base::ScopedAllowBlockingForTesting allow_blocking;
133     EXPECT_TRUE(base::ReadFileToString(test_data_dir.AppendASCII("simple.html"),
134                                        &contents));
135   }
136 
137   TestHatsWebDialog* dialog =
138       Create(browser(), base::TimeDelta::FromSeconds(100),
139              GURL("data:text/html;charset=utf-8," + contents));
140   base::RunLoop run_loop;
141   EXPECT_CALL(*dialog, OnWebContentsFinishedLoad)
142       .WillOnce(testing::Invoke(&run_loop, &base::RunLoop::Quit));
143   run_loop.Run();
144 }
145 
146 // Test the correct state will be set when the resource fails to load.
147 // Load with_inline_js.html which has an inline javascript that points to a
148 // nonexistent file.
IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest,LoadFailureInPreloading)149 IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest, LoadFailureInPreloading) {
150   base::FilePath test_data_dir;
151   base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
152   std::string contents;
153   {
154     base::ScopedAllowBlockingForTesting allow_blocking;
155     EXPECT_TRUE(base::ReadFileToString(
156         test_data_dir.AppendASCII("hats").AppendASCII("with_inline_js.html"),
157         &contents));
158   }
159 
160   ASSERT_TRUE(embedded_test_server()->Start());
161 
162   constexpr char kJSPath[] = "/hats/nonexistent.js";
163   constexpr char kSrcPlaceholder[] = "$JS_SRC";
164   GURL url = embedded_test_server()->GetURL(kJSPath);
165   size_t pos = contents.find(kSrcPlaceholder);
166   EXPECT_NE(pos, std::string::npos);
167   contents.replace(pos, strlen(kSrcPlaceholder), url.spec());
168 
169   TestHatsWebDialog* dialog =
170       Create(browser(), base::TimeDelta::FromSeconds(100),
171              GURL("data:text/html;charset=utf-8," + contents));
172   dialog->set_resource_url(url);
173   base::RunLoop run_loop;
174   EXPECT_CALL(*dialog, OnWebContentsFinishedLoad)
175       .WillOnce(testing::Invoke([dialog, &run_loop]() {
176         EXPECT_FALSE(dialog->resource_loaded());
177         run_loop.Quit();
178       }));
179   run_loop.Run();
180 }
181 
182 // Test cookies aren't blocked.
IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest,Cookies)183 IN_PROC_BROWSER_TEST_F(HatsWebDialogBrowserTest, Cookies) {
184   auto* settings_map =
185       HostContentSettingsMapFactory::GetForProfile(browser()->profile());
186   settings_map->SetDefaultContentSetting(ContentSettingsType::COOKIES,
187                                          CONTENT_SETTING_BLOCK);
188 
189   TestHatsWebDialog* dialog =
190       Create(browser(), base::TimeDelta::FromSeconds(100));
191 
192   settings_map = HostContentSettingsMapFactory::GetForProfile(
193       dialog->otr_profile_for_testing());
194   GURL url1("https://survey.google.com/");
195   GURL url2("https://survey.g.doubleclick.net/");
196   EXPECT_EQ(CONTENT_SETTING_ALLOW,
197             settings_map->GetContentSetting(url1, url1,
198                                             ContentSettingsType::COOKIES));
199   EXPECT_EQ(CONTENT_SETTING_ALLOW,
200             settings_map->GetContentSetting(url2, url2,
201                                             ContentSettingsType::COOKIES));
202 }
203 
204 class MockHatsNextWebDialog : public HatsNextWebDialog {
205  public:
MockHatsNextWebDialog(Browser * browser,const std::string & trigger_id,const GURL & hats_survey_url,const base::TimeDelta & timeout,base::OnceClosure success_callback,base::OnceClosure failure_callback)206   MockHatsNextWebDialog(Browser* browser,
207                         const std::string& trigger_id,
208                         const GURL& hats_survey_url,
209                         const base::TimeDelta& timeout,
210                         base::OnceClosure success_callback,
211                         base::OnceClosure failure_callback)
212       : HatsNextWebDialog(browser,
213                           trigger_id,
214                           hats_survey_url,
215                           timeout,
216                           std::move(success_callback),
217                           std::move(failure_callback)) {}
218 
219   MOCK_METHOD0(ShowWidget, void());
220   MOCK_METHOD0(CloseWidget, void());
221   MOCK_METHOD0(UpdateWidgetSize, void());
222 
WaitForClose()223   void WaitForClose() {
224     base::RunLoop run_loop;
225     EXPECT_CALL(*this, CloseWidget).WillOnce([&]() {
226       widget_->Close();
227       run_loop.Quit();
228     });
229     run_loop.Run();
230   }
231 
WaitForUpdateWidgetSize()232   void WaitForUpdateWidgetSize() {
233     base::RunLoop run_loop;
234     EXPECT_CALL(*this, UpdateWidgetSize).WillOnce(testing::Invoke([&run_loop] {
235       run_loop.Quit();
236     }));
237     run_loop.Run();
238   }
239 };
240 
241 class HatsNextWebDialogBrowserTest : public InProcessBrowserTest {
242  public:
HatsNextWebDialogBrowserTest()243   HatsNextWebDialogBrowserTest() {
244     feature_list_.InitAndEnableFeature(
245         features::kHappinessTrackingSurveysForDesktopMigration);
246   }
247 
SetUpOnMainThread()248   void SetUpOnMainThread() override {
249     hats_service_ = static_cast<MockHatsService*>(
250         HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
251             browser()->profile(), base::BindRepeating(&BuildMockHatsService)));
252   }
253 
254   // Open a blank tab in the main browser, inspect it, and return the devtools
255   // Browser for the undocked devtools window.
OpenUndockedDevToolsWindow()256   Browser* OpenUndockedDevToolsWindow() {
257     ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
258 
259     const bool is_docked = false;
260     DevToolsWindow* devtools_window =
261         DevToolsWindowTesting::OpenDevToolsWindowSync(browser(), is_docked);
262     return devtools_window->browser_;
263   }
264 
hats_service()265   MockHatsService* hats_service() { return hats_service_; }
266 
GetSuccessClosure()267   base::OnceClosure GetSuccessClosure() {
268     return base::BindLambdaForTesting([&]() { ++success_count; });
269   }
270 
GetFailureClosure()271   base::OnceClosure GetFailureClosure() {
272     return base::BindLambdaForTesting([&]() { ++failure_count; });
273   }
274 
275   int success_count = 0;
276   int failure_count = 0;
277 
278  private:
279   base::test::ScopedFeatureList feature_list_;
280   MockHatsService* hats_service_;
281 };
282 
283 // Test that the web dialog correctly receives change to history state that
284 // indicates a survey is ready to be shown.
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,SurveyLoaded)285 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, SurveyLoaded) {
286   ASSERT_TRUE(embedded_test_server()->Start());
287 
288   // Use the preference path constants defined in hats_service.cc.
289   const std::string kLastSurveyStartedTime =
290       std::string(kHatsSurveyTriggerTesting) + ".last_survey_started_time";
291   const std::string kLastMajorVersion =
292       std::string(kHatsSurveyTriggerTesting) + ".last_major_version";
293 
294   auto* dialog = new MockHatsNextWebDialog(
295       browser(), kHatsNextSurveyTriggerIDTesting,
296       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
297       base::TimeDelta::FromSeconds(100), GetSuccessClosure(),
298       GetFailureClosure());
299 
300   // Check that no record of a survey being shown is present.
301   const base::DictionaryValue* pref_data =
302       browser()->profile()->GetPrefs()->GetDictionary(
303           prefs::kHatsSurveyMetadata);
304   base::Optional<base::Time> last_survey_started_time =
305       util::ValueToTime(pref_data->FindPath(kLastSurveyStartedTime));
306   base::Optional<int> last_major_version =
307       pref_data->FindIntPath(kLastMajorVersion);
308   ASSERT_FALSE(last_survey_started_time.has_value());
309   ASSERT_FALSE(last_major_version.has_value());
310 
311   // The hats_next_mock.html will provide a state update to the dialog to
312   // indicate that the survey has been loaded.
313   base::RunLoop run_loop;
314   EXPECT_CALL(*dialog, ShowWidget)
315       .WillOnce(testing::Invoke([dialog, &run_loop]() {
316         EXPECT_FALSE(dialog->IsWaitingForSurveyForTesting());
317         run_loop.Quit();
318       }));
319   run_loop.Run();
320 
321   EXPECT_EQ(1, success_count);
322   EXPECT_EQ(0, failure_count);
323 
324   // Check that a record of the survey being shown has been recorded.
325   pref_data = browser()->profile()->GetPrefs()->GetDictionary(
326       prefs::kHatsSurveyMetadata);
327   last_survey_started_time =
328       util::ValueToTime(pref_data->FindPath(kLastSurveyStartedTime));
329   last_major_version = pref_data->FindIntPath(kLastMajorVersion);
330   ASSERT_TRUE(last_survey_started_time.has_value());
331   ASSERT_TRUE(last_major_version.has_value());
332   ASSERT_EQ(static_cast<uint32_t>(*last_major_version),
333             version_info::GetVersion().components()[0]);
334 }
335 
336 // Test that the web dialog correctly receives change to history state that
337 // indicates the survey window should be closed.
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,SurveyClosed)338 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, SurveyClosed) {
339   ASSERT_TRUE(embedded_test_server()->Start());
340   base::HistogramTester histogram_tester;
341 
342   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
343   auto* dialog = new MockHatsNextWebDialog(
344       browser(), "close_for_testing",
345       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
346       base::TimeDelta::FromSeconds(100), GetSuccessClosure(),
347       GetFailureClosure());
348 
349   // The hats_next_mock.html will provide a state update to the dialog to
350   // indicate that the survey window should be closed.
351   dialog->WaitForClose();
352 
353   EXPECT_EQ(0, success_count);
354   EXPECT_EQ(1, failure_count);
355 
356   // Because no loaded state was provided, only a rejection should be recorded.
357   histogram_tester.ExpectUniqueSample(
358       kHatsShouldShowSurveyReasonHistogram,
359       HatsService::ShouldShowSurveyReasons::kNoRejectedByHatsService, 1);
360 }
361 
362 // Test that a survey which first reports as loaded, then reports closure, only
363 // logs that the survey was shown.
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,SurveyLoadedThenClosed)364 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, SurveyLoadedThenClosed) {
365   ASSERT_TRUE(embedded_test_server()->Start());
366   base::HistogramTester histogram_tester;
367 
368   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
369   auto* dialog = new MockHatsNextWebDialog(
370       browser(), kHatsNextSurveyTriggerIDTesting,
371       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
372       base::TimeDelta::FromSeconds(100), GetSuccessClosure(),
373       GetFailureClosure());
374   dialog->WaitForClose();
375 
376   EXPECT_EQ(1, success_count);
377   EXPECT_EQ(0, failure_count);
378 
379   // The only recorded sample should indicate that the survey was shown.
380   histogram_tester.ExpectUniqueSample(
381       kHatsShouldShowSurveyReasonHistogram,
382       HatsService::ShouldShowSurveyReasons::kYes, 1);
383 }
384 
385 // Test that if the survey does not indicate it is ready for display before the
386 // timeout the widget is closed.
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,SurveyTimeout)387 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, SurveyTimeout) {
388   ASSERT_TRUE(embedded_test_server()->Start());
389   base::HistogramTester histogram_tester;
390 
391   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
392   auto* dialog = new MockHatsNextWebDialog(
393       browser(), "invalid_test",
394       embedded_test_server()->GetURL("/hats/non_existent.html"),
395       base::TimeDelta::FromMilliseconds(1), GetSuccessClosure(),
396       GetFailureClosure());
397 
398   dialog->WaitForClose();
399 
400   EXPECT_EQ(0, success_count);
401   EXPECT_EQ(1, failure_count);
402   histogram_tester.ExpectUniqueSample(
403       kHatsShouldShowSurveyReasonHistogram,
404       HatsService::ShouldShowSurveyReasons::kNoSurveyUnreachable, 1);
405 }
406 
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,UnknownURLFragment)407 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, UnknownURLFragment) {
408   ASSERT_TRUE(embedded_test_server()->Start());
409 
410   // Check that providing an unknown URL fragment results in the dialog being
411   // closed.
412   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
413   auto* dialog = new MockHatsNextWebDialog(
414       browser(), "invalid_url_fragment_for_testing",
415       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
416       base::TimeDelta::FromSeconds(100), GetSuccessClosure(),
417       GetFailureClosure());
418 
419   dialog->WaitForClose();
420   EXPECT_EQ(0, success_count);
421   EXPECT_EQ(1, failure_count);
422 }
423 
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,NewWebContents)424 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, NewWebContents) {
425   ASSERT_TRUE(embedded_test_server()->Start());
426 
427   auto* dialog = new MockHatsNextWebDialog(
428       browser(), "open_new_web_contents_for_testing",
429       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
430       base::TimeDelta::FromSeconds(100), base::DoNothing(), base::DoNothing());
431 
432   // The mock hats dialog will push a close state after it has attempted to
433   // open another web contents.
434   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
435   dialog->WaitForClose();
436 
437   // Check that a tab with http://foo.com (defined in hats_next_mock.html) has
438   // been opened in the regular browser and is active.
439   EXPECT_EQ(
440       GURL("http://foo.com"),
441       browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
442 }
443 
444 // The devtools browser for undocked devtools has no tab strip and can't open
445 // new tabs. Instead it should open new WebContents in the main browser.
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,NewWebContentsForDevtoolsBrowser)446 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,
447                        NewWebContentsForDevtoolsBrowser) {
448   ASSERT_TRUE(embedded_test_server()->Start());
449 
450   Browser* devtools_browser = OpenUndockedDevToolsWindow();
451 
452   auto* dialog = new MockHatsNextWebDialog(
453       devtools_browser, "open_new_web_contents_for_testing",
454       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
455       base::TimeDelta::FromSeconds(100), base::DoNothing(), base::DoNothing());
456 
457   // The mock hats dialog will push a close state after it has attempted to
458   // open another web contents.
459   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
460   dialog->WaitForClose();
461 
462   // Check that a tab with http://foo.com (defined in hats_next_mock.html) has
463   // been opened in the regular browser and is active.
464   EXPECT_EQ(
465       GURL("http://foo.com"),
466       browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
467 }
468 
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,DialogResize)469 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, DialogResize) {
470   ASSERT_TRUE(embedded_test_server()->Start());
471 
472   auto* dialog = new MockHatsNextWebDialog(
473       browser(), "resize_for_testing",
474       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
475       base::TimeDelta::FromSeconds(100), base::DoNothing(), base::DoNothing());
476 
477   // Check that the dialog reports a preferred size the same as the size defined
478   // in hats_next_mock.html.
479   constexpr auto kTargetSize = gfx::Size(70, 300);
480 
481   // Depending on renderer warm-up, an initial empty size may additionally be
482   // reported before hats_next_mock.html has had a chance to resize.
483   dialog->WaitForUpdateWidgetSize();
484   auto size = dialog->CalculatePreferredSize();
485   EXPECT_TRUE(size == kTargetSize || size == dialog->kMinSize);
486   if (size != kTargetSize) {
487     dialog->WaitForUpdateWidgetSize();
488     EXPECT_EQ(kTargetSize, dialog->CalculatePreferredSize());
489   }
490 }
491 
IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest,MaximumSize)492 IN_PROC_BROWSER_TEST_F(HatsNextWebDialogBrowserTest, MaximumSize) {
493   ASSERT_TRUE(embedded_test_server()->Start());
494 
495   EXPECT_CALL(*hats_service(), HatsNextDialogClosed);
496   auto* dialog = new MockHatsNextWebDialog(
497       browser(), "resize_to_large_for_testing",
498       embedded_test_server()->GetURL("/hats/hats_next_mock.html"),
499       base::TimeDelta::FromSeconds(100), base::DoNothing(), base::DoNothing());
500 
501   // Check that the maximum size of the dialog is bounded appropriately by the
502   // dialogs maximum size. Depending on renderer warm-up, an initial empty size
503   // may additionally be reported before hats_next_mock.html has had a chance
504   // to resize.
505   dialog->WaitForUpdateWidgetSize();
506   auto size = dialog->CalculatePreferredSize();
507   EXPECT_TRUE(size == HatsNextWebDialog::kMaxSize || size == dialog->kMinSize);
508   if (size != HatsNextWebDialog::kMaxSize) {
509     dialog->WaitForUpdateWidgetSize();
510     EXPECT_EQ(HatsNextWebDialog::kMaxSize, dialog->CalculatePreferredSize());
511   }
512 }
513