blob: 9c91225af7e7c4589ef9ce53a3fb7a235f226343 [file] [log] [blame]
// Copyright 2015 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 <string>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/json/json_reader.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.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/extensions/app_launch_params.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/extensions/hosted_app_browser_controller.h"
#include "chrome/browser/ui/page_info/page_info_dialog.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/context_menu_params.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/base/clipboard/clipboard.h"
#if defined(OS_MACOSX)
#include "chrome/common/chrome_features.h"
#endif
using content::RenderFrameHost;
using content::WebContents;
using extensions::Extension;
namespace {
constexpr const char kExampleURL[] = "http://example.org/";
constexpr const char kExampleURL2[] = "http://example.com/";
constexpr const char kAppDotComManifest[] = R"( { "name": "Hosted App",
"version": "1",
"manifest_version": 2,
"app": {
"launch": {
"web_url": "%s"
},
"urls": ["*://app.com/"]
}
} )";
enum class AppType {
HOSTED_APP,
BOOKMARK_APP,
};
const auto kAppTypeValues =
::testing::Values(AppType::HOSTED_APP, AppType::BOOKMARK_APP);
void NavigateToURLAndWait(Browser* browser, const GURL& url) {
content::TestNavigationObserver observer(
browser->tab_strip_model()->GetActiveWebContents(),
content::MessageLoopRunner::QuitMode::DEFERRED);
NavigateParams params(browser, url, ui::PAGE_TRANSITION_LINK);
ui_test_utils::NavigateToURL(&params);
observer.Wait();
}
// Used by ShouldLocationBarForXXX. Performs a navigation and then checks that
// the location bar visibility is as expcted.
void NavigateAndCheckForLocationBar(Browser* browser,
const std::string& url_string,
bool expected_visibility) {
GURL url(url_string);
NavigateToURLAndWait(browser, url);
EXPECT_EQ(expected_visibility,
browser->hosted_app_controller()->ShouldShowLocationBar());
}
} // namespace
// Parameters are {app_type, desktop_pwa_flag}. |app_type| controls whether it
// is a Hosted or Bookmark app. |desktop_pwa_flag| enables the
// kDesktopPWAWindowing flag.
class HostedAppTest
: public ExtensionBrowserTest,
public ::testing::WithParamInterface<std::tuple<AppType, bool>> {
public:
HostedAppTest() : app_browser_(nullptr) {}
~HostedAppTest() override {}
void SetUp() override {
bool desktop_pwa_flag;
std::tie(app_type_, desktop_pwa_flag) = GetParam();
if (desktop_pwa_flag) {
scoped_feature_list_.InitAndEnableFeature(features::kDesktopPWAWindowing);
} else {
#if defined(OS_MACOSX)
scoped_feature_list_.InitAndEnableFeature(features::kBookmarkApps);
#endif
}
ExtensionBrowserTest::SetUp();
}
protected:
void SetupApp(const std::string& app_folder) {
SetupApp(test_data_dir_.AppendASCII(app_folder));
}
void SetupApp(const base::FilePath& app_folder) {
const Extension* app = InstallExtensionWithSourceAndFlags(
app_folder, 1, extensions::Manifest::INTERNAL,
app_type_ == AppType::BOOKMARK_APP
? extensions::Extension::FROM_BOOKMARK
: extensions::Extension::NO_FLAGS);
ASSERT_TRUE(app);
// Launch it in a window.
app_browser_ = LaunchAppBrowser(app);
ASSERT_TRUE(app_browser_);
ASSERT_TRUE(app_browser_ != browser());
}
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
}
// Tests that performing |action| results in a new foreground tab
// that navigated to |target_url| in the main browser window.
void TestAppActionOpensForegroundTab(base::OnceClosure action,
const GURL& target_url) {
ASSERT_EQ(app_browser_, chrome::FindLastActive());
size_t num_browsers = chrome::GetBrowserCount(profile());
int num_tabs = browser()->tab_strip_model()->count();
content::WebContents* initial_tab =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_NO_FATAL_FAILURE(std::move(action).Run());
EXPECT_EQ(num_browsers, chrome::GetBrowserCount(profile()));
EXPECT_EQ(browser(), chrome::FindLastActive());
EXPECT_EQ(++num_tabs, browser()->tab_strip_model()->count());
content::WebContents* new_tab =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_NE(initial_tab, new_tab);
EXPECT_EQ(target_url, new_tab->GetLastCommittedURL());
}
Browser* app_browser_;
AppType app_type() const { return app_type_; }
private:
base::test::ScopedFeatureList scoped_feature_list_;
AppType app_type_;
DISALLOW_COPY_AND_ASSIGN(HostedAppTest);
};
// Tests that "Open link in new tab" opens a link in a foreground tab.
// Flaky, see https://crbug.com/795055
IN_PROC_BROWSER_TEST_P(HostedAppTest, DISABLED_OpenLinkInNewTab) {
SetupApp("app");
const GURL url("http://www.foo.com/");
TestAppActionOpensForegroundTab(
base::BindOnce(
[](content::WebContents* app_contents, const GURL& target_url) {
ui_test_utils::UrlLoadObserver url_observer(
target_url, content::NotificationService::AllSources());
content::ContextMenuParams params;
params.page_url = app_contents->GetLastCommittedURL();
params.link_url = target_url;
TestRenderViewContextMenu menu(app_contents->GetMainFrame(),
params);
menu.Init();
menu.ExecuteCommand(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB,
0 /* event_flags */);
url_observer.Wait();
},
app_browser_->tab_strip_model()->GetActiveWebContents(), url),
url);
}
// Tests that Ctrl + Clicking a link opens a foreground tab.
IN_PROC_BROWSER_TEST_P(HostedAppTest, CtrlClickLink) {
ASSERT_TRUE(embedded_test_server()->Start());
// Set up an app which covers app.com URLs.
GURL app_url =
embedded_test_server()->GetURL("app.com", "/click_modifier/href.html");
ui_test_utils::UrlLoadObserver url_observer(
app_url, content::NotificationService::AllSources());
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(
base::StringPrintf(kAppDotComManifest, app_url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
// Wait for the URL to load so that we can click on the page.
url_observer.Wait();
const GURL url = embedded_test_server()->GetURL(
"app.com", "/click_modifier/new_window.html");
TestAppActionOpensForegroundTab(
base::BindOnce(
[](content::WebContents* app_contents, const GURL& target_url) {
ui_test_utils::UrlLoadObserver url_observer(
target_url, content::NotificationService::AllSources());
int ctrl_key;
#if defined(OS_MACOSX)
ctrl_key = blink::WebInputEvent::Modifiers::kMetaKey;
#else
ctrl_key = blink::WebInputEvent::Modifiers::kControlKey;
#endif
content::SimulateMouseClick(app_contents, ctrl_key,
blink::WebMouseEvent::Button::kLeft);
url_observer.Wait();
},
app_browser_->tab_strip_model()->GetActiveWebContents(), url),
url);
}
// Check that the location bar is shown correctly.
IN_PROC_BROWSER_TEST_P(HostedAppTest, ShouldShowLocationBar) {
SetupApp("https_app");
// Navigate to the app's launch page; the location bar should be hidden.
NavigateAndCheckForLocationBar(app_browser_,
"https://www.example.com/empty.html", false);
// Navigate to another page on the same origin; the location bar should still
// hidden.
NavigateAndCheckForLocationBar(app_browser_, "https://www.example.com/blah",
false);
// Navigate to different origin; the location bar should now be visible.
NavigateAndCheckForLocationBar(app_browser_, "https://www.foo.com/blah",
true);
}
// Check that the location bar is shown correctly for HTTP apps when they
// navigate to a HTTPS page on the same origin.
//
// TODO(mgiuca): Disabled on Windows and macOS for being flaky:
// https://crbug.com/814400
#if defined(OS_WIN) || defined(OS_MACOSX)
#define MAYBE_ShouldShowLocationBarForHTTPApp \
DISABLED_ShouldShowLocationBarForHTTPApp
#else
#define MAYBE_ShouldShowLocationBarForHTTPApp ShouldShowLocationBarForHTTPApp
#endif
IN_PROC_BROWSER_TEST_P(HostedAppTest, MAYBE_ShouldShowLocationBarForHTTPApp) {
SetupApp("app");
// Navigate to the app's launch page; the location bar should be visible, even
// though it exactly matches the site, because it is not secure.
NavigateAndCheckForLocationBar(app_browser_,
"http://www.example.com/empty.html", true);
// Navigate to the https version of the site; the location bar should
// be hidden, as it is a more secure version of the site.
NavigateAndCheckForLocationBar(
app_browser_, "https://www.example.com/blah", false);
}
// TODO(mgiuca): Disabled on Windows for being flaky: https://crbug.com/815246
#if defined(OS_WIN)
#define MAYBE_ShouldShowLocationBarForHTTPSApp \
DISABLED_ShouldShowLocationBarForHTTPSApp
#else
#define MAYBE_ShouldShowLocationBarForHTTPSApp ShouldShowLocationBarForHTTPSApp
#endif
// Check that the location bar is shown correctly for HTTPS apps when they
// navigate to a HTTP page on the same origin.
IN_PROC_BROWSER_TEST_P(HostedAppTest, MAYBE_ShouldShowLocationBarForHTTPSApp) {
SetupApp("https_app");
// Navigate to the app's launch page; the location bar should be hidden.
NavigateAndCheckForLocationBar(
app_browser_, "https://www.example.com/empty.html", false);
// Navigate to the http version of the site; the location bar should
// be visible for the https version as it is not secure.
NavigateAndCheckForLocationBar(
app_browser_, "http://www.example.com/blah", true);
}
// Check that the location bar is shown correctly for apps that specify start
// URLs without the 'www.' prefix.
IN_PROC_BROWSER_TEST_P(HostedAppTest, ShouldShowLocationBarForAppWithoutWWW) {
SetupApp("https_app_no_www");
// Navigate to the app's launch page; the location bar should be hidden.
NavigateAndCheckForLocationBar(app_browser_, "https://example.com/empty.html",
false);
// Navigate to the app's launch page with the 'www.' prefis; the location bar
// should be hidden.
NavigateAndCheckForLocationBar(app_browser_,
"https://www.example.com/empty.html", false);
// Navigate to different origin; the location bar should now be visible.
NavigateAndCheckForLocationBar(app_browser_, "https://www.foo.com/blah",
true);
}
// Check that a subframe on a regular web page can navigate to a URL that
// redirects to a hosted app. https://crbug.com/721949.
IN_PROC_BROWSER_TEST_P(HostedAppTest, SubframeRedirectsToHostedApp) {
// This test only applies to hosted apps.
if (app_type() != AppType::HOSTED_APP)
return;
ASSERT_TRUE(embedded_test_server()->Start());
// Set up an app which covers app.com URLs.
GURL app_url = embedded_test_server()->GetURL("app.com", "/title1.html");
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(
base::StringPrintf(kAppDotComManifest, app_url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
// Navigate a regular tab to a page with a subframe.
GURL url = embedded_test_server()->GetURL("foo.com", "/iframe.html");
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
NavigateToURLAndWait(browser(), url);
// Navigate the subframe to a URL that redirects to a URL in the hosted app's
// web extent.
GURL redirect_url = embedded_test_server()->GetURL(
"bar.com", "/server-redirect?" + app_url.spec());
EXPECT_TRUE(NavigateIframeToURL(tab, "test", redirect_url));
// Ensure that the frame navigated successfully and that it has correct
// content.
RenderFrameHost* subframe = content::ChildFrameAt(tab->GetMainFrame(), 0);
EXPECT_EQ(app_url, subframe->GetLastCommittedURL());
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
subframe, "window.domAutomationController.send(document.body.innerText);",
&result));
EXPECT_EQ("This page has no title.", result);
}
IN_PROC_BROWSER_TEST_P(HostedAppTest, BookmarkAppThemeColor) {
if (app_type() != AppType::BOOKMARK_APP)
return;
{
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL);
web_app_info.scope = GURL(kExampleURL);
web_app_info.theme_color = SkColorSetA(SK_ColorBLUE, 0xF0);
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
EXPECT_EQ(
web_app::GetExtensionIdFromApplicationName(app_browser->app_name()),
app->id());
EXPECT_EQ(web_app_info.theme_color,
app_browser->hosted_app_controller()->GetThemeColor().value());
}
{
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL("http://example.org/2");
web_app_info.scope = GURL("http://example.org/");
web_app_info.theme_color = base::Optional<SkColor>();
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
EXPECT_EQ(
web_app::GetExtensionIdFromApplicationName(app_browser->app_name()),
app->id());
EXPECT_FALSE(
app_browser->hosted_app_controller()->GetThemeColor().has_value());
}
}
// Ensure that hosted app windows with blank titles don't display the URL as a
// default window title.
IN_PROC_BROWSER_TEST_P(HostedAppTest, Title) {
if (app_type() != AppType::BOOKMARK_APP)
return;
ASSERT_TRUE(embedded_test_server()->Start());
GURL url = embedded_test_server()->GetURL("app.site.com", "/empty.html");
WebApplicationInfo web_app_info;
web_app_info.app_url = url;
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
content::WebContents* web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
content::WaitForLoadStop(web_contents);
EXPECT_EQ(base::string16(), app_browser->GetWindowTitleForCurrentTab(false));
NavigateToURLAndWait(app_browser, embedded_test_server()->GetURL(
"app.site.com", "/simple.html"));
EXPECT_EQ(base::ASCIIToUTF16("OK"),
app_browser->GetWindowTitleForCurrentTab(false));
}
using HostedAppPWAOnlyTest = HostedAppTest;
// Check the 'Copy URL' menu button for Hosted App windows.
IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest, CopyURL) {
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL);
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
content::BrowserTestClipboardScope test_clipboard_scope;
chrome::ExecuteCommand(app_browser, IDC_COPY_URL);
ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
base::string16 result;
clipboard->ReadText(ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
EXPECT_EQ(result, base::UTF8ToUTF16(kExampleURL));
}
// Check the 'Open in Chrome' menu button for Hosted App windows.
IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest, OpenInChrome) {
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL);
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
{
Browser* app_browser = LaunchAppBrowser(app);
EXPECT_EQ(1, app_browser->tab_strip_model()->count());
EXPECT_EQ(1, browser()->tab_strip_model()->count());
ASSERT_EQ(2u, chrome::GetBrowserCount(browser()->profile()));
chrome::ExecuteCommand(app_browser, IDC_OPEN_IN_CHROME);
// The browser frame is closed next event loop so it's still safe to access
// here.
EXPECT_EQ(0, app_browser->tab_strip_model()->count());
EXPECT_EQ(2, browser()->tab_strip_model()->count());
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
EXPECT_EQ(
GURL(kExampleURL),
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
}
// Wait until the browser actually gets closed. This invalidates
// |app_browser|.
content::RunAllPendingInMessageLoop();
ASSERT_EQ(1u, chrome::GetBrowserCount(browser()->profile()));
}
// This feature is only available for ChromeOS at the moment.
#if defined(OS_CHROMEOS)
// Check the 'App info' menu button for Hosted App windows.
IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest, AppInfoOpensPageInfo) {
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL);
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
bool dialog_created = false;
GetPageInfoDialogCreatedCallbackForTesting() = base::BindOnce(
[](bool* dialog_created) { *dialog_created = true; }, &dialog_created);
chrome::ExecuteCommand(app_browser, IDC_HOSTED_APP_MENU_APP_INFO);
EXPECT_TRUE(dialog_created);
// The test closure should have run. But clear the global in case it hasn't.
EXPECT_FALSE(GetPageInfoDialogCreatedCallbackForTesting());
GetPageInfoDialogCreatedCallbackForTesting().Reset();
}
#endif
IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest, EngagementHistogram) {
base::HistogramTester histograms;
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL);
web_app_info.scope = GURL(kExampleURL);
web_app_info.theme_color = base::Optional<SkColor>();
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
NavigateToURLAndWait(app_browser, GURL(kExampleURL));
// Test shortcut launch.
EXPECT_EQ(web_app::GetExtensionIdFromApplicationName(app_browser->app_name()),
app->id());
histograms.ExpectUniqueSample(
extensions::kPwaWindowEngagementTypeHistogram,
SiteEngagementService::ENGAGEMENT_WEBAPP_SHORTCUT_LAUNCH, 1);
// Test some other engagement events by directly calling into
// SiteEngagementService.
content::WebContents* web_contents =
app_browser->tab_strip_model()->GetActiveWebContents();
SiteEngagementService* site_engagement_service =
SiteEngagementService::Get(app_browser->profile());
site_engagement_service->HandleMediaPlaying(web_contents, false);
site_engagement_service->HandleMediaPlaying(web_contents, true);
site_engagement_service->HandleNavigation(web_contents,
ui::PAGE_TRANSITION_TYPED);
site_engagement_service->HandleUserInput(
web_contents, SiteEngagementService::ENGAGEMENT_MOUSE);
histograms.ExpectTotalCount(extensions::kPwaWindowEngagementTypeHistogram, 5);
histograms.ExpectBucketCount(extensions::kPwaWindowEngagementTypeHistogram,
SiteEngagementService::ENGAGEMENT_MEDIA_VISIBLE,
1);
histograms.ExpectBucketCount(extensions::kPwaWindowEngagementTypeHistogram,
SiteEngagementService::ENGAGEMENT_MEDIA_HIDDEN,
1);
histograms.ExpectBucketCount(extensions::kPwaWindowEngagementTypeHistogram,
SiteEngagementService::ENGAGEMENT_NAVIGATION, 1);
histograms.ExpectBucketCount(extensions::kPwaWindowEngagementTypeHistogram,
SiteEngagementService::ENGAGEMENT_MOUSE, 1);
}
IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest,
EngagementHistogramNotRecordedIfNoScope) {
base::HistogramTester histograms;
WebApplicationInfo web_app_info;
// App with no scope.
web_app_info.app_url = GURL(kExampleURL);
web_app_info.theme_color = base::Optional<SkColor>();
const extensions::Extension* app = InstallBookmarkApp(web_app_info);
Browser* app_browser = LaunchAppBrowser(app);
EXPECT_EQ(web_app::GetExtensionIdFromApplicationName(app_browser->app_name()),
app->id());
histograms.ExpectTotalCount(extensions::kPwaWindowEngagementTypeHistogram, 0);
}
IN_PROC_BROWSER_TEST_P(HostedAppPWAOnlyTest, EngagementHistogramTwoApps) {
base::HistogramTester histograms;
const extensions::Extension *app1, *app2;
// Install two apps.
{
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL);
web_app_info.scope = GURL(kExampleURL);
web_app_info.theme_color = base::Optional<SkColor>();
app1 = InstallBookmarkApp(web_app_info);
}
{
WebApplicationInfo web_app_info;
web_app_info.app_url = GURL(kExampleURL2);
web_app_info.scope = GURL(kExampleURL2);
web_app_info.theme_color = base::Optional<SkColor>();
app2 = InstallBookmarkApp(web_app_info);
}
// Launch them three times. This ensures that each launch only logs once.
// (Since all apps receive the notification on launch, there is a danger that
// we might log too many times.)
Browser* app_browser1 = LaunchAppBrowser(app1);
Browser* app_browser2 = LaunchAppBrowser(app1);
Browser* app_browser3 = LaunchAppBrowser(app2);
EXPECT_EQ(
web_app::GetExtensionIdFromApplicationName(app_browser1->app_name()),
app1->id());
EXPECT_EQ(
web_app::GetExtensionIdFromApplicationName(app_browser2->app_name()),
app1->id());
EXPECT_EQ(
web_app::GetExtensionIdFromApplicationName(app_browser3->app_name()),
app2->id());
histograms.ExpectUniqueSample(
extensions::kPwaWindowEngagementTypeHistogram,
SiteEngagementService::ENGAGEMENT_WEBAPP_SHORTCUT_LAUNCH, 3);
}
// Common app manifest for HostedAppProcessModelTests.
constexpr const char kHostedAppProcessModelManifest[] =
R"( { "name": "Hosted App Process Model Test",
"version": "1",
"manifest_version": 2,
"app": {
"launch": {
"web_url": "%s"
},
"urls": ["*://app.site.com/frame_tree", "*://isolated.site.com/"]
}
} )";
// This set of tests verifies the hosted app process model behavior in various
// isolation modes. They can be run by default, with --site-per-process, or
// with --top-document-isolation. In each mode, they contain an isolated origin.
//
// Relevant frames in the tests:
// - |app| - app.site.com/frame_tree/cross_origin_but_same_site_frames.html
// Main frame, launch URL of the hosted app (i.e. app.launch.web_url).
// - |same_dir| - app.site.com/frame_tree/simple.htm
// Another URL, but still covered by hosted app's web extent
// (i.e. by app.urls).
// - |diff_dir| - app.site.com/save_page/a.htm
// Same origin as |same_dir| and |app|, but not covered by app's
// extent.
// - |same_site| - other.site.com/title1.htm
// Different origin, but same site as |app|, |same_dir|,
// |diff_dir|.
// - |isolated| - isolated.site.com/title1.htm
// Within app's extent, but belongs to an isolated origin.
// - |cross_site| - cross.domain.com/title1.htm
// Cross-site from all the other frames.
class HostedAppProcessModelTest : public HostedAppTest {
public:
HostedAppProcessModelTest() {}
~HostedAppProcessModelTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
std::string origin_list =
embedded_test_server()->GetURL("isolated.site.com", "/").spec();
command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list);
}
void SetUpOnMainThread() override {
HostedAppTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
embedded_test_server()->StartAcceptingConnections();
// TODO(alexmos): This should also be true for TDI, if
// base::FeatureList::IsEnabled(features::kTopDocumentIsolation) is true.
// However, we can't do that yet, because
// ChromeContentBrowserClientExtensionsPart::
// ShouldFrameShareParentSiteInstanceDespiteTopDocumentIsolation() returns
// true for all hosted apps and hence forces even cross-site subframes to
// be kept in the app process.
should_swap_for_cross_site_ = content::AreAllSitesIsolatedForTesting();
process_map_ = extensions::ProcessMap::Get(browser()->profile());
same_dir_url_ = embedded_test_server()->GetURL("app.site.com",
"/frame_tree/simple.htm");
diff_dir_url_ =
embedded_test_server()->GetURL("app.site.com", "/save_page/a.htm");
same_site_url_ =
embedded_test_server()->GetURL("other.site.com", "/title1.html");
isolated_url_ =
embedded_test_server()->GetURL("isolated.site.com", "/title1.html");
cross_site_url_ =
embedded_test_server()->GetURL("cross.domain.com", "/title1.html");
}
// Opens a popup from |rfh| to |url|, verifies whether it should stay in the
// same process as |rfh| and whether it should be in an app process, and then
// closes the popup.
void TestPopupProcess(RenderFrameHost* rfh,
const GURL& url,
bool expect_same_process,
bool expect_app_process) {
content::WebContentsAddedObserver tab_added_observer;
ASSERT_TRUE(
content::ExecuteScript(rfh, "window.open('" + url.spec() + "');"));
content::WebContents* new_tab = tab_added_observer.GetWebContents();
ASSERT_TRUE(new_tab);
EXPECT_TRUE(WaitForLoadStop(new_tab));
EXPECT_EQ(url, new_tab->GetLastCommittedURL());
RenderFrameHost* new_rfh = new_tab->GetMainFrame();
EXPECT_EQ(expect_same_process, rfh->GetProcess() == new_rfh->GetProcess())
<< " for " << url << " from " << rfh->GetLastCommittedURL();
EXPECT_EQ(expect_app_process,
process_map_->Contains(new_rfh->GetProcess()->GetID()))
<< " for " << url << " from " << rfh->GetLastCommittedURL();
EXPECT_EQ(expect_app_process,
new_rfh->GetSiteInstance()->GetSiteURL().SchemeIs(
extensions::kExtensionScheme))
<< " for " << url << " from " << rfh->GetLastCommittedURL();
ASSERT_TRUE(content::ExecuteScript(new_rfh, "window.close();"));
}
// Creates a subframe underneath |parent_rfh| to |url|, verifies whether it
// should stay in the same process as |parent_rfh| and whether it should be in
// an app process, and returns the subframe RFH.
RenderFrameHost* TestSubframeProcess(RenderFrameHost* parent_rfh,
const GURL& url,
bool expect_same_process,
bool expect_app_process) {
return TestSubframeProcess(parent_rfh, url, "", expect_same_process,
expect_app_process);
}
RenderFrameHost* TestSubframeProcess(RenderFrameHost* parent_rfh,
const GURL& url,
const std::string& element_id,
bool expect_same_process,
bool expect_app_process) {
WebContents* web_contents = WebContents::FromRenderFrameHost(parent_rfh);
content::TestNavigationObserver nav_observer(web_contents, 1);
std::string script = "var f = document.createElement('iframe');";
if (!element_id.empty())
script += "f.id = '" + element_id + "';";
script += "f.src = '" + url.spec() + "';";
script += "document.body.appendChild(f);";
EXPECT_TRUE(ExecuteScript(parent_rfh, script));
nav_observer.Wait();
RenderFrameHost* subframe = content::FrameMatchingPredicate(
web_contents, base::Bind(&content::FrameHasSourceUrl, url));
EXPECT_EQ(expect_same_process,
parent_rfh->GetProcess() == subframe->GetProcess())
<< " for " << url << " from " << parent_rfh->GetLastCommittedURL();
EXPECT_EQ(expect_app_process,
process_map_->Contains(subframe->GetProcess()->GetID()))
<< " for " << url << " from " << parent_rfh->GetLastCommittedURL();
EXPECT_EQ(expect_app_process,
subframe->GetSiteInstance()->GetSiteURL().SchemeIs(
extensions::kExtensionScheme))
<< " for " << url << " from " << parent_rfh->GetLastCommittedURL();
return subframe;
}
protected:
bool should_swap_for_cross_site_;
extensions::ProcessMap* process_map_;
GURL same_dir_url_;
GURL diff_dir_url_;
GURL same_site_url_;
GURL isolated_url_;
GURL cross_site_url_;
private:
DISALLOW_COPY_AND_ASSIGN(HostedAppProcessModelTest);
};
// Tests that same-site iframes stay inside the hosted app process, even when
// they are not within the hosted app's extent. This allows same-site scripting
// to work and avoids unnecessary OOPIFs. Also tests that isolated origins in
// iframes do not stay in the app's process, nor do cross-site iframes in modes
// that require them to swap.
IN_PROC_BROWSER_TEST_P(HostedAppProcessModelTest, IframesInsideHostedApp) {
// Set up and launch the hosted app.
GURL url = embedded_test_server()->GetURL(
"app.site.com", "/frame_tree/cross_origin_but_same_site_frames.html");
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(
base::StringPrintf(kHostedAppProcessModelManifest, url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
content::WebContents* web_contents =
app_browser_->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
auto find_frame = [web_contents](const std::string& name) {
return content::FrameMatchingPredicate(
web_contents, base::Bind(&content::FrameMatchesName, name));
};
RenderFrameHost* app = web_contents->GetMainFrame();
RenderFrameHost* same_dir = find_frame("SameOrigin-SamePath");
RenderFrameHost* diff_dir = find_frame("SameOrigin-DifferentPath");
RenderFrameHost* same_site = find_frame("OtherSubdomain-SameSite");
RenderFrameHost* isolated = find_frame("Isolated-SameSite");
RenderFrameHost* cross_site = find_frame("CrossSite");
// Sanity-check sites of all relevant frames to verify test setup.
GURL app_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), app->GetLastCommittedURL());
EXPECT_EQ(extensions::kExtensionScheme, app_site.scheme());
GURL same_dir_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), same_dir->GetLastCommittedURL());
EXPECT_EQ(extensions::kExtensionScheme, same_dir_site.scheme());
EXPECT_EQ(same_dir_site, app_site);
GURL diff_dir_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), diff_dir->GetLastCommittedURL());
EXPECT_NE(extensions::kExtensionScheme, diff_dir_site.scheme());
EXPECT_NE(diff_dir_site, app_site);
GURL same_site_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), same_site->GetLastCommittedURL());
EXPECT_NE(extensions::kExtensionScheme, same_site_site.scheme());
EXPECT_NE(same_site_site, app_site);
EXPECT_EQ(same_site_site, diff_dir_site);
GURL isolated_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), isolated->GetLastCommittedURL());
EXPECT_NE(extensions::kExtensionScheme, isolated_site.scheme());
EXPECT_NE(isolated_site, app_site);
EXPECT_NE(isolated_site, diff_dir_site);
GURL cross_site_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), cross_site->GetLastCommittedURL());
EXPECT_NE(cross_site_site, app_site);
EXPECT_NE(cross_site_site, same_site_site);
// Verify that |same_dir| and |diff_dir| have the same origin according to
// |window.origin| (even though they have different |same_dir_site| and
// |diff_dir_site|).
std::string same_dir_origin;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
same_dir, "domAutomationController.send(window.origin)",
&same_dir_origin));
std::string diff_dir_origin;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
diff_dir, "domAutomationController.send(window.origin)",
&diff_dir_origin));
EXPECT_EQ(diff_dir_origin, same_dir_origin);
// Verify that (1) all same-site iframes stay in the process, (2) isolated
// origin iframe does not, and (3) cross-site iframe leaves if the process
// model calls for it.
EXPECT_EQ(same_dir->GetProcess(), app->GetProcess());
EXPECT_EQ(diff_dir->GetProcess(), app->GetProcess());
EXPECT_EQ(same_site->GetProcess(), app->GetProcess());
EXPECT_NE(isolated->GetProcess(), app->GetProcess());
if (should_swap_for_cross_site_)
EXPECT_NE(cross_site->GetProcess(), app->GetProcess());
else
EXPECT_EQ(cross_site->GetProcess(), app->GetProcess());
// The isolated origin iframe's process should not be in the ProcessMap. If
// we swapped processes for the |cross_site| iframe, its process should also
// not be on the ProcessMap.
EXPECT_FALSE(process_map_->Contains(isolated->GetProcess()->GetID()));
if (should_swap_for_cross_site_)
EXPECT_FALSE(process_map_->Contains(cross_site->GetProcess()->GetID()));
// Verify that |same_dir| and |diff_dir| can script each other.
// (they should - they have the same origin).
std::string inner_text_from_other_frame;
const std::string r_script =
R"( var w = window.open('', 'SameOrigin-SamePath');
domAutomationController.send(w.document.body.innerText); )";
ASSERT_TRUE(content::ExecuteScriptAndExtractString(
diff_dir, r_script, &inner_text_from_other_frame));
EXPECT_EQ("Simple test page.", inner_text_from_other_frame);
}
// Check that if a hosted app has an iframe, and that iframe navigates to URLs
// that are same-site with the app, these navigations ends up in the app
// process.
IN_PROC_BROWSER_TEST_P(HostedAppProcessModelTest,
IframeNavigationsInsideHostedApp) {
// Set up and launch the hosted app.
GURL app_url =
embedded_test_server()->GetURL("app.site.com", "/frame_tree/simple.htm");
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(base::StringPrintf(kHostedAppProcessModelManifest,
app_url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
content::WebContents* web_contents =
app_browser_->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
RenderFrameHost* app = web_contents->GetMainFrame();
// Add a data: URL subframe. This should stay in the app process.
TestSubframeProcess(app, GURL("data:text/html,foo"), "test_iframe",
true /* expect_same_process */,
true /* expect_app_process */);
// Navigate iframe to a non-app-but-same-site-with-app URL and check that it
// stays in the parent process.
{
SCOPED_TRACE("... for data: -> diff_dir");
EXPECT_TRUE(
NavigateIframeToURL(web_contents, "test_iframe", diff_dir_url_));
EXPECT_EQ(ChildFrameAt(app, 0)->GetProcess(), app->GetProcess());
}
// Navigate the iframe to an isolated origin to force an OOPIF.
{
SCOPED_TRACE("... for diff_dir -> isolated");
EXPECT_TRUE(
NavigateIframeToURL(web_contents, "test_iframe", isolated_url_));
EXPECT_NE(ChildFrameAt(app, 0)->GetProcess(), app->GetProcess());
}
// Navigate the iframe to an app URL. This should go back to the app process.
{
SCOPED_TRACE("... for isolated -> same_dir");
EXPECT_TRUE(
NavigateIframeToURL(web_contents, "test_iframe", same_dir_url_));
EXPECT_EQ(ChildFrameAt(app, 0)->GetProcess(), app->GetProcess());
}
// Navigate the iframe back to the OOPIF again.
{
SCOPED_TRACE("... for same_dir -> isolated");
EXPECT_TRUE(
NavigateIframeToURL(web_contents, "test_iframe", isolated_url_));
EXPECT_NE(ChildFrameAt(app, 0)->GetProcess(), app->GetProcess());
}
// Navigate iframe to a non-app-but-same-site-with-app URL and check that it
// also goes back to the parent process.
{
SCOPED_TRACE("... for isolated -> diff_dir");
EXPECT_TRUE(
NavigateIframeToURL(web_contents, "test_iframe", diff_dir_url_));
EXPECT_EQ(ChildFrameAt(app, 0)->GetProcess(), app->GetProcess());
}
}
// Tests that popups opened within a hosted app behave as expected.
// TODO(creis): The goal is for same-site popups to stay in the app's process so
// that they can be scripted, but this is not yet supported. See
// https://crbug.com/718516.
IN_PROC_BROWSER_TEST_P(HostedAppProcessModelTest, PopupsInsideHostedApp) {
// Set up and launch the hosted app.
GURL url = embedded_test_server()->GetURL(
"app.site.com", "/frame_tree/cross_origin_but_same_site_frames.html");
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(
base::StringPrintf(kHostedAppProcessModelManifest, url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
content::WebContents* web_contents =
app_browser_->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
auto find_frame = [web_contents](const std::string& name) {
return content::FrameMatchingPredicate(
web_contents, base::Bind(&content::FrameMatchesName, name));
};
RenderFrameHost* app = web_contents->GetMainFrame();
RenderFrameHost* same_dir = find_frame("SameOrigin-SamePath");
RenderFrameHost* diff_dir = find_frame("SameOrigin-DifferentPath");
RenderFrameHost* same_site = find_frame("OtherSubdomain-SameSite");
RenderFrameHost* isolated = find_frame("Isolated-SameSite");
RenderFrameHost* cross_site = find_frame("CrossSite");
{
SCOPED_TRACE("... for same_dir popup");
TestPopupProcess(app, same_dir_url_, true, true);
}
{
SCOPED_TRACE("... for diff_dir popup");
// TODO(creis): This should stay in the app's process, but that's not yet
// supported in modes that swap for cross-site navigations.
TestPopupProcess(app, diff_dir_url_, !should_swap_for_cross_site_,
!should_swap_for_cross_site_);
}
{
SCOPED_TRACE("... for same_site popup");
// TODO(creis): This should stay in the app's process, but that's not yet
// supported in modes that swap for cross-site navigations.
TestPopupProcess(app, same_site_url_, !should_swap_for_cross_site_,
!should_swap_for_cross_site_);
}
{
SCOPED_TRACE("... for isolated_url popup");
TestPopupProcess(app, isolated_url_, false, false);
}
// For cross-site, the resulting process is only in the app process if we
// don't swap processes.
{
SCOPED_TRACE("... for cross_site popup");
TestPopupProcess(app, cross_site_url_, !should_swap_for_cross_site_,
!should_swap_for_cross_site_);
}
// If the iframes open popups that are same-origin with themselves, the popups
// should be in the same process as the respective iframes.
{
SCOPED_TRACE("... for same_dir iframe popup");
TestPopupProcess(same_dir, same_dir_url_, true, true);
}
{
SCOPED_TRACE("... for diff_dir iframe popup");
// TODO(creis): This should stay in the app's process, but that's not yet
// supported in modes that swap for cross-site navigations.
TestPopupProcess(diff_dir, diff_dir_url_, !should_swap_for_cross_site_,
!should_swap_for_cross_site_);
}
{
SCOPED_TRACE("... for same_site iframe popup");
// TODO(creis): This should stay in the app's process, but that's not yet
// supported in modes that swap for cross-site navigations.
TestPopupProcess(same_site, same_site_url_, !should_swap_for_cross_site_,
!should_swap_for_cross_site_);
}
{
SCOPED_TRACE("... for isolated_url iframe popup");
TestPopupProcess(isolated, isolated_url_, true, false);
}
{
SCOPED_TRACE("... for cross_site iframe popup");
TestPopupProcess(cross_site, cross_site_url_, true,
!should_swap_for_cross_site_);
}
}
// Tests that hosted app URLs loaded in iframes of non-app pages won't cause an
// OOPIF unless there is another reason to create it, but popups from outside
// the app will swap into the app.
// TODO(crbug.com/807471): Flaky on Windows 7.
#if defined(OS_WIN)
#define MAYBE_FromOutsideHostedApp DISABLED_FromOutsideHostedApp
#else
#define MAYBE_FromOutsideHostedApp FromOutsideHostedApp
#endif
IN_PROC_BROWSER_TEST_P(HostedAppProcessModelTest, MAYBE_FromOutsideHostedApp) {
// Set up and launch the hosted app.
GURL app_url =
embedded_test_server()->GetURL("app.site.com", "/frame_tree/simple.htm");
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(base::StringPrintf(kHostedAppProcessModelManifest,
app_url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
content::WebContents* web_contents =
app_browser_->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// Starting same-origin but outside the app, popups should swap to the app.
{
SCOPED_TRACE("... from diff_dir");
ui_test_utils::NavigateToURL(app_browser_, diff_dir_url_);
RenderFrameHost* main_frame = web_contents->GetMainFrame();
EXPECT_FALSE(main_frame->GetSiteInstance()->GetSiteURL().SchemeIs(
extensions::kExtensionScheme));
TestPopupProcess(main_frame, app_url, false, true);
// Subframes in the app should not swap.
RenderFrameHost* diff_dir_rfh =
TestSubframeProcess(main_frame, app_url, true, false);
// Popups from the subframe, though same-origin, should swap to the app.
// See https://crbug.com/89272.
TestPopupProcess(diff_dir_rfh, app_url, false, true);
}
// Starting same-site but outside the app, popups should swap to the app.
{
SCOPED_TRACE("... from same_site");
ui_test_utils::NavigateToURL(app_browser_, same_site_url_);
RenderFrameHost* main_frame = web_contents->GetMainFrame();
EXPECT_FALSE(main_frame->GetSiteInstance()->GetSiteURL().SchemeIs(
extensions::kExtensionScheme));
TestPopupProcess(main_frame, app_url, false, true);
// Subframes in the app should not swap.
RenderFrameHost* same_site_rfh =
TestSubframeProcess(main_frame, app_url, true, false);
// Popups from the subframe should swap to the app, as above.
TestPopupProcess(same_site_rfh, app_url, false, true);
}
// Starting on an isolated origin, popups should swap to the app.
{
SCOPED_TRACE("... from isolated_url");
ui_test_utils::NavigateToURL(app_browser_, isolated_url_);
RenderFrameHost* main_frame = web_contents->GetMainFrame();
EXPECT_FALSE(main_frame->GetSiteInstance()->GetSiteURL().SchemeIs(
extensions::kExtensionScheme));
TestPopupProcess(main_frame, app_url, false, true);
// Subframes in the app should swap process.
// TODO(creis): Perhaps this OOPIF should not be an app process?
RenderFrameHost* isolated_rfh =
TestSubframeProcess(main_frame, app_url, false, true);
// Popups from the subframe into the app should be in the app process.
TestPopupProcess(isolated_rfh, app_url, true, true);
}
// Starting cross-site, popups should swap to the app.
{
SCOPED_TRACE("... from cross_site");
ui_test_utils::NavigateToURL(app_browser_, cross_site_url_);
RenderFrameHost* main_frame = web_contents->GetMainFrame();
EXPECT_FALSE(main_frame->GetSiteInstance()->GetSiteURL().SchemeIs(
extensions::kExtensionScheme));
TestPopupProcess(main_frame, app_url, false, true);
// Subframes in the app should swap if the process model needs it.
// TODO(creis): Perhaps this OOPIF should not be an app process?
RenderFrameHost* cross_site_rfh =
TestSubframeProcess(main_frame, app_url, !should_swap_for_cross_site_,
should_swap_for_cross_site_);
// Popups from the subframe into the app should be in the app process.
TestPopupProcess(cross_site_rfh, app_url, should_swap_for_cross_site_,
true);
}
}
// Helper class that sets up two isolated origins, where one is a subdomain of
// the other: https://isolated.com and https://very.isolated.com.
class HostedAppIsolatedOriginTest : public HostedAppProcessModelTest {
public:
HostedAppIsolatedOriginTest() {}
~HostedAppIsolatedOriginTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
GURL isolated_url = embedded_test_server()->GetURL("isolated.com", "/");
GURL very_isolated_url =
embedded_test_server()->GetURL("very.isolated.com", "/");
std::string origin_list = base::StringPrintf(
"%s,%s", isolated_url.spec().c_str(), very_isolated_url.spec().c_str());
command_line->AppendSwitchASCII(switches::kIsolateOrigins, origin_list);
}
};
// Check that a hosted app that is contained entirely within an isolated.com
// isolated origin is allowed to load in a privileged app process. Also check
// that very.isolated.com, which does *not* match all URLs in the hosted app's
// extent, still ends up in its own non-app process. See
// https://crbug.com/799638.
IN_PROC_BROWSER_TEST_P(HostedAppIsolatedOriginTest,
NestedIsolatedOriginStaysOutsideApp) {
// Set up and launch the hosted app.
GURL app_url =
embedded_test_server()->GetURL("isolated.com", "/frame_tree/simple.htm");
constexpr const char kHostedAppWithinIsolatedOriginManifest[] =
R"( { "name": "Hosted App Within Isolated Origin Test",
"version": "1",
"manifest_version": 2,
"app": {
"launch": {
"web_url": "%s"
},
"urls": ["http://*.isolated.com/frame_tree"]
}
} )";
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(base::StringPrintf(
kHostedAppWithinIsolatedOriginManifest, app_url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
content::WebContents* web_contents =
app_browser_->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// Check that the app loaded properly. Even though its URL is from an
// isolated origin (isolated.com), it should go into an app process because
// the app's extent is contained entirely within isolated.com.
RenderFrameHost* app = web_contents->GetMainFrame();
EXPECT_EQ(extensions::kExtensionScheme,
app->GetSiteInstance()->GetSiteURL().scheme());
GURL app_site = content::SiteInstance::GetSiteForURL(
app_browser_->profile(), app->GetLastCommittedURL());
EXPECT_EQ(extensions::kExtensionScheme, app_site.scheme());
EXPECT_TRUE(process_map_->Contains(app->GetProcess()->GetID()));
// Add a same-site subframe on isolated.com. This should stay in app
// process.
GURL foo_isolated_url =
embedded_test_server()->GetURL("foo.isolated.com", "/title1.html");
TestSubframeProcess(app, foo_isolated_url, true /* expect_same_process */,
true /* expect_app_process */);
// Add a subframe on very.isolated.com. This should go into a separate,
// non-app process.
GURL very_isolated_url =
embedded_test_server()->GetURL("very.isolated.com", "/title2.html");
TestSubframeProcess(app, very_isolated_url, false /* expect_same_process */,
false /* expect_app_process */);
// Similarly, a popup for very.isolated.com should go into a separate,
// non-app process.
TestPopupProcess(app, very_isolated_url, false /* expect_same_process */,
false /* expect_app_process */);
// Navigating main frame from the app to very.isolated.com should also swap
// processes to a non-app process.
ui_test_utils::NavigateToURL(app_browser_, very_isolated_url);
EXPECT_FALSE(process_map_->Contains(
web_contents->GetMainFrame()->GetProcess()->GetID()));
// Navigating main frame back to the app URL should go into an app process.
ui_test_utils::NavigateToURL(app_browser_, app_url);
EXPECT_TRUE(process_map_->Contains(
web_contents->GetMainFrame()->GetProcess()->GetID()));
}
// Check that when a hosted app's extent contains multiple origins, one of
// which is an isolated origin, loading an app URL in that isolated origin does
// not go into the app process.
IN_PROC_BROWSER_TEST_P(HostedAppIsolatedOriginTest,
AppBroaderThanIsolatedOrigin) {
// Set up and launch the hosted app, with the launch URL being in an isolated
// origin.
GURL app_url =
embedded_test_server()->GetURL("isolated.com", "/frame_tree/simple.htm");
constexpr const char kHostedAppBroaderThanIsolatedOriginManifest[] =
R"( { "name": "Hosted App Within Isolated Origin Test",
"version": "1",
"manifest_version": 2,
"app": {
"launch": {
"web_url": "%s"
},
"urls": ["http://*.isolated.com/frame_tree", "*://unisolated.com/"]
}
} )";
extensions::TestExtensionDir test_app_dir;
test_app_dir.WriteManifest(base::StringPrintf(
kHostedAppBroaderThanIsolatedOriginManifest, app_url.spec().c_str()));
SetupApp(test_app_dir.UnpackedPath());
content::WebContents* web_contents =
app_browser_->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
// The app URL shouldn't have loaded in an app process, because that would
// allow isolated.com to share the app process with unisolated.com.
RenderFrameHost* app = web_contents->GetMainFrame();
EXPECT_FALSE(process_map_->Contains(app->GetProcess()->GetID()));
EXPECT_NE(extensions::kExtensionScheme,
app->GetSiteInstance()->GetSiteURL().scheme());
// In contrast, opening a popup or navigating to an app URL on unisolated.com
// is permitted to go into an app process.
GURL unisolated_app_url =
embedded_test_server()->GetURL("unisolated.com", "/title1.html");
TestPopupProcess(app, unisolated_app_url, false /* expect_same_process */,
true /* expect_app_process */);
ui_test_utils::NavigateToURL(app_browser_, unisolated_app_url);
EXPECT_TRUE(process_map_->Contains(
web_contents->GetMainFrame()->GetProcess()->GetID()));
}
INSTANTIATE_TEST_CASE_P(/* no prefix */,
HostedAppTest,
::testing::Combine(kAppTypeValues, ::testing::Bool()));
INSTANTIATE_TEST_CASE_P(/* no prefix */,
HostedAppPWAOnlyTest,
::testing::Values(std::tuple<AppType, bool>{
AppType::BOOKMARK_APP, true}));
INSTANTIATE_TEST_CASE_P(
/* no prefix */,
HostedAppProcessModelTest,
::testing::Combine(::testing::Values(AppType::HOSTED_APP),
::testing::Bool()));
INSTANTIATE_TEST_CASE_P(
/* no prefix */,
HostedAppIsolatedOriginTest,
::testing::Combine(::testing::Values(AppType::HOSTED_APP),
::testing::Bool()));