1 // Copyright 2016 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 "build/build_config.h"
6 #include "chrome/browser/extensions/extension_browsertest.h"
7 #include "chrome/browser/extensions/extension_tab_util.h"
8 #include "chrome/browser/ui/browser.h"
9 #include "chrome/browser/ui/browser_finder.h"
10 #include "chrome/browser/ui/browser_list.h"
11 #include "chrome/browser/ui/tabs/tab_strip_model.h"
12 #include "chrome/common/webui_url_constants.h"
13 #include "chrome/test/base/ui_test_utils.h"
14 #include "content/public/test/browser_test.h"
15 #include "content/public/test/browser_test_utils.h"
16 #include "extensions/common/manifest_handlers/options_page_info.h"
17 
18 namespace extensions {
19 
20 namespace {
21 
GetActiveUrl(Browser * browser)22 const GURL& GetActiveUrl(Browser* browser) {
23   return browser->tab_strip_model()
24       ->GetActiveWebContents()
25       ->GetLastCommittedURL();
26 }
27 
28 }  // namespace
29 
30 using ExtensionTabUtilBrowserTest = ExtensionBrowserTest;
31 
32 // Times out on Win debug. https://crbug.com/811471
33 #if defined(OS_WIN) && !defined(NDEBUG)
34 #define MAYBE_OpenExtensionsOptionsPage DISABLED_OpenExtensionsOptionsPage
35 #else
36 #define MAYBE_OpenExtensionsOptionsPage OpenExtensionsOptionsPage
37 #endif
38 
IN_PROC_BROWSER_TEST_F(ExtensionTabUtilBrowserTest,MAYBE_OpenExtensionsOptionsPage)39 IN_PROC_BROWSER_TEST_F(ExtensionTabUtilBrowserTest,
40                        MAYBE_OpenExtensionsOptionsPage) {
41   // Load an extension with an options page that opens in a tab and one that
42   // opens in the chrome://extensions page in a view.
43   const Extension* options_in_tab =
44       LoadExtension(test_data_dir_.AppendASCII("options_page"));
45   const Extension* options_in_view =
46       LoadExtension(test_data_dir_.AppendASCII("options_page_in_view"));
47   ASSERT_TRUE(options_in_tab);
48   ASSERT_TRUE(options_in_view);
49   ASSERT_TRUE(OptionsPageInfo::HasOptionsPage(options_in_tab));
50   ASSERT_TRUE(OptionsPageInfo::HasOptionsPage(options_in_view));
51 
52   // Start at the new tab page, and then open the extension options page.
53   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
54   EXPECT_EQ(1, browser()->tab_strip_model()->count());
55   GURL options_url = OptionsPageInfo::GetOptionsPage(options_in_tab);
56   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_tab, browser()));
57 
58   // Opening the options page should take the new tab and use it, so we should
59   // have only one tab, and it should be open to the options page.
60   EXPECT_EQ(1, browser()->tab_strip_model()->count());
61   EXPECT_TRUE(content::WaitForLoadStop(
62                   browser()->tab_strip_model()->GetActiveWebContents()));
63   EXPECT_EQ(options_url, GetActiveUrl(browser()));
64 
65   // Calling OpenOptionsPage again shouldn't result in any new tabs, since we
66   // re-use the existing options page.
67   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_tab, browser()));
68   EXPECT_EQ(1, browser()->tab_strip_model()->count());
69   EXPECT_TRUE(content::WaitForLoadStop(
70                   browser()->tab_strip_model()->GetActiveWebContents()));
71   EXPECT_EQ(options_url, GetActiveUrl(browser()));
72 
73   // Navigate to google.com (something non-newtab, non-options). Calling
74   // OpenOptionsPage() should create a new tab and navigate it to the options
75   // page. So we should have two total tabs, with the active tab pointing to
76   // options.
77   ui_test_utils::NavigateToURL(browser(), GURL("http://www.google.com/"));
78   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_tab, browser()));
79   EXPECT_EQ(2, browser()->tab_strip_model()->count());
80   EXPECT_TRUE(content::WaitForLoadStop(
81                   browser()->tab_strip_model()->GetActiveWebContents()));
82   EXPECT_EQ(options_url, GetActiveUrl(browser()));
83 
84   // Navigate the tab to a different extension URL, and call OpenOptionsPage().
85   // We should not reuse the current tab since it's opened to a page that isn't
86   // the options page, and we don't want to arbitrarily close extension content.
87   // Regression test for crbug.com/587581.
88   ui_test_utils::NavigateToURL(browser(),
89                                options_in_tab->GetResourceURL("other.html"));
90   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_tab, browser()));
91   EXPECT_EQ(3, browser()->tab_strip_model()->count());
92   EXPECT_TRUE(content::WaitForLoadStop(
93                   browser()->tab_strip_model()->GetActiveWebContents()));
94   EXPECT_EQ(options_url, GetActiveUrl(browser()));
95 
96   // If the user navigates to the options page e.g. by typing in the url, it
97   // should not override the currently-open tab.
98   ui_test_utils::NavigateToURLWithDisposition(
99       browser(), options_url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
100       ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
101   EXPECT_EQ(4, browser()->tab_strip_model()->count());
102   EXPECT_EQ(options_url, GetActiveUrl(browser()));
103 
104   // Test the extension that has the options page open in a view inside
105   // chrome://extensions.
106   // Triggering OpenOptionsPage() should create a new tab, since there are none
107   // to override.
108   options_url = GURL(std::string(chrome::kChromeUIExtensionsURL) +
109                      "?options=" + options_in_view->id());
110   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_view, browser()));
111   EXPECT_EQ(5, browser()->tab_strip_model()->count());
112   EXPECT_TRUE(content::WaitForLoadStop(
113       browser()->tab_strip_model()->GetActiveWebContents()));
114   EXPECT_EQ(options_url, GetActiveUrl(browser()));
115 
116   // Calling it a second time should not create a new tab, since one already
117   // exists with that options page open.
118   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_view, browser()));
119   EXPECT_EQ(5, browser()->tab_strip_model()->count());
120   EXPECT_TRUE(content::WaitForLoadStop(
121       browser()->tab_strip_model()->GetActiveWebContents()));
122   EXPECT_EQ(options_url, GetActiveUrl(browser()));
123 
124   // Navigate to chrome://extensions (no options). Calling OpenOptionsPage()
125   // should override that tab rather than opening a new tab. crbug.com/595253.
126   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIExtensionsURL));
127   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPage(options_in_view, browser()));
128   EXPECT_EQ(5, browser()->tab_strip_model()->count());
129   EXPECT_TRUE(content::WaitForLoadStop(
130       browser()->tab_strip_model()->GetActiveWebContents()));
131   EXPECT_EQ(options_url, GetActiveUrl(browser()));
132 }
133 
134 // Flaky on Windows: http://crbug.com/745729
135 #if defined(OS_WIN)
136 #define MAYBE_OpenSplitModeExtensionOptionsPageIncognito \
137   DISABLED_OpenSplitModeExtensionOptionsPageIncognito
138 #else
139 #define MAYBE_OpenSplitModeExtensionOptionsPageIncognito \
140   OpenSplitModeExtensionOptionsPageIncognito
141 #endif
IN_PROC_BROWSER_TEST_F(ExtensionTabUtilBrowserTest,MAYBE_OpenSplitModeExtensionOptionsPageIncognito)142 IN_PROC_BROWSER_TEST_F(ExtensionTabUtilBrowserTest,
143                        MAYBE_OpenSplitModeExtensionOptionsPageIncognito) {
144   const Extension* options_split_extension = LoadExtensionIncognito(
145       test_data_dir_.AppendASCII("options_page_split_incognito"));
146   ASSERT_TRUE(options_split_extension);
147   ASSERT_TRUE(OptionsPageInfo::HasOptionsPage(options_split_extension));
148   GURL options_url = OptionsPageInfo::GetOptionsPage(options_split_extension);
149 
150   Browser* incognito = CreateIncognitoBrowser();
151 
152   // There should be two browser windows open, regular and incognito.
153   EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
154 
155   // In the regular browser window, start at the new tab page, and then open the
156   // extension options page.
157   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
158   EXPECT_EQ(1, browser()->tab_strip_model()->count());
159   EXPECT_TRUE(
160       ExtensionTabUtil::OpenOptionsPage(options_split_extension, browser()));
161 
162   // Opening the options page should take the new tab and use it, so we should
163   // have only one tab, and it should be open to the options page.
164   EXPECT_EQ(1, browser()->tab_strip_model()->count());
165   EXPECT_TRUE(content::WaitForLoadStop(
166       browser()->tab_strip_model()->GetActiveWebContents()));
167   EXPECT_EQ(options_url, GetActiveUrl(browser()));
168 
169   // If the options page is already opened from a regular window, calling
170   // OpenOptionsPage() from an incognito window should not refocus to the
171   // options page in the regular window, but instead open the options page in
172   // the incognito window.
173   ui_test_utils::NavigateToURL(incognito, GURL(chrome::kChromeUINewTabURL));
174   EXPECT_EQ(1, incognito->tab_strip_model()->count());
175   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPageFromAPI(options_split_extension,
176                                                        incognito->profile()));
177   EXPECT_EQ(1, incognito->tab_strip_model()->count());
178   EXPECT_TRUE(content::WaitForLoadStop(
179       incognito->tab_strip_model()->GetActiveWebContents()));
180   EXPECT_EQ(options_url, GetActiveUrl(incognito));
181 
182   // Both regular and incognito windows should have one tab each.
183   EXPECT_EQ(1, browser()->tab_strip_model()->count());
184   EXPECT_EQ(1, incognito->tab_strip_model()->count());
185 
186   // Reset the incognito browser.
187   CloseBrowserSynchronously(incognito);
188   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
189   incognito = CreateIncognitoBrowser();
190 
191   // Close the regular browser.
192   CloseBrowserSynchronously(browser());
193   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
194 
195   // In the incognito browser, start at the new tab page, and then open the
196   // extension options page.
197   ui_test_utils::NavigateToURL(incognito, GURL(chrome::kChromeUINewTabURL));
198   EXPECT_EQ(1, incognito->tab_strip_model()->count());
199   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPageFromAPI(options_split_extension,
200                                                        incognito->profile()));
201 
202   // Opening the options page should take the new tab and use it, so we should
203   // have only one tab, and it should be open to the options page.
204   EXPECT_EQ(1, incognito->tab_strip_model()->count());
205   EXPECT_TRUE(content::WaitForLoadStop(
206       incognito->tab_strip_model()->GetActiveWebContents()));
207   EXPECT_EQ(options_url, GetActiveUrl(incognito));
208 
209   // Calling OpenOptionsPage again shouldn't result in any new tabs, since we
210   // re-use the existing options page.
211   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPageFromAPI(options_split_extension,
212                                                        incognito->profile()));
213   EXPECT_EQ(1, incognito->tab_strip_model()->count());
214   EXPECT_TRUE(content::WaitForLoadStop(
215       incognito->tab_strip_model()->GetActiveWebContents()));
216   EXPECT_EQ(options_url, GetActiveUrl(incognito));
217 
218   // Navigate to google.com (something non-newtab, non-options). Calling
219   // OpenOptionsPage() should create a new tab and navigate it to the options
220   // page. So we should have two total tabs, with the active tab pointing to
221   // options.
222   ui_test_utils::NavigateToURL(incognito, GURL("http://www.google.com/"));
223   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPageFromAPI(options_split_extension,
224                                                        incognito->profile()));
225   EXPECT_EQ(2, incognito->tab_strip_model()->count());
226   EXPECT_TRUE(content::WaitForLoadStop(
227       incognito->tab_strip_model()->GetActiveWebContents()));
228   EXPECT_EQ(options_url, GetActiveUrl(incognito));
229 }
230 
IN_PROC_BROWSER_TEST_F(ExtensionTabUtilBrowserTest,OpenSpanningModeExtensionOptionsPageIncognito)231 IN_PROC_BROWSER_TEST_F(ExtensionTabUtilBrowserTest,
232                        OpenSpanningModeExtensionOptionsPageIncognito) {
233   const Extension* options_spanning_extension = LoadExtensionIncognito(
234       test_data_dir_.AppendASCII("options_page_spanning_incognito"));
235   ASSERT_TRUE(options_spanning_extension);
236   ASSERT_TRUE(OptionsPageInfo::HasOptionsPage(options_spanning_extension));
237   GURL options_url =
238       OptionsPageInfo::GetOptionsPage(options_spanning_extension);
239 
240   // Start a regular browser window with two tabs, one that is non-options,
241   // non-newtab and the other that is the options page.
242   ui_test_utils::NavigateToURL(browser(), GURL("http://www.google.com/"));
243   EXPECT_TRUE(
244       ExtensionTabUtil::OpenOptionsPage(options_spanning_extension, browser()));
245   EXPECT_EQ(2, browser()->tab_strip_model()->count());
246   EXPECT_TRUE(content::WaitForLoadStop(
247       browser()->tab_strip_model()->GetActiveWebContents()));
248   EXPECT_EQ(options_url, GetActiveUrl(browser()));
249   // Switch to tab containing google.com such that it is the active tab.
250   browser()->tab_strip_model()->SelectPreviousTab();
251   EXPECT_EQ(GURL("http://www.google.com/"), GetActiveUrl(browser()));
252 
253   // Spanning mode extensions can never open pages in incognito so a regular
254   // (non-OTR) profile must be used. If the options page is already opened from
255   // a regular window, calling OpenOptionsPage() from an incognito window should
256   // refocus to the options page in the regular window.
257   Browser* incognito = CreateIncognitoBrowser();
258   ui_test_utils::NavigateToURL(incognito, GURL(chrome::kChromeUINewTabURL));
259   EXPECT_EQ(1, incognito->tab_strip_model()->count());
260   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPageFromAPI(
261       options_spanning_extension, profile()));
262   // There should be two browser windows open, regular and incognito.
263   EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
264   // Ensure that the regular browser is the foreground browser.
265   EXPECT_EQ(browser(), BrowserList::GetInstance()->GetLastActive());
266   // The options page in the regular window should be in focus instead of
267   // the tab pointing to www.google.com.
268   EXPECT_TRUE(content::WaitForLoadStop(
269       browser()->tab_strip_model()->GetActiveWebContents()));
270   EXPECT_EQ(options_url, GetActiveUrl(browser()));
271 
272   // Only the incognito browser should be left.
273   CloseBrowserSynchronously(browser());
274   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
275 
276   // Start at the new tab page in incognito and open the extension options page.
277   ui_test_utils::NavigateToURL(incognito, GURL(chrome::kChromeUINewTabURL));
278   EXPECT_EQ(1, incognito->tab_strip_model()->count());
279   EXPECT_TRUE(ExtensionTabUtil::OpenOptionsPageFromAPI(
280       options_spanning_extension, profile()));
281 
282   // Opening the options page from an incognito window should open a new regular
283   // profile window, which should have one tab open to the options page.
284   ASSERT_EQ(2u, chrome::GetTotalBrowserCount());
285   BrowserList* browser_list = BrowserList::GetInstance();
286   Browser* regular = !browser_list->get(0u)->profile()->IsOffTheRecord()
287                          ? browser_list->get(0u)
288                          : browser_list->get(1u);
289   EXPECT_EQ(1, regular->tab_strip_model()->count());
290   EXPECT_TRUE(content::WaitForLoadStop(
291       regular->tab_strip_model()->GetActiveWebContents()));
292   EXPECT_EQ(options_url, GetActiveUrl(regular));
293 
294   // Leave only incognito browser open.
295   CloseBrowserSynchronously(regular);
296   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
297 
298   // Right-clicking on an extension action icon in the toolbar and selecting
299   // options should open the options page in a regular window. In this case, the
300   // profile is an OTR profile instead of a non-OTR profile, as described above.
301   ui_test_utils::NavigateToURL(incognito, GURL(chrome::kChromeUINewTabURL));
302   EXPECT_EQ(1, incognito->tab_strip_model()->count());
303   // Because the OpenOptionsPage() call originates from an OTR window via, e.g.
304   // the action menu, instead of initiated by the extension, the
305   // OpenOptionsPage() version that takes a Browser* is used.
306   EXPECT_TRUE(
307       ExtensionTabUtil::OpenOptionsPage(options_spanning_extension, incognito));
308   // There should be two browser windows open, regular and incognito.
309   EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
310   browser_list = BrowserList::GetInstance();
311   regular = !browser_list->get(0u)->profile()->IsOffTheRecord()
312                 ? browser_list->get(0u)
313                 : browser_list->get(1u);
314   // Ensure that the regular browser is the foreground browser.
315   EXPECT_EQ(regular, browser_list->GetLastActive());
316   EXPECT_EQ(1, regular->tab_strip_model()->count());
317   EXPECT_TRUE(content::WaitForLoadStop(
318       regular->tab_strip_model()->GetActiveWebContents()));
319   EXPECT_EQ(options_url, GetActiveUrl(regular));
320 }
321 
322 }  // namespace extensions
323