blob: 8d13099174c15c08236f183f24c7ca5c690b30c5 [file] [log] [blame]
// Copyright 2025 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/strings/strcat.h"
#include "chrome/browser/extensions/api/user_scripts/user_scripts_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/background_script_executor.h"
#include "extensions/common/extension_id.h"
#include "extensions/test/extension_test_message_listener.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/element_identifier.h"
namespace extensions {
class UserScriptsUITest : public InteractiveBrowserTestT<UserScriptsAPITest> {
public:
// Checks that toggle is `!enabled` and then toggles it to `enabled` state.
auto CheckCurrentToggleStateAndThenToggleItInUI(
ui::ElementIdentifier page_id,
const DeepQuery toggle_dom_path,
bool enabled) {
return Steps(
// ClickElement() won't click something off screen so scroll the toggle
// into view in case it is not.
ScrollIntoView(page_id, toggle_dom_path),
// Check the toggle is `!enabled`.
EnsurePresent(page_id, toggle_dom_path),
CheckJsResultAt(page_id, toggle_dom_path, "(el) => el.checked",
!enabled),
// Click the toggle and check it is `enabled`.
ClickElement(page_id, toggle_dom_path),
CheckJsResultAt(page_id, toggle_dom_path, "(el) => el.checked",
enabled));
}
// Toggles the extensions_features::kUserScriptUserExtensionToggle toggle to
// `enabled` state.
auto TogglePerExtensionToggleInUI(ui::ElementIdentifier page_id,
const ExtensionId& extension_id,
bool enabled) {
const DeepQuery kPathToUserScriptsToggle{
"extensions-manager",
"extensions-detail-view",
"extensions-toggle-row#allow-user-scripts",
"cr-toggle#crToggle",
};
// Enable the per-extension toggle in the UI.
return Steps(
// Navigate to the extensions detail page for the extension (where the
// toggle lives).
NavigateWebContents(page_id,
GURL(base::StrCat({chrome::kChromeUIExtensionsURL,
"?id=", extension_id.c_str()}))),
CheckCurrentToggleStateAndThenToggleItInUI(
page_id, kPathToUserScriptsToggle, enabled));
}
// Toggles the (non extensions_features::kUserScriptUserExtensionToggle state)
// dev mode toggle `enabled` state.
auto ToggleDevModeInUI(ui::ElementIdentifier page_id, bool enabled) {
const DeepQuery kPathToDevModeToggle{
"extensions-manager extensions-toolbar",
"cr-toggle#devMode",
};
// Enable dev mode toggle in the UI.
return Steps(
// Navigate to the extensions detail page for the extension.
NavigateWebContents(page_id, GURL(chrome::kChromeUIExtensionsURL)),
CheckCurrentToggleStateAndThenToggleItInUI(
page_id, kPathToDevModeToggle, enabled));
}
// Checks that the chrome.userScripts API is not available in the background
// script.
auto VerifyUserScriptsIsNotAvailable(const ExtensionId& extension_id) {
// Register the user script in the extension background script and confirm
// it registered successfully.
return CheckResult(
[this, &extension_id]() -> bool {
return CheckUserScriptsAPIAvailability(extension_id) == "unavailable";
},
true, "Checking that the userScripts API is not available");
}
std::string CheckUserScriptsAPIAvailability(const ExtensionId& extension_id) {
return BackgroundScriptExecutor::ExecuteScript(
profile(), extension_id, "checkApiAvailability();",
BackgroundScriptExecutor::ResultCapture::kSendScriptResult)
.GetString();
}
// Registers a dynamic user script with the chrome.userScripts API.
auto RegisterUserScript(const ExtensionId& extension_id) {
// Register the user script in the extension background script and confirm
// it registered successfully.
return CheckResult(
[this, &extension_id]() -> std::string {
return BackgroundScriptExecutor::ExecuteScript(
profile(), extension_id, "registerUserScripts();",
BackgroundScriptExecutor::ResultCapture::kSendScriptResult)
.TakeString();
},
/*matcher=*/"success", /*check_description=*/
"Registering dynamic user script and checking that it completed");
}
};
// Tests the toggling the UI toggle (dependent on feature) controls whether the
// user has allowed userScripts API usage.
IN_PROC_BROWSER_TEST_P(UserScriptsUITest, ToggleControls_UserScriptsAPIUsage) {
// Load extension that has API permission to use the userScripts API, but not
// the per-extension toggle for userScripts enabled.
ExtensionTestMessageListener extension_background_started_listener =
ExtensionTestMessageListener("started");
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("user_scripts/allowed_tests"));
ASSERT_TRUE(extension);
ASSERT_TRUE(extension_background_started_listener.WaitUntilSatisfied());
const DeepQuery kPathToUserScriptInjectedDiv{
"#user-script-code",
};
// This test verifies that:
// 1) The userScripts API is initially unavailable.
// 2) Enabling the toggle allows for a dynamic user script can be registered
// and injected.
// 3) Disabling the toggle causes user scripts to no longer inject
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTab);
DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(
ui::test::PollingStateObserver<std::string>, kApiAvailability);
RunTestSequence(
InstrumentTab(kTab),
// When the UI toggle changes to enable the userScript API the message
// path is: webUI (renderer) -> browser -> extension (renderer). Because
// of the multiple hops, the simplest method of checking is just polling
// for when the API changes to the desired state.
PollState(kApiAvailability,
[this, &extension]() {
return CheckUserScriptsAPIAvailability(extension->id());
}),
VerifyUserScriptsIsNotAvailable(extension->id()),
// Enable the toggle depending on feature state and wait for the
// userScripts API to become available to use.
GetParam() ? TogglePerExtensionToggleInUI(kTab, extension->id(),
/*enabled=*/true)
: ToggleDevModeInUI(kTab, /*enabled=*/true),
WaitForState(kApiAvailability, "available"),
RegisterUserScript(extension->id()),
// Navigate tab to a webpage where the user script should inject a <div>.
NavigateWebContents(
kTab, embedded_test_server()->GetURL("example.com", "/simple.html")),
// Ensure the user script injected its <div>.
EnsurePresent(kTab, kPathToUserScriptInjectedDiv),
// Disable the toggle depending on feature state and wait for the
// userScripts API to become unavailable to use.
GetParam() ? TogglePerExtensionToggleInUI(kTab, extension->id(),
/*enabled=*/false)
: ToggleDevModeInUI(kTab, /*enabled=*/false),
WaitForState(kApiAvailability, "unavailable"),
// Navigate tab to a webpage where the user script should no longer inject
// a <div>.
NavigateWebContents(
kTab, embedded_test_server()->GetURL("example.com", "/simple.html")),
// Ensure the user script no longer injects its <div>.
EnsureNotPresent(kTab, kPathToUserScriptInjectedDiv));
}
INSTANTIATE_TEST_SUITE_P(PerExtensionToggle,
UserScriptsUITest,
// extensions_features::kUserScriptUserExtensionToggle
testing::Values("true"));
INSTANTIATE_TEST_SUITE_P(DevModeToggle,
UserScriptsUITest,
// extensions_features::kUserScriptUserExtensionToggle
testing::Values("false"));
} // namespace extensions