1// Copyright (c) 2012 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 "base/command_line.h"
6#include "base/memory/scoped_vector.h"
7#include "base/path_service.h"
8#include "base/strings/stringprintf.h"
9#include "chrome/browser/extensions/extension_apitest.h"
10#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/ui/browser.h"
12#include "chrome/browser/ui/browser_finder.h"
13#include "chrome/browser/ui/browser_iterator.h"
14#include "chrome/browser/ui/panels/panel_manager.h"
15#include "chrome/browser/ui/tabs/tab_strip_model.h"
16#include "chrome/common/chrome_paths.h"
17#include "chrome/common/chrome_switches.h"
18#include "chrome/test/base/test_switches.h"
19#include "chrome/test/base/ui_test_utils.h"
20#include "content/public/browser/render_process_host.h"
21#include "content/public/browser/web_contents.h"
22#include "content/public/common/result_codes.h"
23#include "content/public/common/url_constants.h"
24#include "content/public/test/browser_test_utils.h"
25#include "extensions/browser/extension_host.h"
26#include "extensions/browser/extension_system.h"
27#include "extensions/browser/process_manager.h"
28#include "extensions/common/constants.h"
29#include "extensions/common/extension.h"
30#include "extensions/common/switches.h"
31#include "extensions/test/extension_test_message_listener.h"
32#include "extensions/test/result_catcher.h"
33#include "net/dns/mock_host_resolver.h"
34#include "net/test/embedded_test_server/embedded_test_server.h"
35#include "testing/gtest/include/gtest/gtest.h"
36
37#if defined(USE_ASH)
38#include "extensions/browser/app_window/app_window_registry.h"
39#endif
40
41#if defined(USE_ASH) && defined(OS_CHROMEOS)
42// TODO(stevenjb): Figure out the correct behavior for Ash + Win
43#define USE_ASH_PANELS
44#endif
45
46using content::OpenURLParams;
47using content::Referrer;
48using content::WebContents;
49
50// Disabled, http://crbug.com/64899.
51IN_PROC_BROWSER_TEST_F(ExtensionApiTest, DISABLED_WindowOpen) {
52  CommandLine::ForCurrentProcess()->AppendSwitch(
53      extensions::switches::kEnableExperimentalExtensionApis);
54
55  extensions::ResultCatcher catcher;
56  ASSERT_TRUE(LoadExtensionIncognito(test_data_dir_
57      .AppendASCII("window_open").AppendASCII("spanning")));
58  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
59}
60
61int GetPanelCount(Browser* browser) {
62#if defined(USE_ASH_PANELS)
63  return static_cast<int>(extensions::AppWindowRegistry::Get(
64      browser->profile())->app_windows().size());
65#else
66  return PanelManager::GetInstance()->num_panels();
67#endif
68}
69
70bool WaitForTabsAndPopups(Browser* browser,
71                          int num_tabs,
72                          int num_popups,
73                          int num_panels) {
74  SCOPED_TRACE(
75      base::StringPrintf("WaitForTabsAndPopups tabs:%d, popups:%d, panels:%d",
76                         num_tabs, num_popups, num_panels));
77  // We start with one tab and one browser already open.
78  ++num_tabs;
79  size_t num_browsers = static_cast<size_t>(num_popups) + 1;
80
81  const base::TimeDelta kWaitTime = base::TimeDelta::FromSeconds(10);
82  base::TimeTicks end_time = base::TimeTicks::Now() + kWaitTime;
83  while (base::TimeTicks::Now() < end_time) {
84    if (chrome::GetBrowserCount(browser->profile(),
85                                browser->host_desktop_type()) == num_browsers &&
86        browser->tab_strip_model()->count() == num_tabs &&
87        GetPanelCount(browser) == num_panels)
88      break;
89
90    content::RunAllPendingInMessageLoop();
91  }
92
93  EXPECT_EQ(num_browsers,
94            chrome::GetBrowserCount(browser->profile(),
95                                    browser->host_desktop_type()));
96  EXPECT_EQ(num_tabs, browser->tab_strip_model()->count());
97  EXPECT_EQ(num_panels, GetPanelCount(browser));
98
99  int num_popups_seen = 0;
100  for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
101    if (*iter == browser)
102      continue;
103
104    EXPECT_TRUE((*iter)->is_type_popup());
105    ++num_popups_seen;
106  }
107  EXPECT_EQ(num_popups, num_popups_seen);
108
109  return ((num_browsers ==
110               chrome::GetBrowserCount(browser->profile(),
111                                       browser->host_desktop_type())) &&
112          (num_tabs == browser->tab_strip_model()->count()) &&
113          (num_panels == GetPanelCount(browser)) &&
114          (num_popups == num_popups_seen));
115}
116
117IN_PROC_BROWSER_TEST_F(ExtensionApiTest, BrowserIsApp) {
118  host_resolver()->AddRule("a.com", "127.0.0.1");
119  ASSERT_TRUE(StartEmbeddedTestServer());
120  ASSERT_TRUE(LoadExtension(
121      test_data_dir_.AppendASCII("window_open").AppendASCII("browser_is_app")));
122
123  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 0, 2, 0));
124
125  for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
126    if (*iter == browser())
127      ASSERT_FALSE(iter->is_app());
128    else
129      ASSERT_TRUE(iter->is_app());
130  }
131}
132
133IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WindowOpenPopupDefault) {
134  ASSERT_TRUE(StartEmbeddedTestServer());
135  ASSERT_TRUE(LoadExtension(
136      test_data_dir_.AppendASCII("window_open").AppendASCII("popup")));
137
138  const int num_tabs = 1;
139  const int num_popups = 0;
140  EXPECT_TRUE(WaitForTabsAndPopups(browser(), num_tabs, num_popups, 0));
141}
142
143IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WindowOpenPopupIframe) {
144  ASSERT_TRUE(StartEmbeddedTestServer());
145  base::FilePath test_data_dir;
146  PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir);
147  embedded_test_server()->ServeFilesFromDirectory(test_data_dir);
148  ASSERT_TRUE(LoadExtension(
149      test_data_dir_.AppendASCII("window_open").AppendASCII("popup_iframe")));
150
151  const int num_tabs = 1;
152  const int num_popups = 0;
153  EXPECT_TRUE(WaitForTabsAndPopups(browser(), num_tabs, num_popups, 0));
154}
155
156IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WindowOpenPopupLarge) {
157  ASSERT_TRUE(StartEmbeddedTestServer());
158  ASSERT_TRUE(LoadExtension(
159      test_data_dir_.AppendASCII("window_open").AppendASCII("popup_large")));
160
161  // On other systems this should open a new popup window.
162  const int num_tabs = 0;
163  const int num_popups = 1;
164  EXPECT_TRUE(WaitForTabsAndPopups(browser(), num_tabs, num_popups, 0));
165}
166
167IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WindowOpenPopupSmall) {
168  ASSERT_TRUE(StartEmbeddedTestServer());
169  ASSERT_TRUE(LoadExtension(
170      test_data_dir_.AppendASCII("window_open").AppendASCII("popup_small")));
171
172  // On ChromeOS this should open a new panel (acts like a new popup window).
173  // On other systems this should open a new popup window.
174  const int num_tabs = 0;
175  const int num_popups = 1;
176  EXPECT_TRUE(WaitForTabsAndPopups(browser(), num_tabs, num_popups, 0));
177}
178
179// Disabled on Windows. Often times out or fails: crbug.com/177530
180#if defined(OS_WIN)
181#define MAYBE_PopupBlockingExtension DISABLED_PopupBlockingExtension
182#else
183#define MAYBE_PopupBlockingExtension PopupBlockingExtension
184#endif
185IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_PopupBlockingExtension) {
186  host_resolver()->AddRule("*", "127.0.0.1");
187  ASSERT_TRUE(StartEmbeddedTestServer());
188
189  ASSERT_TRUE(LoadExtension(
190      test_data_dir_.AppendASCII("window_open").AppendASCII("popup_blocking")
191      .AppendASCII("extension")));
192
193  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 5, 3, 0));
194}
195
196IN_PROC_BROWSER_TEST_F(ExtensionApiTest, PopupBlockingHostedApp) {
197  host_resolver()->AddRule("*", "127.0.0.1");
198  ASSERT_TRUE(test_server()->Start());
199
200  ASSERT_TRUE(LoadExtension(
201      test_data_dir_.AppendASCII("window_open").AppendASCII("popup_blocking")
202      .AppendASCII("hosted_app")));
203
204  // The app being tested owns the domain a.com .  The test URLs we navigate
205  // to below must be within that domain, so that they fall within the app's
206  // web extent.
207  GURL::Replacements replace_host;
208  std::string a_dot_com = "a.com";
209  replace_host.SetHostStr(a_dot_com);
210
211  const std::string popup_app_contents_path(
212    "files/extensions/api_test/window_open/popup_blocking/hosted_app/");
213
214  GURL open_tab =
215      test_server()->GetURL(popup_app_contents_path + "open_tab.html")
216          .ReplaceComponents(replace_host);
217  GURL open_popup =
218      test_server()->GetURL(popup_app_contents_path + "open_popup.html")
219          .ReplaceComponents(replace_host);
220
221  browser()->OpenURL(OpenURLParams(
222      open_tab, Referrer(), NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_TYPED,
223      false));
224  browser()->OpenURL(OpenURLParams(
225      open_popup, Referrer(), NEW_FOREGROUND_TAB,
226      ui::PAGE_TRANSITION_TYPED, false));
227
228  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 3, 1, 0));
229}
230
231IN_PROC_BROWSER_TEST_F(ExtensionApiTest, WindowArgumentsOverflow) {
232  ASSERT_TRUE(RunExtensionTest("window_open/argument_overflow")) << message_;
233}
234
235class WindowOpenPanelDisabledTest : public ExtensionApiTest {
236  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
237    ExtensionApiTest::SetUpCommandLine(command_line);
238    // TODO(jennb): Re-enable when panels are enabled by default.
239    // command_line->AppendSwitch(switches::kDisablePanels);
240  }
241};
242
243IN_PROC_BROWSER_TEST_F(WindowOpenPanelDisabledTest,
244                       DISABLED_WindowOpenPanelNotEnabled) {
245  ASSERT_TRUE(RunExtensionTest("window_open/panel_not_enabled")) << message_;
246}
247
248class WindowOpenPanelTest : public ExtensionApiTest {
249  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
250    ExtensionApiTest::SetUpCommandLine(command_line);
251    command_line->AppendSwitch(switches::kEnablePanels);
252  }
253};
254
255#if defined(USE_ASH_PANELS)
256// On Ash, this currently fails because we're currently opening new panel
257// windows as popup windows instead.
258#define MAYBE_WindowOpenPanel DISABLED_WindowOpenPanel
259#else
260#define MAYBE_WindowOpenPanel WindowOpenPanel
261#endif
262IN_PROC_BROWSER_TEST_F(WindowOpenPanelTest, MAYBE_WindowOpenPanel) {
263  ASSERT_TRUE(RunExtensionTest("window_open/panel")) << message_;
264}
265
266#if defined(USE_ASH_PANELS) || defined(OS_LINUX)
267// On Ash, this currently fails because we're currently opening new panel
268// windows as popup windows instead.
269// We're also failing on Linux-aura due to the panel is not opened in the
270// right origin.
271#define MAYBE_WindowOpenPanelDetached DISABLED_WindowOpenPanelDetached
272#else
273#define MAYBE_WindowOpenPanelDetached WindowOpenPanelDetached
274#endif
275IN_PROC_BROWSER_TEST_F(WindowOpenPanelTest, MAYBE_WindowOpenPanelDetached) {
276  ASSERT_TRUE(RunExtensionTest("window_open/panel_detached")) << message_;
277}
278
279#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
280// TODO(erg): Bring up ash http://crbug.com/300084
281#define MAYBE_CloseNonExtensionPanelsOnUninstall \
282  DISABLED_CloseNonExtensionPanelsOnUninstall
283#else
284#define MAYBE_CloseNonExtensionPanelsOnUninstall \
285  CloseNonExtensionPanelsOnUninstall
286#endif
287IN_PROC_BROWSER_TEST_F(WindowOpenPanelTest,
288                       MAYBE_CloseNonExtensionPanelsOnUninstall) {
289#if defined(OS_WIN) && defined(USE_ASH)
290  // Disable this test in Metro+Ash for now (http://crbug.com/262796).
291  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests))
292    return;
293#endif
294
295#if defined(USE_ASH_PANELS)
296  // On Ash, new panel windows open as popup windows instead.
297  int num_popups, num_panels;
298  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePanels)) {
299    num_popups = 2;
300    num_panels = 2;
301  } else {
302    num_popups = 4;
303    num_panels = 0;
304  }
305#else
306  int num_popups = 2;
307  int num_panels = 2;
308#endif
309  ASSERT_TRUE(StartEmbeddedTestServer());
310
311  // Setup listeners to wait on strings we expect the extension pages to send.
312  std::vector<std::string> test_strings;
313  test_strings.push_back("content_tab");
314  if (num_panels)
315    test_strings.push_back("content_panel");
316  test_strings.push_back("content_popup");
317
318  ScopedVector<ExtensionTestMessageListener> listeners;
319  for (size_t i = 0; i < test_strings.size(); ++i) {
320    listeners.push_back(
321        new ExtensionTestMessageListener(test_strings[i], false));
322  }
323
324  const extensions::Extension* extension = LoadExtension(
325      test_data_dir_.AppendASCII("window_open").AppendASCII(
326          "close_panels_on_uninstall"));
327  ASSERT_TRUE(extension);
328
329  // Two tabs. One in extension domain and one in non-extension domain.
330  // Two popups - one in extension domain and one in non-extension domain.
331  // Two panels - one in extension domain and one in non-extension domain.
332  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 2, num_popups, num_panels));
333
334  // Wait on test messages to make sure the pages loaded.
335  for (size_t i = 0; i < listeners.size(); ++i)
336    ASSERT_TRUE(listeners[i]->WaitUntilSatisfied());
337
338  UninstallExtension(extension->id());
339
340  // Wait for the tabs and popups in non-extension domain to stay open.
341  // Expect everything else, including panels, to close.
342  num_popups -= 1;
343#if defined(USE_ASH_PANELS)
344  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnablePanels)) {
345    // On Ash, new panel windows open as popup windows instead, so there are 2
346    // extension domain popups that will close (instead of 1 popup on non-Ash).
347    num_popups -= 1;
348  }
349#endif
350#if defined(USE_ASH)
351#if !defined(OS_WIN)
352  // On linux ash we close all popup applications when closing its extension.
353  num_popups = 0;
354#endif
355#endif
356  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 1, num_popups, 0));
357}
358
359// This test isn't applicable on Chrome OS, which automatically reloads crashed
360// pages.
361#if !defined(OS_CHROMEOS)
362IN_PROC_BROWSER_TEST_F(WindowOpenPanelTest, ClosePanelsOnExtensionCrash) {
363#if defined(USE_ASH_PANELS)
364  // On Ash, new panel windows open as popup windows instead.
365  int num_popups = 4;
366  int num_panels = 0;
367#else
368  int num_popups = 2;
369  int num_panels = 2;
370#endif
371  ASSERT_TRUE(StartEmbeddedTestServer());
372
373  // Setup listeners to wait on strings we expect the extension pages to send.
374  std::vector<std::string> test_strings;
375  test_strings.push_back("content_tab");
376  if (num_panels)
377    test_strings.push_back("content_panel");
378  test_strings.push_back("content_popup");
379
380  ScopedVector<ExtensionTestMessageListener> listeners;
381  for (size_t i = 0; i < test_strings.size(); ++i) {
382    listeners.push_back(
383        new ExtensionTestMessageListener(test_strings[i], false));
384  }
385
386  const extensions::Extension* extension = LoadExtension(
387      test_data_dir_.AppendASCII("window_open").AppendASCII(
388          "close_panels_on_uninstall"));
389  ASSERT_TRUE(extension);
390
391  // Two tabs. One in extension domain and one in non-extension domain.
392  // Two popups - one in extension domain and one in non-extension domain.
393  // Two panels - one in extension domain and one in non-extension domain.
394  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 2, num_popups, num_panels));
395
396  // Wait on test messages to make sure the pages loaded.
397  for (size_t i = 0; i < listeners.size(); ++i)
398    ASSERT_TRUE(listeners[i]->WaitUntilSatisfied());
399
400  // Crash the extension.
401  extensions::ExtensionHost* extension_host =
402      extensions::ExtensionSystem::Get(browser()->profile())->
403          process_manager()->GetBackgroundHostForExtension(extension->id());
404  ASSERT_TRUE(extension_host);
405  base::KillProcess(extension_host->render_process_host()->GetHandle(),
406                    content::RESULT_CODE_KILLED, false);
407  WaitForExtensionCrash(extension->id());
408
409  // Only expect panels to close. The rest stay open to show a sad-tab.
410  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 2, num_popups, 0));
411}
412#endif  // !defined(OS_CHROMEOS)
413
414#if defined(USE_ASH_PANELS)
415// This test is not applicable on Ash. The modified window.open behavior only
416// applies to non-Ash panel windows.
417#define MAYBE_WindowOpenFromPanel DISABLED_WindowOpenFromPanel
418#else
419#define MAYBE_WindowOpenFromPanel WindowOpenFromPanel
420#endif
421IN_PROC_BROWSER_TEST_F(WindowOpenPanelTest, MAYBE_WindowOpenFromPanel) {
422  ASSERT_TRUE(StartEmbeddedTestServer());
423
424  // Load the extension that will open a panel which then calls window.open.
425  ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("window_open").
426                            AppendASCII("panel_window_open")));
427
428  // Expect one panel (opened by extension) and one tab (from the panel calling
429  // window.open). Panels modify the WindowOpenDisposition in window.open
430  // to always open in a tab.
431  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 1, 0, 1));
432}
433
434IN_PROC_BROWSER_TEST_F(ExtensionApiTest, DISABLED_WindowOpener) {
435  ASSERT_TRUE(RunExtensionTest("window_open/opener")) << message_;
436}
437
438#if defined(OS_MACOSX)
439// Extension popup windows are incorrectly sized on OSX, crbug.com/225601
440#define MAYBE_WindowOpenSized DISABLED_WindowOpenSized
441#else
442#define MAYBE_WindowOpenSized WindowOpenSized
443#endif
444// Ensure that the width and height properties of a window opened with
445// chrome.windows.create match the creation parameters. See crbug.com/173831.
446IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_WindowOpenSized) {
447  ASSERT_TRUE(RunExtensionTest("window_open/window_size")) << message_;
448  EXPECT_TRUE(WaitForTabsAndPopups(browser(), 0, 1, 0));
449}
450
451// Tests that an extension page can call window.open to an extension URL and
452// the new window has extension privileges.
453IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WindowOpenExtension) {
454  ASSERT_TRUE(LoadExtension(
455      test_data_dir_.AppendASCII("uitest").AppendASCII("window_open")));
456
457  GURL start_url(std::string(extensions::kExtensionScheme) +
458                     url::kStandardSchemeSeparator +
459                     last_loaded_extension_id() + "/test.html");
460  ui_test_utils::NavigateToURL(browser(), start_url);
461  WebContents* newtab = NULL;
462  ASSERT_NO_FATAL_FAILURE(
463      OpenWindow(browser()->tab_strip_model()->GetActiveWebContents(),
464      start_url.Resolve("newtab.html"), true, &newtab));
465
466  bool result = false;
467  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(newtab, "testExtensionApi()",
468                                                   &result));
469  EXPECT_TRUE(result);
470}
471
472// Tests that if an extension page calls window.open to an invalid extension
473// URL, the browser doesn't crash.
474IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WindowOpenInvalidExtension) {
475  ASSERT_TRUE(LoadExtension(
476      test_data_dir_.AppendASCII("uitest").AppendASCII("window_open")));
477
478  GURL start_url(std::string(extensions::kExtensionScheme) +
479                     url::kStandardSchemeSeparator +
480                     last_loaded_extension_id() + "/test.html");
481  ui_test_utils::NavigateToURL(browser(), start_url);
482  ASSERT_NO_FATAL_FAILURE(
483      OpenWindow(browser()->tab_strip_model()->GetActiveWebContents(),
484      GURL("chrome-extension://thisissurelynotavalidextensionid/newtab.html"),
485      false, NULL));
486
487  // If we got to this point, we didn't crash, so we're good.
488}
489
490// Tests that calling window.open from the newtab page to an extension URL
491// gives the new window extension privileges - even though the opening page
492// does not have extension privileges, we break the script connection, so
493// there is no privilege leak.
494IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, WindowOpenNoPrivileges) {
495  ASSERT_TRUE(LoadExtension(
496      test_data_dir_.AppendASCII("uitest").AppendASCII("window_open")));
497
498  ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
499  WebContents* newtab = NULL;
500  ASSERT_NO_FATAL_FAILURE(
501      OpenWindow(browser()->tab_strip_model()->GetActiveWebContents(),
502                 GURL(std::string(extensions::kExtensionScheme) +
503                     url::kStandardSchemeSeparator +
504                     last_loaded_extension_id() + "/newtab.html"),
505                 false,
506                 &newtab));
507
508  // Extension API should succeed.
509  bool result = false;
510  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(newtab, "testExtensionApi()",
511                                                   &result));
512  EXPECT_TRUE(result);
513}
514