blob: 31129e734b7745f810e8cbb9a5e9cfa3e0360ce6 [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 "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/ui_test_utils.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 "extensions/browser/extension_registrar.h"
#include "extensions/browser/renderer_startup_helper.h"
#include "extensions/browser/script_executor.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/common/mojom/match_origin_as_fallback.mojom.h"
#include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace {
constexpr char kCheckIfEvalAllowedScriptSource[] =
R"(var result;
try {
eval('result = "allowed eval"');
} catch (e) {
result = 'disallowed eval';
}
result;)";
constexpr char kGetMessagingProperties[] =
R"(let messagingProperties = [
'sendMessage', 'onMessage', 'connect', 'onConnect'
];
let runtimeProperties =
chrome && chrome.runtime
? Object.keys(chrome.runtime)
: [];
messagingProperties =
messagingProperties.filter((prop) => {
return runtimeProperties.includes(prop);
});
messagingProperties;)";
} // namespace
class UserScriptWorldBrowserTest : public ExtensionApiTest {
public:
UserScriptWorldBrowserTest() = default;
UserScriptWorldBrowserTest(const UserScriptWorldBrowserTest&) = delete;
UserScriptWorldBrowserTest& operator=(const UserScriptWorldBrowserTest&) =
delete;
~UserScriptWorldBrowserTest() override = default;
// ExtensionApiTest:
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
// Executes the given `script` in a user script world associated with the
// `extension` and the given `world_id`, returning the script result. This
// expects the script to succeed (i.e., not throw an error) and runs the
// script in the primary main frame of the active WebContents.
base::Value ExecuteScriptInUserScriptWorld(
const std::string& script,
const Extension& extension,
const std::optional<std::string>& world_id = std::nullopt) {
SCOPED_TRACE(script.c_str());
ScriptExecutor script_executor(GetActiveWebContents());
base::RunLoop run_loop;
std::vector<ScriptExecutor::FrameResult> script_results;
auto on_complete =
[&run_loop, &script_results](
std::vector<ScriptExecutor::FrameResult> frame_results) {
script_results = std::move(frame_results);
run_loop.Quit();
};
std::vector<mojom::JSSourcePtr> sources;
sources.push_back(mojom::JSSource::New(script, GURL()));
script_executor.ExecuteScript(
mojom::HostID(mojom::HostID::HostType::kExtensions, extension.id()),
mojom::CodeInjection::NewJs(mojom::JSInjection::New(
std::move(sources), mojom::ExecutionWorld::kUserScript, world_id,
blink::mojom::WantResultOption::kWantResult,
blink::mojom::UserActivationOption::kDoNotActivate,
blink::mojom::PromiseResultOption::kAwait)),
ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
mojom::MatchOriginAsFallbackBehavior::kNever,
mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
GURL() /* webview_src */, base::BindLambdaForTesting(on_complete));
run_loop.Run();
if (script_results.size() != 1) {
ADD_FAILURE() << "Incorrect script execution result count: "
<< script_results.size();
return base::Value();
}
ScriptExecutor::FrameResult& frame_result = script_results[0];
if (!frame_result.error.empty()) {
ADD_FAILURE() << "Unexpected script error: " << frame_result.error;
return base::Value();
}
if (frame_result.value.is_none()) {
ADD_FAILURE() << "Null return value";
return base::Value();
}
return std::move(frame_result.value);
}
// Navigates the active web contents to `url`, waiting for the navigation to
// (successfully) complete.
void NavigateToURL(const GURL& url) {
content::TestNavigationObserver nav_observer(GetActiveWebContents());
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
nav_observer.Wait();
EXPECT_TRUE(nav_observer.last_navigation_succeeded());
}
// Loads and returns an extension with the given `host_permission`.
const Extension* LoadExtensionWithHostPermission(
const std::string& host_permission) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("extension")
.AddHostPermission(host_permission)
.Build();
extension_registrar()->AddExtension(extension);
EXPECT_TRUE(
extension_registry()->enabled_extensions().GetByID(extension->id()));
return extension.get();
}
// Sets the user script world properties in the renderer(s).
void SetDefaultUserScriptWorldProperties(const Extension& extension,
std::optional<std::string> csp,
bool enable_messaging) {
SetUserScriptWorldProperties(extension, /*world_id=*/std::nullopt,
std::move(csp), enable_messaging);
}
void SetUserScriptWorldProperties(const Extension& extension,
std::optional<std::string> world_id,
std::optional<std::string> csp,
bool enable_messaging) {
RendererStartupHelperFactory::GetForBrowserContext(profile())
->SetUserScriptWorldProperties(
extension,
mojom::UserScriptWorldInfo::New(extension.id(), std::move(world_id),
std::move(csp), enable_messaging));
}
// Clears associated user script world properties in the renderer(s).
void ClearUserScriptWorldProperties(const Extension& extension,
std::optional<std::string> world_id) {
RendererStartupHelperFactory::GetForBrowserContext(profile())
->ClearUserScriptWorldProperties(extension, std::move(world_id));
}
};
// Tests that a user script world for an extension executed in an
// isolated world and has limited access to extension APIs.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
LimitedAPIsAreAvailableInUserScriptWorlds) {
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
// Enable messaging to get the full suite of possible APIs exposed to
// user script worlds.
SetDefaultUserScriptWorldProperties(*extension, std::nullopt,
/*enable_messaging=*/true);
GURL example_com =
embedded_test_server()->GetURL("example.com", "/simple.html");
NavigateToURL(example_com);
content::WebContents* web_contents = GetActiveWebContents();
content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
// Set a flag in the main world of the page. This will allow us to verify
// the new script is running in an isolated world.
constexpr char kSetFlagScript[] = "window.mainWorldFlag = 'executionFlag';";
// NOTE: We *need* this to happen in the main world for the test.
EXPECT_TRUE(content::ExecJs(main_frame, kSetFlagScript));
// Inject a script into a user script world. The script will return the
// values of both the main world flag (set above) and all properties exposed
// on `chrome.runtime`.
static constexpr char kScriptSource[] =
R"(let result = {};
result.mainWorldFlag = window.mainWorldFlag || '<no flag>';
result.chromeKeys =
chrome ? Object.keys(chrome).sort() : '<no chrome>';
result.runtimeKeys = chrome && chrome.runtime ?
Object.keys(chrome.runtime).sort() : '<no runtime>';
result;)";
base::Value script_result =
ExecuteScriptInUserScriptWorld(kScriptSource, *extension);
// Verify the expected results. Since the user script world is less
// privileged, it shouldn't have access to most runtime APIs (such as reload,
// onStartup, getManifest, etc).
static constexpr char kExpectedJson[] =
R"({
"mainWorldFlag": "<no flag>",
"chromeKeys": ["csi", "loadTimes", "runtime", "test"],
"runtimeKeys": ["ContextType", "OnInstalledReason",
"OnRestartRequiredReason", "PlatformArch",
"PlatformNaclArch", "PlatformOs",
"RequestUpdateCheckStatus",
"connect", "dynamicId", "id", "onConnect",
"onMessage", "sendMessage"]
})";
EXPECT_THAT(script_result, base::test::IsJson(kExpectedJson));
}
// Tests that, by default, the user script world's CSP is the same as the
// extension's CSP, but it can be updated to a more relaxed value.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
UserScriptWorldCspDefaultsToExtensionsAndCanBeUpdated) {
// Load a simple extension with permission to example.com and navigate a new
// tab to example.com.
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// Execute a script that attempts to eval() some code.
base::Value script_result = ExecuteScriptInUserScriptWorld(
kCheckIfEvalAllowedScriptSource, *extension);
// This should fail, since by default the user script world CSP is the same
// as the extension's CSP (which prevents eval).
EXPECT_EQ(script_result, "disallowed eval");
// Update the user script world CSP to allow unsafe eval.
SetDefaultUserScriptWorldProperties(*extension, "script-src 'unsafe-eval'",
/*enable_messaging=*/true);
// Navigate to create a new isolated world.
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// Now, eval should be allowed.
script_result = ExecuteScriptInUserScriptWorld(
kCheckIfEvalAllowedScriptSource, *extension);
EXPECT_EQ(script_result, "allowed eval");
}
// Tests that an update to the user script world's CSP does not apply to any
// already-created user script worlds.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
CspUpdatesDoNotApplyToExistingUserScriptWorlds) {
// Load a simple extension with permission to example.com and navigate a new
// tab to example.com.
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
base::Value script_result = ExecuteScriptInUserScriptWorld(
kCheckIfEvalAllowedScriptSource, *extension);
EXPECT_EQ(script_result, "disallowed eval");
// Update the user script world CSP to allow unsafe eval.
SetDefaultUserScriptWorldProperties(*extension, "script-src 'unsafe-eval'",
/*enable_messaging=*/true);
// Re-evaluate the script. Eval should still be disallowed since CSP updates
// do not apply to existing isolated worlds (by design).
script_result = ExecuteScriptInUserScriptWorld(
kCheckIfEvalAllowedScriptSource, *extension);
EXPECT_EQ(script_result, "disallowed eval");
}
// Tests that newly-created documents may greedily initialize isolated world CSP
// values.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
CspMayBeGreedilyInitializedOnDocumentCreation) {
// Load a simple extension with permission to example.com and navigate a new
// tab to example.com.
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
base::Value script_result = ExecuteScriptInUserScriptWorld(
kCheckIfEvalAllowedScriptSource, *extension);
EXPECT_EQ(script_result, "disallowed eval");
// Navigate to create a new document. At this point, no user script code has
// injected in this new document.
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// Update the user script world CSP to allow unsafe eval.
SetDefaultUserScriptWorldProperties(*extension, "script-src 'unsafe-eval'",
/*enable_messaging=*/true);
// Re-evaluate the script. Somewhat surprisingly, eval is still disallowed.
// This is because the new document greedily instantiates CSP for the current
// execution world, which, in this case, is the isolated world. This results
// in the isolated world CSP for the document being set when we navigate,
// which is before the new CSP is set. While not necessarily desirable, this
// is largely okay -- the proper CSP will be set whenever a new world is
// created, and we document that setting the CSP doesn't affect any existing
// isolated worlds. This test is mostly here for documentation and to
// highlight behavior changes.
script_result = ExecuteScriptInUserScriptWorld(
kCheckIfEvalAllowedScriptSource, *extension);
EXPECT_EQ(script_result, "disallowed eval");
}
// Tests sending a message from a user script. This is sent via
// runtime.sendMessage from the user script, and should be received via
// runtime.onUserScriptMessage in the background script.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest, SendMessageAPI) {
static constexpr char kManifest[] =
R"({
"name": "User Script Extension",
"manifest_version": 3,
"version": "0.1",
"background": {"service_worker": "background.js"},
"host_permissions": ["http://example.com/*"]
})";
// The background script will listen for a message from a user script.
// Upon receiving one, it will validate the message and sender and respond
// with 'pong'.
static constexpr char kBackgroundJs[] =
R"(chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
chrome.test.fail(`Unexpected message received: ${msg}`);
});
chrome.runtime.onMessageExternal.addListener(
(msg, sender, sendResponse) => {
chrome.test.fail(`Unexpected external message received: ${msg}`);
});
chrome.runtime.onUserScriptMessage.addListener(
(msg, sender, sendResponse) => {
chrome.test.assertEq('ping', msg);
const url = new URL(sender.url);
chrome.test.assertEq('example.com', url.hostname);
chrome.test.assertEq('/simple.html', url.pathname);
chrome.test.assertEq(0, sender.frameId);
chrome.test.assertTrue(!!sender.tab);
sendResponse('pong');
chrome.test.succeed();
});)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Enable messaging.
SetDefaultUserScriptWorldProperties(*extension, std::nullopt,
/*enable_messaging=*/true);
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// A bit overly nifty: here, we execute a user script that sends a message.
// Because this an MV3 extension, sendMessage() will return a promise that
// resolves when the other end responds. The ScriptExecutor will wait for
// that promise to resolve, so the end value of this script is the response
// from the background script.
static constexpr char kScriptSource[] =
R"(chrome.runtime.sendMessage('ping');)";
// The ResultCatcher validates the background script checks...
ResultCatcher result_catcher;
base::Value script_result =
ExecuteScriptInUserScriptWorld(kScriptSource, *extension);
// ...And the script result validates the user script expectation.
EXPECT_EQ(script_result, "pong");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
// Tests opening a message port from a user script. This is sent via
// runtime.connect() from the user script, and should be received via
// runtime.onUserScriptConnect in the background script.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest, ConnectAPI) {
static constexpr char kManifest[] =
R"({
"name": "User Script Extension",
"manifest_version": 3,
"version": "0.1",
"background": {"service_worker": "background.js"},
"host_permissions": ["http://example.com/*"]
})";
// The background script will listen for a new connection from a user script.
// Upon one opening, it validates the opener and waits for a new message,
// then validating the message and responding with 'pong', and then
// succeeds when the port is disconnected (after having received the message).
static constexpr char kBackgroundJs[] =
R"(chrome.runtime.onConnect.addListener((port) => {
chrome.test.fail(`Unexpected connection received`);
});
chrome.runtime.onConnectExternal.addListener((port) => {
chrome.test.fail(`Unexpected external connection received`);
});
chrome.runtime.onUserScriptConnect.addListener((port) => {
chrome.test.assertEq('myport', port.name);
const sender = port.sender;
chrome.test.assertTrue(!!sender);
const url = new URL(sender.url);
chrome.test.assertEq('example.com', url.hostname);
chrome.test.assertEq('/simple.html', url.pathname);
chrome.test.assertEq(0, sender.frameId);
chrome.test.assertTrue(!!sender.tab);
let receivedMsg = false;
port.onMessage.addListener((msg) => {
receivedMsg = true;
chrome.test.assertEq('ping', msg);
port.postMessage('pong');
});
port.onDisconnect.addListener(() => {
chrome.test.assertTrue(receivedMsg);
chrome.test.succeed();
});
});)";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Enable messaging.
SetDefaultUserScriptWorldProperties(*extension, std::nullopt,
/*enable_messaging=*/true);
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// The user script will open a port, post 'ping', wait for the responding
// 'pong', and then disconnect the port. We execute this in a promise with
// the expected resolved value of 'success'.
static constexpr char kScriptSource[] =
R"(new Promise((resolve) => {
let port = chrome.runtime.connect({name: 'myport'});
port.onMessage.addListener((msg) => {
if (msg != 'pong') {
resolve(`Unexpected message: ${msg}`);
return;
}
port.disconnect();
resolve('success');
});
port.postMessage('ping');
});)";
// The ResultCatcher validates the background script checks...
ResultCatcher result_catcher;
base::Value script_result =
ExecuteScriptInUserScriptWorld(kScriptSource, *extension);
// ...And the script result validates the user script expectation.
EXPECT_EQ(script_result, "success");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
// Tests that attempting to message another extension from a user script throws
// an error.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
TryingToSendMessageToOtherExtensionTriggersError) {
static constexpr char kManifest[] =
R"({
"name": "User Script Extension",
"manifest_version": 3,
"version": "0.1",
"host_permissions": ["http://example.com/*"]
})";
TestExtensionDir test_dir;
test_dir.WriteManifest(kManifest);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
// Enable messaging.
SetDefaultUserScriptWorldProperties(*extension, std::nullopt,
/*enable_messaging=*/true);
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
static constexpr char kTrySendMessage[] =
R"(let targetId = 'a'.repeat(32);
let errorMsg = /User scripts may not message external extensions./;
chrome.test.runTests([
function sendMessageToExternalExtensionThrowsError() {
chrome.test.assertThrows(chrome.runtime.sendMessage, null,
[targetId, 'test message'], errorMsg);
chrome.test.succeed();
},
function connectToExternalExtensionThrowsError() {
chrome.test.assertThrows(chrome.runtime.connect, null,
[targetId], errorMsg);
chrome.test.succeed();
},
]);
// Eval the script to a non-null value.
'success';)";
ResultCatcher result_catcher;
base::Value script_result =
ExecuteScriptInUserScriptWorld(kTrySendMessage, *extension);
EXPECT_EQ(script_result, "success");
EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message();
}
// Verifies that messaging APIs are exposed if and only if the user script world
// is configured to allow them.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
MessagingAPIsAreNotExposedIfEnableMessagingIsFalse) {
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
GURL example_com =
embedded_test_server()->GetURL("example.com", "/simple.html");
NavigateToURL(example_com);
// By default, messaging APIs are not allowed.
{
base::Value script_result =
ExecuteScriptInUserScriptWorld(kGetMessagingProperties, *extension);
EXPECT_THAT(script_result, base::test::IsJson("[]"));
}
// Flip the bit to allow messaging APIs and refresh the page.
SetDefaultUserScriptWorldProperties(*extension, std::nullopt,
/*enable_messaging=*/true);
NavigateToURL(example_com);
// Now, all messaging APIs should be exposed.
{
base::Value script_result =
ExecuteScriptInUserScriptWorld(kGetMessagingProperties, *extension);
EXPECT_THAT(script_result,
base::test::IsJson(
R"(["sendMessage","onMessage","connect","onConnect"])"));
}
}
// Tests injection into different user script worlds in the renderer.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
DifferentUserScriptWorldsAreIsolated) {
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
GURL example_com =
embedded_test_server()->GetURL("example.com", "/simple.html");
NavigateToURL(example_com);
// Code that either sets `flag` to track the number of times script has
// injected in this world (either initializing to `1` or incrementing the
// previous value).
constexpr char kCode[] =
R"(window.flag = window.flag ? window.flag + 1 : 1;
window.flag;)";
// First, inject into "world 1". The return result should be `1`, since this
// is the first time we're injecting in "world 1".
EXPECT_EQ(base::Value(1),
ExecuteScriptInUserScriptWorld(kCode, *extension, "world 1"));
// Next, inject into "world 2". Since this is a different user script world,
// the return result should again be `1` -- user scripts worlds are isolated
// from each other.
EXPECT_EQ(base::Value(1),
ExecuteScriptInUserScriptWorld(kCode, *extension, "world 2"));
// Finally, inject into "world 1" again. The return result should be `2`,
// since this is the second time we're injecting into this user script world.
EXPECT_EQ(base::Value(2),
ExecuteScriptInUserScriptWorld(kCode, *extension, "world 1"));
}
// Tests that different user script worlds have unique configurations for CSP.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
UniquePropertiesPerUserScriptWorld_CSP) {
// Load a simple extension with permission to example.com and navigate a new
// tab to example.com.
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// Check whether eval is allowed in either "world 1" or "world 2". Neither
// have been configured, so both should default to disallowing eval.
EXPECT_EQ(ExecuteScriptInUserScriptWorld(kCheckIfEvalAllowedScriptSource,
*extension, "world 1"),
"disallowed eval");
EXPECT_EQ(ExecuteScriptInUserScriptWorld(kCheckIfEvalAllowedScriptSource,
*extension, "world 2"),
"disallowed eval");
// Allow eval in "world 1", but leave "world 2" as default (disallowing eval).
SetUserScriptWorldProperties(*extension, "world 1",
"script-src 'unsafe-eval'",
/*enable_messaging=*/false);
// Navigate to create a new isolated world.
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// Check whether eval is allowed again. It should be allowed in world 1,
// but not in world 2.
EXPECT_EQ(ExecuteScriptInUserScriptWorld(kCheckIfEvalAllowedScriptSource,
*extension, "world 1"),
"allowed eval");
EXPECT_EQ(ExecuteScriptInUserScriptWorld(kCheckIfEvalAllowedScriptSource,
*extension, "world 2"),
"disallowed eval");
}
// Tests clearing configurations for user script worlds.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
ClearingUserScriptWorldConfigurations) {
// Load a simple extension with permission to example.com.
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
// Allow eval in "world 1".
SetUserScriptWorldProperties(*extension, "world 1",
"script-src 'unsafe-eval'",
/*enable_messaging=*/false);
// Navigate to example.com and check the world's CSP.
const GURL url =
embedded_test_server()->GetURL("example.com", "/simple.html");
NavigateToURL(url);
EXPECT_EQ(ExecuteScriptInUserScriptWorld(kCheckIfEvalAllowedScriptSource,
*extension, "world 1"),
"allowed eval");
// Now, clear the configuration for "world 1".
ClearUserScriptWorldProperties(*extension, "world 1");
// Ensure the roundtrip to the renderer completes by sending another script
// to inject. Otherwise, the call to clear the configuration may race with the
// new document creation and, as described in
// `CspMayBeGreedilyInitializedOnDocumentCreation`, this may initialize the
// CSP to the old one.
EXPECT_EQ("foo",
ExecuteScriptInUserScriptWorld("'foo';", *extension, "world 1"));
// Navigate to create a new isolated world.
NavigateToURL(url);
// Eval should be disallowed (the default) since the configuration was
// cleared.
EXPECT_EQ(ExecuteScriptInUserScriptWorld(kCheckIfEvalAllowedScriptSource,
*extension, "world 1"),
"disallowed eval");
}
// Tests that different user script worlds have unique configurations for
// enabling messaging.
IN_PROC_BROWSER_TEST_F(UserScriptWorldBrowserTest,
UniquePropertiesPerUserScriptWorld_Messaging) {
const Extension* extension =
LoadExtensionWithHostPermission("http://example.com/*");
GURL example_com =
embedded_test_server()->GetURL("example.com", "/simple.html");
NavigateToURL(example_com);
// Check whether messaging APIs are allowed in either "world 1" or "world 2".
// Neither have been configured, so both should default to disallowing
// messaging.
EXPECT_THAT(ExecuteScriptInUserScriptWorld(kGetMessagingProperties,
*extension, "world 1"),
base::test::IsJson("[]"));
EXPECT_THAT(ExecuteScriptInUserScriptWorld(kGetMessagingProperties,
*extension, "world 2"),
base::test::IsJson("[]"));
// Allow messaging in "world 1", but leave "world 2" as default (disallowing
// messaging).
SetUserScriptWorldProperties(*extension, "world 1",
/*csp=*/std::nullopt,
/*enable_messaging=*/true);
// Navigate to create a new isolated world.
NavigateToURL(embedded_test_server()->GetURL("example.com", "/simple.html"));
// Check whether messaging is allowed again. It should be allowed in world 1,
// but not in world 2.
EXPECT_THAT(ExecuteScriptInUserScriptWorld(kGetMessagingProperties,
*extension, "world 1"),
base::test::IsJson(
R"(["sendMessage","onMessage","connect","onConnect"])"));
EXPECT_THAT(ExecuteScriptInUserScriptWorld(kGetMessagingProperties,
*extension, "world 2"),
base::test::IsJson("[]"));
}
} // namespace extensions