// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/command_line.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_file_util.h" #include "chrome/test/base/test_switches.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/site_instance.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_navigation_observer.h" #include "extensions/browser/process_map.h" #include "extensions/common/extension.h" #include "extensions/common/switches.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "sync/api/string_ordinal.h" using content::NavigationController; using content::RenderViewHost; using content::SiteInstance; using content::WebContents; using extensions::Extension; class AppApiTest : public ExtensionApiTest { protected: // Gets the base URL for files for a specific test, making sure that it uses // "localhost" as the hostname, since that is what the extent is declared // as in the test apps manifests. GURL GetTestBaseURL(std::string test_directory) { GURL::Replacements replace_host; std::string host_str("localhost"); // must stay in scope with replace_host replace_host.SetHostStr(host_str); GURL base_url = embedded_test_server()->GetURL( "/extensions/api_test/" + test_directory + "/"); return base_url.ReplaceComponents(replace_host); } // Pass flags to make testing apps easier. virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { ExtensionApiTest::SetUpCommandLine(command_line); CommandLine::ForCurrentProcess()->AppendSwitch( switches::kDisablePopupBlocking); CommandLine::ForCurrentProcess()->AppendSwitch( extensions::switches::kAllowHTTPBackgroundPage); } // Helper function to test that independent tabs of the named app are loaded // into separate processes. void TestAppInstancesHelper(std::string app_name) { LOG(INFO) << "Start of test."; extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); ASSERT_TRUE(LoadExtension( test_data_dir_.AppendASCII(app_name))); const Extension* extension = GetSingleLoadedExtension(); // Open two tabs in the app, one outside it. GURL base_url = GetTestBaseURL(app_name); // Test both opening a URL in a new tab, and opening a tab and then // navigating it. Either way, app tabs should be considered extension // processes, but they have no elevated privileges and thus should not // have WebUI bindings. ui_test_utils::NavigateToURLWithDisposition( browser(), base_url.Resolve("path1/empty.html"), NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); LOG(INFO) << "Nav 1."; EXPECT_TRUE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(1)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(1)->GetWebUI()); content::WindowedNotificationObserver tab_added_observer( chrome::NOTIFICATION_TAB_ADDED, content::NotificationService::AllSources()); chrome::NewTab(browser()); tab_added_observer.Wait(); LOG(INFO) << "New tab."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path2/empty.html")); LOG(INFO) << "Nav 2."; EXPECT_TRUE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(2)->GetWebUI()); // We should have opened 2 new extension tabs. Including the original blank // tab, we now have 3 tabs. The two app tabs should not be in the same // process, since they do not have the background permission. (Thus, we // want to separate them to improve responsiveness.) ASSERT_EQ(3, browser()->tab_strip_model()->count()); WebContents* tab1 = browser()->tab_strip_model()->GetWebContentsAt(1); WebContents* tab2 = browser()->tab_strip_model()->GetWebContentsAt(2); EXPECT_NE(tab1->GetRenderProcessHost(), tab2->GetRenderProcessHost()); // Opening tabs with window.open should keep the page in the opener's // process. ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile(), browser()->host_desktop_type())); OpenWindow(tab1, base_url.Resolve("path1/empty.html"), true, NULL); LOG(INFO) << "WindowOpenHelper 1."; OpenWindow(tab2, base_url.Resolve("path2/empty.html"), true, NULL); LOG(INFO) << "End of test."; UnloadExtension(extension->id()); } }; // Omits the disable-popup-blocking flag so we can cover that case. class BlockedAppApiTest : public AppApiTest { protected: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { ExtensionApiTest::SetUpCommandLine(command_line); CommandLine::ForCurrentProcess()->AppendSwitch( extensions::switches::kAllowHTTPBackgroundPage); } }; // Tests that hosted apps with the background permission get a process-per-app // model, since all pages need to be able to script the background page. // http://crbug.com/172750 IN_PROC_BROWSER_TEST_F(AppApiTest, DISABLED_AppProcess) { LOG(INFO) << "Start of test."; extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("app_process"))); LOG(INFO) << "Loaded extension."; // Open two tabs in the app, one outside it. GURL base_url = GetTestBaseURL("app_process"); // Test both opening a URL in a new tab, and opening a tab and then navigating // it. Either way, app tabs should be considered extension processes, but // they have no elevated privileges and thus should not have WebUI bindings. ui_test_utils::NavigateToURLWithDisposition( browser(), base_url.Resolve("path1/empty.html"), NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); EXPECT_TRUE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(1)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(1)->GetWebUI()); LOG(INFO) << "Nav 1."; ui_test_utils::NavigateToURLWithDisposition( browser(), base_url.Resolve("path2/empty.html"), NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); EXPECT_TRUE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(2)->GetWebUI()); LOG(INFO) << "Nav 2."; content::WindowedNotificationObserver tab_added_observer( chrome::NOTIFICATION_TAB_ADDED, content::NotificationService::AllSources()); chrome::NewTab(browser()); tab_added_observer.Wait(); LOG(INFO) << "New tab."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path3/empty.html")); LOG(INFO) << "Nav 3."; EXPECT_FALSE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(3)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(3)->GetWebUI()); // We should have opened 3 new extension tabs. Including the original blank // tab, we now have 4 tabs. Because the app_process app has the background // permission, all of its instances are in the same process. Thus two tabs // should be part of the extension app and grouped in the same process. ASSERT_EQ(4, browser()->tab_strip_model()->count()); WebContents* tab = browser()->tab_strip_model()->GetWebContentsAt(1); EXPECT_EQ(tab->GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()); EXPECT_NE(tab->GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(3)-> GetRenderProcessHost()); // Now let's do the same using window.open. The same should happen. ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile(), browser()->host_desktop_type())); OpenWindow(tab, base_url.Resolve("path1/empty.html"), true, NULL); LOG(INFO) << "WindowOpenHelper 1."; OpenWindow(tab, base_url.Resolve("path2/empty.html"), true, NULL); LOG(INFO) << "WindowOpenHelper 2."; // TODO(creis): This should open in a new process (i.e., false for the last // argument), but we temporarily avoid swapping processes away from a hosted // app if it has an opener, because some OAuth providers make script calls // between non-app popups and non-app iframes in the app process. // See crbug.com/59285. OpenWindow(tab, base_url.Resolve("path3/empty.html"), true, NULL); LOG(INFO) << "WindowOpenHelper 3."; // Now let's have these pages navigate, into or out of the extension web // extent. They should switch processes. const GURL& app_url(base_url.Resolve("path1/empty.html")); const GURL& non_app_url(base_url.Resolve("path3/empty.html")); NavigateInRenderer(browser()->tab_strip_model()->GetWebContentsAt(2), non_app_url); LOG(INFO) << "NavigateTabHelper 1."; NavigateInRenderer(browser()->tab_strip_model()->GetWebContentsAt(3), app_url); LOG(INFO) << "NavigateTabHelper 2."; EXPECT_NE(tab->GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()); EXPECT_EQ(tab->GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(3)-> GetRenderProcessHost()); // If one of the popup tabs navigates back to the app, window.opener should // be valid. NavigateInRenderer(browser()->tab_strip_model()->GetWebContentsAt(6), app_url); LOG(INFO) << "NavigateTabHelper 3."; EXPECT_EQ(tab->GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(6)-> GetRenderProcessHost()); bool windowOpenerValid = false; ASSERT_TRUE(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetWebContentsAt(6), "window.domAutomationController.send(window.opener != null)", &windowOpenerValid)); ASSERT_TRUE(windowOpenerValid); LOG(INFO) << "End of test."; } // Test that hosted apps without the background permission use a process per app // instance model, such that separate instances are in separate processes. // Flaky on Windows. http://crbug.com/248047 #if defined(OS_WIN) #define MAYBE_AppProcessInstances DISABLED_AppProcessInstances #else #define MAYBE_AppProcessInstances AppProcessInstances #endif IN_PROC_BROWSER_TEST_F(AppApiTest, MAYBE_AppProcessInstances) { TestAppInstancesHelper("app_process_instances"); } // Test that hosted apps with the background permission but that set // allow_js_access to false also use a process per app instance model. // Separate instances should be in separate processes. // Flaky on XP: http://crbug.com/165834 #if defined(OS_WIN) #define MAYBE_AppProcessBackgroundInstances \ DISABLED_AppProcessBackgroundInstances #else #define MAYBE_AppProcessBackgroundInstances AppProcessBackgroundInstances #endif IN_PROC_BROWSER_TEST_F(AppApiTest, MAYBE_AppProcessBackgroundInstances) { TestAppInstancesHelper("app_process_background_instances"); } // Tests that bookmark apps do not use the app process model and are treated // like normal web pages instead. http://crbug.com/104636. // Timing out on Windows. http://crbug.com/238777 #if defined(OS_WIN) #define MAYBE_BookmarkAppGetsNormalProcess DISABLED_BookmarkAppGetsNormalProcess #else #define MAYBE_BookmarkAppGetsNormalProcess BookmarkAppGetsNormalProcess #endif IN_PROC_BROWSER_TEST_F(AppApiTest, MAYBE_BookmarkAppGetsNormalProcess) { ExtensionService* service = extensions::ExtensionSystem::Get( browser()->profile())->extension_service(); extensions::ProcessMap* process_map = service->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL base_url = GetTestBaseURL("app_process"); // Load an app as a bookmark app. std::string error; scoped_refptr<const Extension> extension(extension_file_util::LoadExtension( test_data_dir_.AppendASCII("app_process"), extensions::Manifest::UNPACKED, Extension::FROM_BOOKMARK, &error)); service->OnExtensionInstalled(extension.get(), syncer::StringOrdinal::CreateInitialOrdinal(), false /* no requirement errors */, extensions::Blacklist::NOT_BLACKLISTED, false /* don't wait for idle */); ASSERT_TRUE(extension.get()); ASSERT_TRUE(extension->from_bookmark()); // Test both opening a URL in a new tab, and opening a tab and then navigating // it. Either way, bookmark app tabs should be considered normal processes // with no elevated privileges and no WebUI bindings. ui_test_utils::NavigateToURLWithDisposition( browser(), base_url.Resolve("path1/empty.html"), NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); EXPECT_FALSE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(1)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(1)->GetWebUI()); content::WindowedNotificationObserver tab_added_observer( chrome::NOTIFICATION_TAB_ADDED, content::NotificationService::AllSources()); chrome::NewTab(browser()); tab_added_observer.Wait(); ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path2/empty.html")); EXPECT_FALSE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()->GetID())); EXPECT_FALSE(browser()->tab_strip_model()->GetWebContentsAt(2)->GetWebUI()); // We should have opened 2 new bookmark app tabs. Including the original blank // tab, we now have 3 tabs. Because normal pages use the // process-per-site-instance model, each should be in its own process. ASSERT_EQ(3, browser()->tab_strip_model()->count()); WebContents* tab = browser()->tab_strip_model()->GetWebContentsAt(1); EXPECT_NE(tab->GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()); // Now let's do the same using window.open. The same should happen. ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile(), browser()->host_desktop_type())); OpenWindow(tab, base_url.Resolve("path1/empty.html"), true, NULL); OpenWindow(tab, base_url.Resolve("path2/empty.html"), true, NULL); // Now let's have a tab navigate out of and back into the app's web // extent. Neither navigation should switch processes. const GURL& app_url(base_url.Resolve("path1/empty.html")); const GURL& non_app_url(base_url.Resolve("path3/empty.html")); RenderViewHost* host2 = browser()->tab_strip_model()->GetWebContentsAt(2)->GetRenderViewHost(); NavigateInRenderer(browser()->tab_strip_model()->GetWebContentsAt(2), non_app_url); EXPECT_EQ(host2->GetProcess(), browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()); NavigateInRenderer(browser()->tab_strip_model()->GetWebContentsAt(2), app_url); EXPECT_EQ(host2->GetProcess(), browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()); } // Tests that app process switching works properly in the following scenario: // 1. navigate to a page1 in the app // 2. page1 redirects to a page2 outside the app extent (ie, "/server-redirect") // 3. page2 redirects back to a page in the app // The final navigation should end up in the app process. // See http://crbug.com/61757 IN_PROC_BROWSER_TEST_F(AppApiTest, AppProcessRedirectBack) { host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("app_process"))); // Open two tabs in the app. GURL base_url = GetTestBaseURL("app_process"); chrome::NewTab(browser()); ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); chrome::NewTab(browser()); // Wait until the second tab finishes its redirect train (2 hops). // 1. We navigate to redirect.html // 2. Renderer navigates and finishes, counting as a load stop. // 3. Renderer issues the meta refresh to navigate to server-redirect. // 4. Renderer is now in a "provisional load", waiting for navigation to // complete. // 5. Browser sees a redirect response from server-redirect to empty.html, and // transfers that to a new navigation, using RequestTransferURL. // 6. Renderer navigates to empty.html, and finishes loading, counting as the // second load stop ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser(), base_url.Resolve("path1/redirect.html"), 2); // 3 tabs, including the initial about:blank. The last 2 should be the same // process. ASSERT_EQ(3, browser()->tab_strip_model()->count()); EXPECT_EQ("/extensions/api_test/app_process/path1/empty.html", browser()->tab_strip_model()->GetWebContentsAt(2)-> GetController().GetLastCommittedEntry()->GetURL().path()); EXPECT_EQ(browser()->tab_strip_model()->GetWebContentsAt(1)-> GetRenderProcessHost(), browser()->tab_strip_model()->GetWebContentsAt(2)-> GetRenderProcessHost()); } // Ensure that re-navigating to a URL after installing or uninstalling it as an // app correctly swaps the tab to the app process. (http://crbug.com/80621) // // Fails on Windows. http://crbug.com/238670 // Added logging to help diagnose the location of the problem. IN_PROC_BROWSER_TEST_F(AppApiTest, NavigateIntoAppProcess) { extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); // The app under test acts on URLs whose host is "localhost", // so the URLs we navigate to must have host "localhost". GURL base_url = GetTestBaseURL("app_process"); // Load an app URL before loading the app. LOG(INFO) << "Loading path1/empty.html."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); LOG(INFO) << "Loading path1/empty.html - done."; WebContents* contents = browser()->tab_strip_model()->GetWebContentsAt(0); EXPECT_FALSE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); // Load app and re-navigate to the page. LOG(INFO) << "Loading extension."; const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); LOG(INFO) << "Loading extension - done."; ASSERT_TRUE(app); LOG(INFO) << "Loading path1/empty.html."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); LOG(INFO) << "Loading path1/empty.html - done."; EXPECT_TRUE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); // Disable app and re-navigate to the page. LOG(INFO) << "Disabling extension."; DisableExtension(app->id()); LOG(INFO) << "Disabling extension - done."; LOG(INFO) << "Loading path1/empty.html."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); LOG(INFO) << "Loading path1/empty.html - done."; EXPECT_FALSE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); } // Ensure that reloading a URL after installing or uninstalling it as an app // correctly swaps the tab to the app process. (http://crbug.com/80621) // // Added logging to help diagnose the location of the problem. // http://crbug.com/238670 IN_PROC_BROWSER_TEST_F(AppApiTest, ReloadIntoAppProcess) { extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); // The app under test acts on URLs whose host is "localhost", // so the URLs we navigate to must have host "localhost". GURL base_url = GetTestBaseURL("app_process"); // Load app, disable it, and navigate to the page. LOG(INFO) << "Loading extension."; const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); LOG(INFO) << "Loading extension - done."; ASSERT_TRUE(app); LOG(INFO) << "Disabling extension."; DisableExtension(app->id()); LOG(INFO) << "Disabling extension - done."; LOG(INFO) << "Navigate to path1/empty.html."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); LOG(INFO) << "Navigate to path1/empty.html - done."; WebContents* contents = browser()->tab_strip_model()->GetWebContentsAt(0); EXPECT_FALSE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); // Enable app and reload the page. LOG(INFO) << "Enabling extension."; EnableExtension(app->id()); LOG(INFO) << "Enabling extension - done."; content::WindowedNotificationObserver reload_observer( content::NOTIFICATION_LOAD_STOP, content::Source<NavigationController>( &browser()->tab_strip_model()->GetActiveWebContents()-> GetController())); LOG(INFO) << "Reloading."; chrome::Reload(browser(), CURRENT_TAB); reload_observer.Wait(); LOG(INFO) << "Reloading - done."; EXPECT_TRUE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); // Disable app and reload the page. LOG(INFO) << "Disabling extension."; DisableExtension(app->id()); LOG(INFO) << "Disabling extension - done."; content::WindowedNotificationObserver reload_observer2( content::NOTIFICATION_LOAD_STOP, content::Source<NavigationController>( &browser()->tab_strip_model()->GetActiveWebContents()-> GetController())); LOG(INFO) << "Reloading."; chrome::Reload(browser(), CURRENT_TAB); reload_observer2.Wait(); LOG(INFO) << "Reloading - done."; EXPECT_FALSE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); } // Ensure that reloading a URL with JavaScript after installing or uninstalling // it as an app correctly swaps the process. (http://crbug.com/80621) // // Crashes on Windows and Mac. http://crbug.com/238670 // Added logging to help diagnose the location of the problem. IN_PROC_BROWSER_TEST_F(AppApiTest, ReloadIntoAppProcessWithJavaScript) { extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); // The app under test acts on URLs whose host is "localhost", // so the URLs we navigate to must have host "localhost". GURL base_url = GetTestBaseURL("app_process"); // Load app, disable it, and navigate to the page. LOG(INFO) << "Loading extension."; const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); LOG(INFO) << "Loading extension - done."; ASSERT_TRUE(app); LOG(INFO) << "Disabling extension."; DisableExtension(app->id()); LOG(INFO) << "Disabling extension - done."; LOG(INFO) << "Navigate to path1/empty.html."; ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); LOG(INFO) << "Navigate to path1/empty.html - done."; WebContents* contents = browser()->tab_strip_model()->GetWebContentsAt(0); EXPECT_FALSE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); // Enable app and reload via JavaScript. LOG(INFO) << "Enabling extension."; EnableExtension(app->id()); LOG(INFO) << "Enabling extension - done."; content::WindowedNotificationObserver js_reload_observer( content::NOTIFICATION_LOAD_STOP, content::Source<NavigationController>( &browser()->tab_strip_model()->GetActiveWebContents()-> GetController())); LOG(INFO) << "Executing location.reload()."; ASSERT_TRUE(content::ExecuteScript(contents, "location.reload();")); js_reload_observer.Wait(); LOG(INFO) << "Executing location.reload() - done."; EXPECT_TRUE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); // Disable app and reload via JavaScript. LOG(INFO) << "Disabling extension."; DisableExtension(app->id()); LOG(INFO) << "Disabling extension - done."; content::WindowedNotificationObserver js_reload_observer2( content::NOTIFICATION_LOAD_STOP, content::Source<NavigationController>( &browser()->tab_strip_model()->GetActiveWebContents()-> GetController())); LOG(INFO) << "Executing location = location."; ASSERT_TRUE(content::ExecuteScript(contents, "location = location;")); js_reload_observer2.Wait(); LOG(INFO) << "Executing location = location - done."; EXPECT_FALSE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); } // Tests that if we have a non-app process (path3/container.html) that has an // iframe with a URL in the app's extent (path1/iframe.html), then opening a // link from that iframe to a new window to a URL in the app's extent (path1/ // empty.html) results in the new window being in an app process. See // http://crbug.com/89272 for more details. IN_PROC_BROWSER_TEST_F(AppApiTest, OpenAppFromIframe) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (http://crbug.com/262796). if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) return; #endif extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL base_url = GetTestBaseURL("app_process"); // Load app and start URL (not in the app). const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); ASSERT_TRUE(app); ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path3/container.html")); EXPECT_FALSE(process_map->Contains( browser()->tab_strip_model()->GetWebContentsAt(0)-> GetRenderProcessHost()->GetID())); const BrowserList* active_browser_list = BrowserList::GetInstance(chrome::GetActiveDesktop()); EXPECT_EQ(2U, active_browser_list->size()); content::WebContents* popup_contents = active_browser_list->get(1)->tab_strip_model()->GetActiveWebContents(); content::WaitForLoadStop(popup_contents); // Popup window should be in the app's process. RenderViewHost* popup_host = popup_contents->GetRenderViewHost(); EXPECT_TRUE(process_map->Contains(popup_host->GetProcess()->GetID())); } // Similar to the previous test, but ensure that popup blocking bypass // isn't granted to the iframe. See crbug.com/117446. #if defined(OS_CHROMEOS) // http://crbug.com/153513 #define MAYBE_OpenAppFromIframe DISABLED_OpenAppFromIframe #else #define MAYBE_OpenAppFromIframe OpenAppFromIframe #endif IN_PROC_BROWSER_TEST_F(BlockedAppApiTest, MAYBE_OpenAppFromIframe) { host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); // Load app and start URL (not in the app). const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); ASSERT_TRUE(app); ui_test_utils::NavigateToURL( browser(), GetTestBaseURL("app_process").Resolve("path3/container.html")); WebContents* tab = browser()->tab_strip_model()->GetActiveWebContents(); PopupBlockerTabHelper* popup_blocker_tab_helper = PopupBlockerTabHelper::FromWebContents(tab); if (!popup_blocker_tab_helper->GetBlockedPopupsCount()) { content::WindowedNotificationObserver observer( chrome::NOTIFICATION_WEB_CONTENT_SETTINGS_CHANGED, content::NotificationService::AllSources()); observer.Wait(); } EXPECT_EQ(1u, popup_blocker_tab_helper->GetBlockedPopupsCount()); } // Tests that if an extension launches an app via chrome.tabs.create with an URL // that's not in the app's extent but that server redirects to it, we still end // up with an app process. See http://crbug.com/99349 for more details. IN_PROC_BROWSER_TEST_F(AppApiTest, ServerRedirectToAppFromExtension) { host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(StartEmbeddedTestServer()); LoadExtension(test_data_dir_.AppendASCII("app_process")); const Extension* launcher = LoadExtension(test_data_dir_.AppendASCII("app_launcher")); // There should be two navigations by the time the app page is loaded. // 1. The extension launcher page. // 2. The app's URL (which includes a server redirect). // Note that the server redirect does not generate a navigation event. content::TestNavigationObserver test_navigation_observer( browser()->tab_strip_model()->GetActiveWebContents(), 2); test_navigation_observer.StartWatchingNewWebContents(); // Load the launcher extension, which should launch the app. ui_test_utils::NavigateToURLWithDisposition( browser(), launcher->GetResourceURL("server_redirect.html"), CURRENT_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); // Wait for app tab to be created and loaded. test_navigation_observer.Wait(); // App has loaded, and chrome.app.isInstalled should be true. bool is_installed = false; ASSERT_TRUE(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetActiveWebContents(), "window.domAutomationController.send(chrome.app.isInstalled)", &is_installed)); ASSERT_TRUE(is_installed); } // Tests that if an extension launches an app via chrome.tabs.create with an URL // that's not in the app's extent but that client redirects to it, we still end // up with an app process. IN_PROC_BROWSER_TEST_F(AppApiTest, ClientRedirectToAppFromExtension) { host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(StartEmbeddedTestServer()); LoadExtension(test_data_dir_.AppendASCII("app_process")); const Extension* launcher = LoadExtension(test_data_dir_.AppendASCII("app_launcher")); // There should be three navigations by the time the app page is loaded. // 1. The extension launcher page. // 2. The URL that the extension launches, which client redirects. // 3. The app's URL. content::TestNavigationObserver test_navigation_observer( browser()->tab_strip_model()->GetActiveWebContents(), 3); test_navigation_observer.StartWatchingNewWebContents(); // Load the launcher extension, which should launch the app. ui_test_utils::NavigateToURLWithDisposition( browser(), launcher->GetResourceURL("client_redirect.html"), CURRENT_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); // Wait for app tab to be created and loaded. test_navigation_observer.Wait(); // App has loaded, and chrome.app.isInstalled should be true. bool is_installed = false; ASSERT_TRUE(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetActiveWebContents(), "window.domAutomationController.send(chrome.app.isInstalled)", &is_installed)); ASSERT_TRUE(is_installed); } // Tests that if we have an app process (path1/container.html) with a non-app // iframe (path3/iframe.html), then opening a link from that iframe to a new // window to a same-origin non-app URL (path3/empty.html) should keep the window // in the app process. // This is in contrast to OpenAppFromIframe, since here the popup will not be // missing special permissions and should be scriptable from the iframe. // See http://crbug.com/92669 for more details. IN_PROC_BROWSER_TEST_F(AppApiTest, OpenWebPopupFromWebIframe) { extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL base_url = GetTestBaseURL("app_process"); // Load app and start URL (in the app). const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); ASSERT_TRUE(app); ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/container.html")); content::RenderProcessHost* process = browser()->tab_strip_model()->GetWebContentsAt(0)->GetRenderProcessHost(); EXPECT_TRUE(process_map->Contains(process->GetID())); // Popup window should be in the app's process. const BrowserList* active_browser_list = BrowserList::GetInstance(chrome::GetActiveDesktop()); EXPECT_EQ(2U, active_browser_list->size()); content::WebContents* popup_contents = active_browser_list->get(1)->tab_strip_model()->GetActiveWebContents(); content::WaitForLoadStop(popup_contents); RenderViewHost* popup_host = popup_contents->GetRenderViewHost(); EXPECT_EQ(process, popup_host->GetProcess()); } // http://crbug.com/118502 #if defined(OS_MACOSX) || defined(OS_LINUX) #define MAYBE_ReloadAppAfterCrash DISABLED_ReloadAppAfterCrash #else #define MAYBE_ReloadAppAfterCrash ReloadAppAfterCrash #endif IN_PROC_BROWSER_TEST_F(AppApiTest, MAYBE_ReloadAppAfterCrash) { extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("app_process"))); GURL base_url = GetTestBaseURL("app_process"); // Load the app, chrome.app.isInstalled should be true. ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/empty.html")); WebContents* contents = browser()->tab_strip_model()->GetWebContentsAt(0); EXPECT_TRUE(process_map->Contains( contents->GetRenderProcessHost()->GetID())); bool is_installed = false; ASSERT_TRUE(content::ExecuteScriptAndExtractBool( contents, "window.domAutomationController.send(chrome.app.isInstalled)", &is_installed)); ASSERT_TRUE(is_installed); // Crash the tab and reload it, chrome.app.isInstalled should still be true. content::CrashTab(browser()->tab_strip_model()->GetActiveWebContents()); content::WindowedNotificationObserver observer( content::NOTIFICATION_LOAD_STOP, content::Source<NavigationController>( &browser()->tab_strip_model()->GetActiveWebContents()-> GetController())); chrome::Reload(browser(), CURRENT_TAB); observer.Wait(); ASSERT_TRUE(content::ExecuteScriptAndExtractBool( contents, "window.domAutomationController.send(chrome.app.isInstalled)", &is_installed)); ASSERT_TRUE(is_installed); } // Test that a cross-process navigation away from a hosted app stays in the same // BrowsingInstance, so that postMessage calls to the app's other windows still // work. IN_PROC_BROWSER_TEST_F(AppApiTest, SameBrowsingInstanceAfterSwap) { extensions::ProcessMap* process_map = extensions::ExtensionSystem::Get( browser()->profile())->extension_service()->process_map(); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); GURL base_url = GetTestBaseURL("app_process"); // Load app and start URL (in the app). const Extension* app = LoadExtension(test_data_dir_.AppendASCII("app_process")); ASSERT_TRUE(app); ui_test_utils::NavigateToURL(browser(), base_url.Resolve("path1/iframe.html")); content::SiteInstance* app_instance = browser()->tab_strip_model()->GetWebContentsAt(0)->GetSiteInstance(); EXPECT_TRUE(process_map->Contains(app_instance->GetProcess()->GetID())); // Popup window should be in the app's process. const BrowserList* active_browser_list = BrowserList::GetInstance(chrome::GetActiveDesktop()); EXPECT_EQ(2U, active_browser_list->size()); content::WebContents* popup_contents = active_browser_list->get(1)->tab_strip_model()->GetActiveWebContents(); content::WaitForLoadStop(popup_contents); SiteInstance* popup_instance = popup_contents->GetSiteInstance(); EXPECT_EQ(app_instance, popup_instance); // Navigate the popup to another process outside the app. GURL non_app_url(base_url.Resolve("path3/empty.html")); ui_test_utils::NavigateToURL(active_browser_list->get(1), non_app_url); SiteInstance* new_instance = popup_contents->GetSiteInstance(); EXPECT_NE(app_instance, new_instance); // It should still be in the same BrowsingInstance, allowing postMessage to // work. EXPECT_TRUE(app_instance->IsRelatedSiteInstance(new_instance)); }