blob: ac3ece43abafb63a7f081938621951125f3d2ff6 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include "base/test/gmock_expected_support.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/extensions/browsertest_util.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/extensions/service_worker_apitest.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
#include "chrome/common/chrome_features.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.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 "content/public/test/web_transport_simple_test_server.h"
#include "extensions/browser/api/web_request/extension_web_request_event_router.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/guest_view/web_view/web_view_guest.h"
#include "extensions/test/extension_test_message_listener.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace controlled_frame {
namespace {
constexpr char kWebRequestOnBeforeRequestEventName[] =
"webViewInternal.onBeforeRequest";
constexpr char kWebRequestOnAuthRequiredEventName[] =
"webViewInternal.onAuthRequired";
constexpr char kEvalSuccessStr[] = "SUCCESS";
const extensions::MenuItem::Id CreateMenuItemId(
const extensions::MenuItem::ExtensionKey& extension_key,
const std::string& string_uid) {
extensions::MenuItem::Id id;
id.extension_key = extension_key;
id.string_uid = string_uid;
return id;
}
const content::EvalJsResult CreateContextMenuItem(
content::RenderFrameHost* app_frame,
const std::string& id,
const std::string& title) {
return content::EvalJs(app_frame, content::JsReplace(R"(
new Promise((resolve, reject) => {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.contextMenus || !frame.contextMenus.create) {
reject('FAIL: frame, frame.contextMenus, or ' +
'frame.contextMenus.create is undefined');
return;
}
frame.contextMenus.create(
{ title: $2, id: $1 },
() => { resolve('SUCCESS'); });
});
)",
id, title));
}
const content::EvalJsResult UpdateContextMenuItemTitle(
content::RenderFrameHost* app_frame,
const std::string& id,
const std::string& new_title) {
return content::EvalJs(app_frame, content::JsReplace(R"(
new Promise((resolve, reject) => {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.contextMenus || !frame.contextMenus.update) {
reject('FAIL: frame, frame.contextMenus, or ' +
'frame.contextMenus.update is undefined');
return;
}
frame.contextMenus.update(
/*id=*/$1,
{ title: $2 },
() => { resolve('SUCCESS'); });
});
)",
id, new_title));
}
const content::EvalJsResult RemoveContextMenuItem(
content::RenderFrameHost* app_frame,
const std::string& id) {
return content::EvalJs(app_frame, content::JsReplace(R"(
new Promise((resolve, reject) => {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.contextMenus || !frame.contextMenus.remove) {
reject('FAIL: frame, frame.contextMenus, or ' +
'frame.contextMenus.remove is undefined');
return;
}
frame.contextMenus.remove(
/*id=*/$1,
() => { resolve('SUCCESS'); });
});
)",
id));
}
const content::EvalJsResult RemoveAllContextMenuItems(
content::RenderFrameHost* app_frame) {
return content::EvalJs(app_frame, R"(
new Promise((resolve, reject) => {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.contextMenus || !frame.contextMenus.removeAll) {
reject('FAIL: frame, frame.contextMenus, or ' +
'frame.contextMenus.removeAll is undefined');
return;
}
frame.contextMenus.removeAll(() => { resolve('SUCCESS'); });
});
)");
}
const content::EvalJsResult SetBackgroundColorToWhite(
extensions::WebViewGuest* guest) {
return content::EvalJs(guest->GetGuestMainFrame(), R"(
(function() {
document.body.style.backgroundColor = 'white';
return 'SUCCESS';
})();
)");
}
const content::EvalJsResult ExecuteScriptRedBackgroundCode(
content::RenderFrameHost* app_frame) {
return content::EvalJs(app_frame, R"(
new Promise((resolve, reject) => {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.request) {
reject('FAIL');
return;
}
frame.executeScript(
{code: "document.body.style.backgroundColor = 'red';"},
() => { resolve('SUCCESS') });
});
)");
}
const content::EvalJsResult ExecuteScriptRedBackgroundFile(
content::RenderFrameHost* app_frame) {
return content::EvalJs(app_frame, R"(
new Promise((resolve, reject) => {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.request) {
reject('FAIL');
return;
}
frame.executeScript(
{file: "/execute_script.input.js"},
() => { resolve('SUCCESS') });
});
)");
}
const content::EvalJsResult VerifyBackgroundColorIsRed(
extensions::WebViewGuest* guest) {
return content::EvalJs(guest->GetGuestMainFrame(), R"(
(function() {
if (document.body.style.backgroundColor === 'red') {
return 'SUCCESS';
} else {
return 'FAIL';
}
})();
)");
}
[[nodiscard]] bool IsControlledFramePresent(
content::RenderFrameHost* app_frame) {
return ExecJs(app_frame, R"(
new Promise((resolve, reject) => {
const controlledframe = document.createElement('controlledframe');
if (('src' in controlledframe)) {
// Tag is defined.
resolve('SUCCESS');
} else {
reject('FAIL');
}
});
)");
}
// TODO(odejesush): Add tests for the rest of the Promise API methods.
const char* kControlledFramePromiseApiMethods[]{"back", "forward", "go"};
} // namespace
class ControlledFrameApiTest
: public web_app::IsolatedWebAppBrowserTestHarness {
public:
void SetUpOnMainThread() override {
web_app::IsolatedWebAppBrowserTestHarness::SetUpOnMainThread();
embedded_https_test_server().ServeFilesFromSourceDirectory(
GetChromeTestDataDir().AppendASCII("web_apps/simple_isolated_app"));
ASSERT_TRUE(embedded_https_test_server().Start());
}
web_app::IsolatedWebAppUrlInfo CreateAndInstallEmptyApp(
const web_app::ManifestBuilder& manifest_builder) {
app_ = web_app::IsolatedWebAppBuilder(manifest_builder).BuildBundle();
app_->TrustSigningKey();
base::expected<web_app::IsolatedWebAppUrlInfo, std::string> url_info =
app_->Install(profile());
CHECK(url_info.has_value()) << url_info.error();
return url_info.value();
}
[[nodiscard]] bool CreateControlledFrame(content::RenderFrameHost* frame,
const GURL& src) {
static std::string kCreateControlledFrame = R"(
new Promise((resolve, reject) => {
const controlledframe = document.createElement('controlledframe');
if (!('src' in controlledframe)) {
// Tag is undefined or generates a malformed response.
reject('FAIL');
return;
}
controlledframe.setAttribute('src', $1);
controlledframe.addEventListener('loadstop', resolve);
controlledframe.addEventListener('loadabort', reject);
document.body.appendChild(controlledframe);
});
)";
return ExecJs(frame, content::JsReplace(kCreateControlledFrame, src));
}
extensions::WebViewGuest* GetWebViewGuest(
content::RenderFrameHost* embedder_frame) {
extensions::WebViewGuest* web_view_guest = nullptr;
embedder_frame->ForEachRenderFrameHostWithAction(
[&web_view_guest](content::RenderFrameHost* rfh) {
if (auto* web_view =
extensions::WebViewGuest::FromRenderFrameHost(rfh)) {
web_view_guest = web_view;
return content::RenderFrameHost::FrameIterationAction::kStop;
}
return content::RenderFrameHost::FrameIterationAction::kContinue;
});
return web_view_guest;
}
void ExpectMenuItemWithIdAndTitle(
const extensions::MenuItem::ExtensionKey& extension_key,
const std::string& expected_id,
const std::string& expected_title) {
auto* menu_manager = extensions::MenuManager::Get(profile());
extensions::MenuItem* menu_item =
menu_manager->GetItemById(CreateMenuItemId(extension_key, expected_id));
ASSERT_TRUE(menu_item);
EXPECT_EQ(expected_title, menu_item->title());
}
private:
std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> app_;
};
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ContextMenusCreate) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
extensions::WebViewGuest* web_view_guest = GetWebViewGuest(app_frame);
auto* menu_manager = extensions::MenuManager::Get(profile());
const extensions::MenuItem::ExtensionKey extension_key(
/*extension_id=*/"", web_view_guest->owner_rfh()->GetProcess()->GetID(),
web_view_guest->owner_rfh()->GetRoutingID(),
web_view_guest->view_instance_id());
EXPECT_EQ(0u, menu_manager->MenuItemsSize(extension_key));
static constexpr std::string kItem1ID = "1";
static constexpr std::string kItem1Title = "Test";
EXPECT_EQ(kEvalSuccessStr,
CreateContextMenuItem(app_frame, kItem1ID, kItem1Title));
ASSERT_EQ(1u, menu_manager->MenuItemsSize(extension_key));
ExpectMenuItemWithIdAndTitle(extension_key, kItem1ID, kItem1Title);
static constexpr std::string kItem2ID = "2";
static constexpr std::string kItem2Title = "Test2";
EXPECT_EQ(kEvalSuccessStr,
CreateContextMenuItem(app_frame, kItem2ID, kItem2Title));
ASSERT_EQ(2u, menu_manager->MenuItemsSize(extension_key));
ExpectMenuItemWithIdAndTitle(extension_key, kItem2ID, kItem2Title);
static constexpr std::string kItem3ID = "3";
static constexpr std::string kItem3Title = "Test3";
EXPECT_EQ(kEvalSuccessStr,
CreateContextMenuItem(app_frame, kItem3ID, kItem3Title));
ASSERT_EQ(3u, menu_manager->MenuItemsSize(extension_key));
ExpectMenuItemWithIdAndTitle(extension_key, kItem3ID, kItem3Title);
}
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ContextMenusUpdate) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
extensions::WebViewGuest* web_view_guest = GetWebViewGuest(app_frame);
auto* menu_manager = extensions::MenuManager::Get(profile());
static constexpr std::string kItem1ID = "1";
static constexpr std::string kItem1Title = "Test";
EXPECT_EQ(kEvalSuccessStr,
CreateContextMenuItem(app_frame, kItem1ID, kItem1Title));
const extensions::MenuItem::ExtensionKey extension_key(
/*extension_id=*/"", web_view_guest->owner_rfh()->GetProcess()->GetID(),
web_view_guest->owner_rfh()->GetRoutingID(),
web_view_guest->view_instance_id());
ASSERT_EQ(1u, menu_manager->MenuItemsSize(extension_key));
ExpectMenuItemWithIdAndTitle(extension_key, kItem1ID, kItem1Title);
static constexpr std::string kItem1NewTitle = "Test1";
EXPECT_EQ(kEvalSuccessStr,
UpdateContextMenuItemTitle(app_frame, kItem1ID, kItem1NewTitle));
ASSERT_EQ(1u, menu_manager->MenuItemsSize(extension_key));
ExpectMenuItemWithIdAndTitle(extension_key, kItem1ID, kItem1NewTitle);
}
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ContextMenusRemove) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
extensions::WebViewGuest* web_view_guest = GetWebViewGuest(app_frame);
auto* menu_manager = extensions::MenuManager::Get(profile());
static constexpr std::string kItem1ID = "1";
static constexpr std::string kItem1Title = "Test1";
EXPECT_EQ(kEvalSuccessStr,
CreateContextMenuItem(app_frame, kItem1ID, kItem1Title));
EXPECT_EQ(kEvalSuccessStr, CreateContextMenuItem(app_frame, /*id=*/"2",
/*title=*/"Test2"));
EXPECT_EQ(kEvalSuccessStr, RemoveContextMenuItem(app_frame, kItem1ID));
const extensions::MenuItem::ExtensionKey extension_key(
/*extension_id=*/"", web_view_guest->owner_rfh()->GetProcess()->GetID(),
web_view_guest->owner_rfh()->GetRoutingID(),
web_view_guest->view_instance_id());
ASSERT_EQ(1u, menu_manager->MenuItemsSize(extension_key));
extensions::MenuItem* deleted_item =
menu_manager->GetItemById(CreateMenuItemId(extension_key, kItem1ID));
EXPECT_FALSE(deleted_item);
}
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ContextMenusRemoveAll) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
extensions::WebViewGuest* web_view_guest = GetWebViewGuest(app_frame);
auto* menu_manager = extensions::MenuManager::Get(profile());
EXPECT_EQ(kEvalSuccessStr, CreateContextMenuItem(app_frame, /*id=*/"1",
/*title=*/"Test1"));
EXPECT_EQ(kEvalSuccessStr, CreateContextMenuItem(app_frame, /*id=*/"2",
/*title=*/"Test2"));
EXPECT_EQ(kEvalSuccessStr, RemoveAllContextMenuItems(app_frame));
const extensions::MenuItem::ExtensionKey extension_key(
/*extension_id=*/"", web_view_guest->owner_rfh()->GetProcess()->GetID(),
web_view_guest->owner_rfh()->GetRoutingID(),
web_view_guest->view_instance_id());
ASSERT_EQ(0u, menu_manager->MenuItemsSize(extension_key));
}
// This test checks if the Controlled Frame is able to intercept URL navigation
// requests.
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, URLLoaderIsProxied) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
const GURL& kOriginalControlledFrameUrl =
embedded_https_test_server().GetURL("/index.html");
ASSERT_TRUE(CreateControlledFrame(app_frame, kOriginalControlledFrameUrl));
auto* web_request_event_router =
extensions::WebRequestEventRouter::Get(profile());
EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnBeforeRequestEventName));
const std::string& kServerHostPort =
embedded_https_test_server().host_port_pair().ToString();
EXPECT_EQ("SUCCESS",
content::EvalJs(app_frame, content::JsReplace(R"(
(function() {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.request) {
return 'FAIL: frame or frame.request is undefined';
}
frame.request.onBeforeRequest.addListener(() => {
return { cancel: true };
}, { urls: ['https://*/controlled_frame_cancel.html'] }, ['blocking']);
frame.request.onBeforeRequest.addListener(() => {
return { cancel: false };
}, { urls: ['https://*/controlled_frame_success.html'] }, ['blocking']);
frame.request.onBeforeRequest.addListener(() => {
return {
redirectUrl: 'https://' + $1 + '/controlled_frame_redirect_target.html'
};
}, { urls: ['https://*/controlled_frame_redirect.html'] }, ['blocking']);
return 'SUCCESS';
})();
)",
kServerHostPort)));
EXPECT_EQ(3u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnBeforeRequestEventName));
auto* web_view_guest = GetWebViewGuest(app_frame);
content::WebContents* guest_web_contents = web_view_guest->web_contents();
// Check that navigations can be cancelled.
{
content::TestNavigationObserver navigation_observer(
guest_web_contents, net::Error::ERR_BLOCKED_BY_CLIENT,
content::MessageLoopRunner::QuitMode::IMMEDIATE,
/*ignore_uncommitted_navigations=*/false);
web_view_guest->NavigateGuest(embedded_https_test_server()
.GetURL("/controlled_frame_cancel.html")
.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(net::Error::ERR_BLOCKED_BY_CLIENT,
navigation_observer.last_net_error_code());
EXPECT_EQ(kOriginalControlledFrameUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
}
// Check that navigations can be redirected.
{
content::TestNavigationObserver navigation_observer(
guest_web_contents, /*expected_number_of_navigations=*/1u);
web_view_guest->NavigateGuest(embedded_https_test_server()
.GetURL("/controlled_frame_redirect.html")
.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(embedded_https_test_server().GetURL(
"/controlled_frame_redirect_target.html"),
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
}
// Check that navigations can succeed.
{
content::TestNavigationObserver navigation_observer(
guest_web_contents, /*expected_number_of_navigations=*/1u);
const GURL& kControlledFrameSuccessUrl =
embedded_https_test_server().GetURL("/controlled_frame_success.html");
web_view_guest->NavigateGuest(kControlledFrameSuccessUrl.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(kControlledFrameSuccessUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
}
}
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, AuthRequestIsProxied) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
auto* web_request_event_router =
extensions::WebRequestEventRouter::Get(profile());
EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnAuthRequiredEventName));
EXPECT_EQ(true, content::EvalJs(app_frame, R"(
(function() {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.request) {
return false;
}
const expectedUsername = 'test';
const expectedPassword = 'pass';
frame.request.onAuthRequired.addListener(() => {
return {
authCredentials: {
username: expectedUsername,
password: expectedPassword
}
};
}, { urls: [`https://*/auth-basic*`] }, ['blocking']);
return true;
})();
)"));
EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnAuthRequiredEventName));
auto* web_view_guest = GetWebViewGuest(app_frame);
content::WebContents* guest_web_contents = web_view_guest->web_contents();
// Check that the injecting the credentials through WebRequest produces a
// successful navigation.
{
content::TestNavigationObserver navigation_observer(
guest_web_contents,
/*expected_number_of_navigations=*/1u);
const GURL& kAuthBasicUrl =
embedded_https_test_server().GetURL("/auth-basic?password=pass");
web_view_guest->NavigateGuest(kAuthBasicUrl.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(kAuthBasicUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
}
// Check that the injecting the wrong credentials through WebRequest produces
// an error.
{
content::TestNavigationObserver navigation_observer(
guest_web_contents,
/*expected_number_of_navigations=*/1u);
const GURL& kAuthBasicUrl =
embedded_https_test_server().GetURL("/auth-basic?password=badpass");
web_view_guest->NavigateGuest(kAuthBasicUrl.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(kAuthBasicUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
// The auth request fails but keeps retrying until this error is produced.
// TODO(https://crbug.com/1502580): The error produced here should be
// authentication related.
EXPECT_EQ(net::Error::ERR_TOO_MANY_RETRIES,
navigation_observer.last_net_error_code());
EXPECT_FALSE(navigation_observer.last_navigation_succeeded());
}
}
IN_PROC_BROWSER_TEST_F(ControlledFrameApiTest, ExecuteScript) {
std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> app =
web_app::IsolatedWebAppBuilder(web_app::ManifestBuilder())
.AddHtml("/execute_script.input.js",
"document.body.style.backgroundColor = 'red';")
.BuildBundle();
app->TrustSigningKey();
ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
app->Install(profile()));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
auto* web_view_guest = GetWebViewGuest(app_frame);
// Verify that executeScript() using JS code can change the background color.
EXPECT_EQ(kEvalSuccessStr, SetBackgroundColorToWhite(web_view_guest));
EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundCode(app_frame));
EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));
// Verify that executeScript() using a JS file changes the background color.
EXPECT_EQ(kEvalSuccessStr, SetBackgroundColorToWhite(web_view_guest));
EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundFile(app_frame));
EXPECT_EQ(kEvalSuccessStr, VerifyBackgroundColorIsRed(web_view_guest));
}
class ControlledFrameWebSocketApiTest : public ControlledFrameApiTest {
public:
void SetUpOnMainThread() override {
ControlledFrameApiTest::SetUpOnMainThread();
websocket_test_server_ = std::make_unique<net::SpawnedTestServer>(
net::SpawnedTestServer::TYPE_WS, net::GetWebSocketTestDataDirectory());
ASSERT_TRUE(websocket_test_server_->Start());
}
net::SpawnedTestServer* websocket_test_server() {
return websocket_test_server_.get();
}
GURL GetWebSocketUrl(const std::string& path) {
GURL::Replacements replacements;
replacements.SetSchemeStr("ws");
return websocket_test_server_->GetURL(path).ReplaceComponents(replacements);
}
private:
std::unique_ptr<net::SpawnedTestServer> websocket_test_server_;
};
IN_PROC_BROWSER_TEST_F(ControlledFrameWebSocketApiTest, WebSocketIsProxied) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
const GURL& kOriginalControlledFrameUrl =
embedded_https_test_server().GetURL("/index.html");
ASSERT_TRUE(CreateControlledFrame(app_frame, kOriginalControlledFrameUrl));
auto* web_request_event_router =
extensions::WebRequestEventRouter::Get(profile());
EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnBeforeRequestEventName));
// Use Web Sockets before installing a WebRequest event listener to verify
// that it works inside of the Controlled Frame.
auto* web_view_guest = GetWebViewGuest(app_frame);
content::WebContents* guest_web_contents = web_view_guest->web_contents();
GURL::Replacements http_scheme_replacement;
http_scheme_replacement.SetSchemeStr("http");
const GURL& kWebSocketConnectCheckUrl =
websocket_test_server()
->GetURL("/connect_check.html")
.ReplaceComponents(http_scheme_replacement);
{
content::TitleWatcher title_watcher(guest_web_contents, u"PASS");
title_watcher.AlsoWaitForTitle(u"FAIL");
content::TestNavigationObserver navigation_observer(
guest_web_contents,
/*expected_number_of_navigations=*/1u);
web_view_guest->NavigateGuest(kWebSocketConnectCheckUrl.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(kWebSocketConnectCheckUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
EXPECT_EQ(u"PASS", title_watcher.WaitAndGetTitle());
}
{
content::TestNavigationObserver navigation_observer(
guest_web_contents,
/*expected_number_of_navigations=*/1u);
web_view_guest->NavigateGuest(kOriginalControlledFrameUrl.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(kOriginalControlledFrameUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
}
// Set up a WebRequest event listener that cancels any requests to the Web
// Socket server.
EXPECT_EQ(true, content::EvalJs(app_frame,
R"(
(function() {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.request) {
return false;
}
frame.request.onBeforeRequest.addListener(() => {
return { cancel: true };
}, { urls: ['ws://*/*'] }, ['blocking']);
return true;
})();
)"));
EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnBeforeRequestEventName));
{
content::TitleWatcher title_watcher(guest_web_contents, u"PASS");
title_watcher.AlsoWaitForTitle(u"FAIL");
content::TestNavigationObserver navigation_observer(
guest_web_contents,
/*expected_number_of_navigations=*/1u);
web_view_guest->NavigateGuest(kWebSocketConnectCheckUrl.spec(),
/*navigation_handle_callback=*/{},
/*force_navigation=*/false);
navigation_observer.WaitForNavigationFinished();
EXPECT_EQ(kWebSocketConnectCheckUrl,
web_view_guest->GetGuestMainFrame()->GetLastCommittedURL());
EXPECT_TRUE(navigation_observer.last_navigation_succeeded());
EXPECT_EQ(u"FAIL", title_watcher.WaitAndGetTitle());
}
}
class ControlledFrameWebTransportApiTest : public ControlledFrameApiTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
ControlledFrameApiTest::SetUpCommandLine(command_line);
webtransport_server_.SetUpCommandLine(command_line);
webtransport_server_.Start();
}
content::WebTransportSimpleTestServer& webtransport_server() {
return webtransport_server_;
}
protected:
content::WebTransportSimpleTestServer webtransport_server_;
};
IN_PROC_BROWSER_TEST_F(ControlledFrameWebTransportApiTest,
WebTransportIsProxied) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
auto* web_request_event_router =
extensions::WebRequestEventRouter::Get(profile());
EXPECT_EQ(0u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnBeforeRequestEventName));
// Use WebTransport before installing a WebRequest event listener to verify
// that it works inside of the Controlled Frame.
auto* web_view_guest = GetWebViewGuest(app_frame);
EXPECT_EQ(true, content::EvalJs(
web_view_guest->GetGuestMainFrame(),
content::JsReplace(
R"(
(async function() {
const url = 'https://localhost:' + $1 + '/echo_test';
try {
const transport = new WebTransport(url);
await transport.ready;
} catch (e) {
console.log(url + ': ' + e.name + ': ' + e.message);
return false;
}
return true;
})();
)",
webtransport_server().server_address().port())));
// Set up a WebRequest event listener that cancels any requests to the
// WebTransport server.
EXPECT_EQ(true, content::EvalJs(app_frame,
R"(
let cancelRequest = false;
(function() {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame || !frame.request) {
return false;
}
const onBeforeRequestHandler =
frame.request.onBeforeRequest.addListener(() => {
return { cancel: true };
}, { urls: ['https://localhost/*'] }, ['blocking']);
return true;
})();
)"));
EXPECT_EQ(1u, web_request_event_router->GetListenerCountForTesting(
profile(), kWebRequestOnBeforeRequestEventName));
EXPECT_EQ(false, content::EvalJs(
web_view_guest->GetGuestMainFrame(),
content::JsReplace(
R"(
(async function() {
cancelRequest = true;
const url = 'https://localhost:' + $1 + '/echo_test';
try {
const transport = new WebTransport(url);
await transport.ready;
} catch (e) {
console.log(url + ': ' + e.name + ': ' + e.message);
return false;
}
return true;
})();
)",
webtransport_server().server_address().port())));
}
namespace {
constexpr char kPermissionAllowedHost[] = "permission-allowed.com";
constexpr char kPermissionDisallowedHost[] = "permission-disllowed.com";
} // namespace
class ControlledFramePermissionsPolicyTest : public ControlledFrameApiTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
ControlledFrameApiTest::SetUpCommandLine(command_line);
command_line->AppendArg("--use-fake-device-for-media-stream");
}
void SetUpPermissionRequestEventListener(content::RenderFrameHost* app_frame,
bool allow_permission) {
const std::string& handle_request_str = allow_permission ? "allow" : "deny";
EXPECT_EQ("SUCCESS", content::EvalJs(app_frame, content::JsReplace(
R"(
(function() {
const frame = document.getElementsByTagName('controlledframe')[0];
if (!frame) {
return 'FAIL: Could not find a controlledframe element.';
}
frame.addEventListener('permissionrequest', (e) => {
e.request[$1]();
});
return 'SUCCESS'
})();
)",
handle_request_str)));
}
void RequestMediaPermissionFromControlledFrame(
content::RenderFrameHost* app_frame,
bool request_audio,
bool request_video,
bool expect_audio_permission_allowed,
bool expect_video_permission_allowed) {
extensions::WebViewGuest* web_view_guest = GetWebViewGuest(app_frame);
EXPECT_EQ("SUCCESS", content::EvalJs(web_view_guest->GetGuestMainFrame(),
content::JsReplace(
R"(
(async function() {
const constraints = { audio: $1, video: $2 };
const expectAudioPermissionAllowed = $3;
const expectVideoPermissionAllowed = $4;
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const checkPermissionType =
function(type, tracks, expectPermissionAllowed) {
const hasTracks = tracks.length;
if (expectPermissionAllowed != hasTracks) {
const expectedPermissionStr =
expectPermissionAllowed ? 'has' : 'does not have';
const hasTrackStr = hasTracks ? 'has' : 'does not have';
return 'FAIL: getUserMedia() ' + expectedPermissionStr + ' ' +
type + ' stream permission, but ' + hasTrackStr + ' ' +
type + ' tracks';
}
return 'SUCCESS';
}
let audioPermissionCheckResult = checkPermissionType(
'audio', stream.getAudioTracks(), expectAudioPermissionAllowed);
if (audioPermissionCheckResult != 'SUCCESS') {
return audioPermissionCheckResult;
}
let videoPermissionCheckResult = checkPermissionType(
'video', stream.getVideoTracks(), expectVideoPermissionAllowed);
if (videoPermissionCheckResult != 'SUCCESS') {
return videoPermissionCheckResult;
}
return 'SUCCESS';
} catch (err) {
if (!expectAudioPermissionAllowed && !expectVideoPermissionAllowed) {
return 'SUCCESS';
}
return 'FAIL: ' + err.name + ': ' + err.message;
}
})();
)",
request_audio, request_video,
expect_audio_permission_allowed,
expect_video_permission_allowed)));
}
};
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
CameraPermissionAllowed) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/false,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/true);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
OnlyCameraPermissionAllowed) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/true);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
CameraPermissionDenied) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/false);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/false,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
CameraPermissionDisallowed) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionDisallowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/false,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
MicrophonePermissionAllowed) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/false,
/*expect_audio_permission_allowed=*/true,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
OnlyMicrophonePermissionAllowed) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/true,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
MicrophonePermissionDenied) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/false);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/false,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
MicrophonePermissionDisallowed) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder().AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionDisallowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/false,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
CameraAndMicrophonePermissionAllowed) {
web_app::IsolatedWebAppUrlInfo url_info = CreateAndInstallEmptyApp(
web_app::ManifestBuilder()
.AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)})
.AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone,
/*self=*/true,
{embedded_https_test_server().GetOrigin(
kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/true,
/*expect_video_permission_allowed=*/true);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
CameraAndMicrophonePermissionDenied) {
web_app::IsolatedWebAppUrlInfo url_info = CreateAndInstallEmptyApp(
web_app::ManifestBuilder()
.AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)})
.AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone,
/*self=*/true,
{embedded_https_test_server().GetOrigin(
kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionAllowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/false);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/false);
}
IN_PROC_BROWSER_TEST_F(ControlledFramePermissionsPolicyTest,
CameraAndMicrophonePermissionDisallowed) {
web_app::IsolatedWebAppUrlInfo url_info = CreateAndInstallEmptyApp(
web_app::ManifestBuilder()
.AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kCamera, /*self=*/true,
{embedded_https_test_server().GetOrigin(kPermissionAllowedHost)})
.AddPermissionsPolicy(
blink::mojom::PermissionsPolicyFeature::kMicrophone,
/*self=*/true,
{embedded_https_test_server().GetOrigin(
kPermissionAllowedHost)}));
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL(kPermissionDisallowedHost,
"/index.html")));
SetUpPermissionRequestEventListener(app_frame, /*allow_permission=*/true);
RequestMediaPermissionFromControlledFrame(
app_frame,
/*request_audio=*/true,
/*request_video=*/true,
/*expect_audio_permission_allowed=*/false,
/*expect_video_permission_allowed=*/false);
}
class ControlledFramePromiseApiTest
: public ControlledFrameApiTest,
public testing::WithParamInterface<const char*> {};
IN_PROC_BROWSER_TEST_P(ControlledFramePromiseApiTest, PromiseAPIs) {
std::unique_ptr<web_app::ScopedProxyIsolatedWebApp> app =
web_app::IsolatedWebAppBuilder(web_app::ManifestBuilder())
.AddFolderFromDisk("/", "web_apps/simple_isolated_app")
.BuildAndStartProxyServer();
ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
app->Install(profile()));
content::RenderFrameHost* app_frame =
OpenApp(url_info.app_id(), "/controlled_frame_api_test.html");
ASSERT_TRUE(CreateControlledFrame(
app_frame, app->proxy_server().GetURL("/controlled_frame.html")));
EXPECT_EQ("SUCCESS",
content::EvalJs(app_frame, content::JsReplace(R"(
const frame = document.getElementsByTagName('controlledframe')[0];
testAPI(frame, $1);
)",
GetParam())));
}
INSTANTIATE_TEST_SUITE_P(PromiseAPIs,
ControlledFramePromiseApiTest,
testing::ValuesIn(kControlledFramePromiseApiMethods));
class ControlledFrameServiceWorkerTest
: public extensions::ServiceWorkerBasedBackgroundTest {
public:
ControlledFrameServiceWorkerTest(const ControlledFrameServiceWorkerTest&) =
delete;
ControlledFrameServiceWorkerTest& operator=(
const ControlledFrameServiceWorkerTest&) = delete;
void SetUpOnMainThread() override {
extensions::ServiceWorkerBasedBackgroundTest::SetUpOnMainThread();
embedded_https_test_server().ServeFilesFromSourceDirectory(
GetChromeTestDataDir());
ASSERT_TRUE(embedded_https_test_server().Start());
}
protected:
ControlledFrameServiceWorkerTest() {
feature_list.InitWithFeatures(
/*enabled_features=*/{features::kIsolatedWebApps,
features::kIsolatedWebAppDevMode},
/*disabled_features=*/{});
}
~ControlledFrameServiceWorkerTest() override = default;
base::test::ScopedFeatureList feature_list;
};
// This test ensures that loading an extension Service Worker does not cause a
// crash, and that Controlled Frame is not allowed in the Service Worker
// context. For more details, see https://crbug.com/1462384.
// This test is the same as ServiceWorkerBasedBackgroundTest.Basic.
IN_PROC_BROWSER_TEST_F(ControlledFrameServiceWorkerTest, PRE_Basic) {
ExtensionTestMessageListener newtab_listener("CREATED");
newtab_listener.set_failure_message("CREATE_FAILED");
ExtensionTestMessageListener worker_listener("WORKER_RUNNING");
worker_listener.set_failure_message("NON_WORKER_SCOPE");
const extensions::Extension* extension =
LoadExtension(test_data_dir_.AppendASCII(
"service_worker/worker_based_background/basic"));
ASSERT_TRUE(extension);
const extensions::ExtensionId extension_id = extension->id();
EXPECT_TRUE(worker_listener.WaitUntilSatisfied());
const GURL url =
embedded_https_test_server().GetURL("/extensions/test_file.html");
content::WebContents* new_web_contents =
extensions::browsertest_util::AddTab(browser(), url);
EXPECT_TRUE(new_web_contents);
EXPECT_TRUE(newtab_listener.WaitUntilSatisfied());
// Service Worker extension does not have ExtensionHost.
EXPECT_FALSE(process_manager()->GetBackgroundHostForExtension(extension_id));
}
// After browser restarts, this test step ensures that opening a tab fires
// tabs.onCreated event listener to the extension without explicitly loading the
// extension. This is because the extension registered a listener before browser
// restarted in PRE_Basic.
IN_PROC_BROWSER_TEST_F(ControlledFrameServiceWorkerTest, Basic) {
ExtensionTestMessageListener newtab_listener("CREATED");
newtab_listener.set_failure_message("CREATE_FAILED");
const GURL url =
embedded_https_test_server().GetURL("/extensions/test_file.html");
content::WebContents* new_web_contents =
extensions::browsertest_util::AddTab(browser(), url);
EXPECT_TRUE(new_web_contents);
EXPECT_TRUE(newtab_listener.WaitUntilSatisfied());
}
class ControlledFrameAvailableChannelTest
: public ControlledFrameApiTest,
public testing::WithParamInterface<version_info::Channel> {
protected:
ControlledFrameAvailableChannelTest() : channel_(GetParam()) {}
private:
extensions::ScopedCurrentChannel channel_;
};
INSTANTIATE_TEST_SUITE_P(ControlledFrameAvailableChannels,
ControlledFrameAvailableChannelTest,
testing::Values(version_info::Channel::STABLE,
version_info::Channel::BETA,
version_info::Channel::DEV,
version_info::Channel::CANARY,
version_info::Channel::DEFAULT));
IN_PROC_BROWSER_TEST_P(ControlledFrameAvailableChannelTest, Test) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_TRUE(CreateControlledFrame(
app_frame, embedded_https_test_server().GetURL("/index.html")));
// Test if Controlled Frame is available.
EXPECT_EQ(kEvalSuccessStr, ExecuteScriptRedBackgroundFile(app_frame));
}
class ControlledFrameNotAvailableChannelTest
: public ControlledFrameApiTest,
public testing::WithParamInterface<version_info::Channel> {
protected:
ControlledFrameNotAvailableChannelTest() : channel_(GetParam()) {}
private:
extensions::ScopedCurrentChannel channel_;
};
INSTANTIATE_TEST_SUITE_P(ControlledFrameNotAvailableChannels,
ControlledFrameNotAvailableChannelTest,
testing::Values(version_info::Channel::STABLE,
version_info::Channel::BETA,
version_info::Channel::DEV,
version_info::Channel::CANARY,
version_info::Channel::DEFAULT));
IN_PROC_BROWSER_TEST_P(ControlledFrameNotAvailableChannelTest, Test) {
// Test if Controlled Frame is not available.
const GURL start_url("https://app.site.test/example/index");
InstallPWA(start_url);
content::WebContents* app_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_FALSE(IsControlledFramePresent(app_contents->GetPrimaryMainFrame()));
}
class ControlledFrameDisabledTest : public ControlledFrameApiTest {
protected:
ControlledFrameDisabledTest() {
feature_list.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{features::kControlledFrame});
}
~ControlledFrameDisabledTest() override = default;
private:
base::test::ScopedFeatureList feature_list;
};
IN_PROC_BROWSER_TEST_F(ControlledFrameDisabledTest, MissingFeature) {
web_app::IsolatedWebAppUrlInfo url_info =
CreateAndInstallEmptyApp(web_app::ManifestBuilder());
content::RenderFrameHost* app_frame = OpenApp(url_info.app_id());
ASSERT_FALSE(IsControlledFramePresent(app_frame));
}
} // namespace controlled_frame