1 // Copyright 2020 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 "chrome/browser/ui/views/extensions/settings_overridden_dialog_view.h"
6 
7 #include "base/path_service.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/test/scoped_feature_list.h"
10 #include "base/time/time.h"
11 #include "build/build_config.h"
12 #include "chrome/app/vector_icons/vector_icons.h"
13 #include "chrome/browser/extensions/chrome_test_extension_loader.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/search_engines/template_url_service_factory.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_window.h"
18 #include "chrome/browser/ui/extensions/settings_api_bubble_helpers.h"
19 #include "chrome/browser/ui/extensions/settings_overridden_dialog_controller.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/browser/ui/test/test_browser_dialog.h"
22 #include "chrome/browser/ui/ui_features.h"
23 #include "chrome/common/chrome_paths.h"
24 #include "chrome/common/webui_url_constants.h"
25 #include "chrome/test/base/search_test_utils.h"
26 #include "chrome/test/base/ui_test_utils.h"
27 #include "components/search_engines/search_engines_test_util.h"
28 #include "components/search_engines/template_url.h"
29 #include "components/search_engines/template_url_service.h"
30 #include "content/public/test/browser_test.h"
31 #include "content/public/test/browser_test_utils.h"
32 #include "ui/views/test/widget_test.h"
33 
34 namespace {
35 
36 // A stub dialog controller that displays the dialog with the supplied params.
37 class TestDialogController : public SettingsOverriddenDialogController {
38  public:
TestDialogController(ShowParams show_params,base::Optional<DialogResult> * dialog_result_out)39   TestDialogController(ShowParams show_params,
40                        base::Optional<DialogResult>* dialog_result_out)
41       : show_params_(std::move(show_params)),
42         dialog_result_out_(dialog_result_out) {
43     DCHECK(dialog_result_out_);
44   }
45   TestDialogController(const TestDialogController&) = delete;
46   TestDialogController& operator=(const TestDialogController&) = delete;
47   ~TestDialogController() override = default;
48 
49  private:
ShouldShow()50   bool ShouldShow() override { return true; }
GetShowParams()51   ShowParams GetShowParams() override { return show_params_; }
OnDialogShown()52   void OnDialogShown() override {}
HandleDialogResult(DialogResult result)53   void HandleDialogResult(DialogResult result) override {
54     ASSERT_FALSE(dialog_result_out_->has_value());
55     *dialog_result_out_ = result;
56   }
57 
58   const ShowParams show_params_;
59 
60   // The result to populate. Must outlive this object.
61   base::Optional<DialogResult>* const dialog_result_out_;
62 };
63 
64 }  // namespace
65 
66 class SettingsOverriddenDialogViewBrowserTest : public DialogBrowserTest {
67  public:
68   enum class DefaultSearch {
69     kUseDefault,
70     kUseNonGoogleFromDefaultList,
71     kUseNewSearch,
72   };
73 
SettingsOverriddenDialogViewBrowserTest()74   SettingsOverriddenDialogViewBrowserTest() {
75     scoped_feature_list_.InitAndEnableFeature(
76         features::kExtensionSettingsOverriddenDialogs);
77   }
78   ~SettingsOverriddenDialogViewBrowserTest() override = default;
79 
SetUpOnMainThread()80   void SetUpOnMainThread() override {
81     DialogBrowserTest::SetUpOnMainThread();
82     search_test_utils::WaitForTemplateURLServiceToLoad(
83         TemplateURLServiceFactory::GetForProfile(browser()->profile()));
84   }
85 
ShowUi(const std::string & name)86   void ShowUi(const std::string& name) override {
87     test_name_ = name;
88     if (name == "SimpleDialog") {
89       ShowSimpleDialog(false, browser());
90     } else if (name == "SimpleDialogWithIcon") {
91       ShowSimpleDialog(true, browser());
92     } else if (name == "NtpOverriddenDialog_BackToDefault") {
93       ShowNtpOverriddenDefaultDialog();
94     } else if (name == "NtpOverriddenDialog_Generic") {
95       ShowNtpOverriddenGenericDialog();
96     } else if (name == "SearchOverriddenDialog_BackToGoogle") {
97       ShowSearchOverriddenDialog(DefaultSearch::kUseDefault);
98     } else if (name == "SearchOverriddenDialog_BackToOther") {
99       ShowSearchOverriddenDialog(DefaultSearch::kUseNonGoogleFromDefaultList);
100     } else if (name == "SearchOverriddenDialog_Generic") {
101       ShowSearchOverriddenDialog(DefaultSearch::kUseNewSearch);
102     } else {
103       NOTREACHED() << name;
104     }
105   }
106 
107   // Creates, shows, and returns a dialog anchored to the given |browser|. The
108   // dialog is owned by the views framework.
ShowSimpleDialog(bool show_icon,Browser * browser)109   SettingsOverriddenDialogView* ShowSimpleDialog(bool show_icon,
110                                                  Browser* browser) {
111     SettingsOverriddenDialogController::ShowParams params{
112         base::ASCIIToUTF16("Settings overridden dialog title"),
113         base::ASCIIToUTF16(
114             "Settings overriden dialog body, which is quite a bit "
115             "longer than the title alone")};
116     if (show_icon)
117       params.icon = &kProductIcon;
118     auto* dialog =
119         new SettingsOverriddenDialogView(std::make_unique<TestDialogController>(
120             std::move(params), &dialog_result_));
121     dialog->Show(browser->window()->GetNativeWindow());
122 
123     return dialog;
124   }
125 
ShowNtpOverriddenDefaultDialog()126   void ShowNtpOverriddenDefaultDialog() {
127     // Load an extension overriding the NTP and open a new tab to trigger the
128     // dialog.
129     LoadExtensionOverridingNewTab();
130     NavigateToNewTab();
131   }
132 
ShowNtpOverriddenGenericDialog()133   void ShowNtpOverriddenGenericDialog() {
134     SetNewSearchProvider(DefaultSearch::kUseNonGoogleFromDefaultList);
135     LoadExtensionOverridingNewTab();
136     NavigateToNewTab();
137   }
138 
ShowSearchOverriddenDialog(DefaultSearch search)139   void ShowSearchOverriddenDialog(DefaultSearch search) {
140     SetNewSearchProvider(search);
141     LoadExtensionOverridingSearch();
142     PerformSearchFromOmnibox();
143   }
144 
VerifyUi()145   bool VerifyUi() override {
146     if (!DialogBrowserTest::VerifyUi())
147       return false;
148 
149     if (base::StartsWith(test_name_, "SearchOverriddenDialog",
150                          base::CompareCase::SENSITIVE)) {
151       // Note: Because this is a test, we don't actually expect this navigation
152       // to succeed. But we can still check that the user was sent to
153       // example.com (the new search engine).
154       EXPECT_EQ("www.example.com", browser()
155                                        ->tab_strip_model()
156                                        ->GetActiveWebContents()
157                                        ->GetLastCommittedURL()
158                                        .host_piece());
159     }
160 
161     return true;
162   }
163 
164   base::Optional<SettingsOverriddenDialogController::DialogResult>
dialog_result() const165   dialog_result() const {
166     return dialog_result_;
167   }
168 
169  private:
LoadExtensionOverridingNewTab()170   void LoadExtensionOverridingNewTab() {
171     base::FilePath test_root_path;
172     ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_root_path));
173 
174     Profile* const profile = browser()->profile();
175     scoped_refptr<const extensions::Extension> extension =
176         extensions::ChromeTestExtensionLoader(profile).LoadExtension(
177             test_root_path.AppendASCII("extensions/api_test/override/newtab"));
178     ASSERT_TRUE(extension);
179   }
180 
LoadExtensionOverridingSearch()181   void LoadExtensionOverridingSearch() {
182     base::FilePath test_root_path;
183     ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_root_path));
184 
185     Profile* const profile = browser()->profile();
186     scoped_refptr<const extensions::Extension> extension =
187         extensions::ChromeTestExtensionLoader(profile).LoadExtension(
188             test_root_path.AppendASCII("extensions/search_provider_override"));
189     ASSERT_TRUE(extension);
190   }
191 
NavigateToNewTab()192   void NavigateToNewTab() {
193     ui_test_utils::NavigateToURLWithDisposition(
194         browser(), GURL(chrome::kChromeUINewTabURL),
195         WindowOpenDisposition::NEW_FOREGROUND_TAB,
196         ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
197   }
198 
SetNewSearchProvider(DefaultSearch search)199   void SetNewSearchProvider(DefaultSearch search) {
200     if (search == DefaultSearch::kUseDefault)
201       return;
202 
203     TemplateURLService* const template_url_service =
204         TemplateURLServiceFactory::GetForProfile(browser()->profile());
205 
206     bool new_search_shows_in_default_list = true;
207     // If the test requires a search engine that doesn't show in the default
208     // list, we need to add one.
209     if (search == DefaultSearch::kUseNewSearch) {
210       new_search_shows_in_default_list = false;
211       template_url_service->Add(
212           std::make_unique<TemplateURL>(*GenerateDummyTemplateURLData("test")));
213     }
214 
215     TemplateURLService::TemplateURLVector template_urls =
216         template_url_service->GetTemplateURLs();
217     auto iter =
218         std::find_if(template_urls.begin(), template_urls.end(),
219                      [template_url_service, new_search_shows_in_default_list](
220                          const TemplateURL* turl) {
221                        return !turl->HasGoogleBaseURLs(
222                                   template_url_service->search_terms_data()) &&
223                               template_url_service->ShowInDefaultList(turl) ==
224                                   new_search_shows_in_default_list;
225                      });
226     ASSERT_TRUE(iter != template_urls.end());
227 
228     template_url_service->SetUserSelectedDefaultSearchProvider(*iter);
229   }
230 
PerformSearchFromOmnibox()231   void PerformSearchFromOmnibox() {
232     ui_test_utils::SendToOmniboxAndSubmit(browser(), "Penguin",
233                                           base::TimeTicks::Now());
234     content::WaitForLoadStop(
235         browser()->tab_strip_model()->GetActiveWebContents());
236   }
237 
238   std::string test_name_;
239 
240   base::Optional<SettingsOverriddenDialogController::DialogResult>
241       dialog_result_;
242 
243   base::test::ScopedFeatureList scoped_feature_list_;
244 };
245 
246 ////////////////////////////////////////////////////////////////////////////////
247 // UI Browser Tests
248 
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_SimpleDialog)249 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
250                        InvokeUi_SimpleDialog) {
251   ShowAndVerifyUi();
252 }
253 
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_SimpleDialogWithIcon)254 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
255                        InvokeUi_SimpleDialogWithIcon) {
256   ShowAndVerifyUi();
257 }
258 
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_NtpOverriddenDialog_BackToDefault)259 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
260                        InvokeUi_NtpOverriddenDialog_BackToDefault) {
261   // Force the post-install NTP UI to be enabled, so that we can test on all
262   // platforms.
263   extensions::SetNtpPostInstallUiEnabledForTesting(true);
264   ShowAndVerifyUi();
265   extensions::SetNtpPostInstallUiEnabledForTesting(false);
266 }
267 
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_NtpOverriddenDialog_Generic)268 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
269                        InvokeUi_NtpOverriddenDialog_Generic) {
270   // Force the post-install NTP UI to be enabled, so that we can test on all
271   // platforms.
272   extensions::SetNtpPostInstallUiEnabledForTesting(true);
273   ShowAndVerifyUi();
274   extensions::SetNtpPostInstallUiEnabledForTesting(false);
275 }
276 
277 // The chrome_settings_overrides API that allows extensions to override the
278 // default search provider is only available on Windows and Mac.
279 #if defined(OS_WIN) || defined(OS_MAC)
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_SearchOverriddenDialog_BackToGoogle)280 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
281                        InvokeUi_SearchOverriddenDialog_BackToGoogle) {
282   ShowAndVerifyUi();
283 }
284 
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_SearchOverriddenDialog_BackToOther)285 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
286                        InvokeUi_SearchOverriddenDialog_BackToOther) {
287   ShowAndVerifyUi();
288 }
289 
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,InvokeUi_SearchOverriddenDialog_Generic)290 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
291                        InvokeUi_SearchOverriddenDialog_Generic) {
292   ShowAndVerifyUi();
293 }
294 #endif  // defined(OS_WIN) || defined(OS_MAC)
295 
296 ////////////////////////////////////////////////////////////////////////////////
297 // Functional Browser Tests
298 
299 // Verify that if the parent window is closed, the dialog notifies the
300 // controller that it was closed without any user action.
IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,DialogWindowClosed)301 IN_PROC_BROWSER_TEST_F(SettingsOverriddenDialogViewBrowserTest,
302                        DialogWindowClosed) {
303   Browser* second_browser = CreateBrowser(browser()->profile());
304   ASSERT_TRUE(second_browser);
305 
306   SettingsOverriddenDialogView* dialog =
307       ShowSimpleDialog(false, second_browser);
308 
309   views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
310       dialog->GetWidget());
311   CloseBrowserSynchronously(second_browser);
312   widget_destroyed_waiter.Wait();
313   ASSERT_TRUE(dialog_result());
314   EXPECT_EQ(SettingsOverriddenDialogController::DialogResult::
315                 kDialogClosedWithoutUserAction,
316             *dialog_result());
317 }
318