blob: 8aab3b27b91c5d925ab57ec2353d5f2ff3369ae2 [file] [log] [blame]
// Copyright 2017 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/stringprintf.h"
#include "base/test/scoped_feature_list.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "extensions/renderer/dispatcher.h"
#include "extensions/renderer/native_extension_bindings_system.h"
#include "extensions/renderer/native_extension_bindings_system_test_base.h"
#include "extensions/renderer/script_context.h"
namespace extensions {
namespace {
constexpr char kCallUserScriptsRegister[] =
R"((function() {
chrome.userScripts.register(
[{
id: 'script',
matches: ['*://*/*'],
js: [{file: 'script.js'}],
}]);
});)";
constexpr char kCallDebuggerGetTargets[] =
R"((function() {
chrome.debugger.getTargets();
});)";
} // namespace
TEST_F(NativeExtensionBindingsSystemUnittest, InitializeContext) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("foo")
.AddAPIPermissions({"idle", "power", "webRequest", "tabs"})
.Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), mojom::ContextType::kPrivilegedExtension);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
// Test that chrome-specified APIs (like tabs) are present.
v8::Local<v8::Value> chrome =
GetPropertyFromObject(context->Global(), context, "chrome");
ASSERT_FALSE(chrome.IsEmpty());
ASSERT_TRUE(chrome->IsObject());
v8::Local<v8::Value> tabs =
GetPropertyFromObject(chrome.As<v8::Object>(), context, "tabs");
ASSERT_FALSE(tabs.IsEmpty());
ASSERT_TRUE(tabs->IsObject());
v8::Local<v8::Value> query =
GetPropertyFromObject(tabs.As<v8::Object>(), context, "query");
ASSERT_FALSE(query.IsEmpty());
ASSERT_TRUE(query->IsFunction());
}
// Tests that Developer Mode controls API visibility. The API controlled
// depends on feature state.
class DeveloperModeBindingsSystemUnittest
: public NativeExtensionBindingsSystemUnittest,
public testing::WithParamInterface<bool> {
public:
DeveloperModeBindingsSystemUnittest() {
user_scripts_api_controlled_by_dev_mode_ = GetParam();
if (user_scripts_api_controlled_by_dev_mode_) {
// Ensure chrome.userScripts is controlled by Developer Mode.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{}, /*disabled_features=*/{
extensions_features::kUserScriptUserExtensionToggle,
extensions_features::kDebuggerAPIRestrictedToDevMode});
} else {
// Ensure chrome.debugger is controlled by Developer Mode.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/
{extensions_features::kUserScriptUserExtensionToggle,
extensions_features::kDebuggerAPIRestrictedToDevMode},
/*disabled_features=*/{});
}
}
DeveloperModeBindingsSystemUnittest(
const DeveloperModeBindingsSystemUnittest&) = delete;
DeveloperModeBindingsSystemUnittest& operator=(
const DeveloperModeBindingsSystemUnittest&) = delete;
~DeveloperModeBindingsSystemUnittest() override = default;
protected:
bool user_scripts_api_controlled_by_dev_mode() {
return user_scripts_api_controlled_by_dev_mode_;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
bool user_scripts_api_controlled_by_dev_mode_;
};
TEST_P(DeveloperModeBindingsSystemUnittest,
RestrictDeveloperModeAPIsUserIsInDeveloperMode) {
// With kDeveloperModeRestriction enabled, developer mode-only APIs
// should be available if and only if the user is in dev mode.
SetCurrentDeveloperMode(kRendererProfileId, true);
std::string api_name =
user_scripts_api_controlled_by_dev_mode() ? "userScripts" : "debugger";
scoped_refptr<const Extension> extension = ExtensionBuilder("foo")
.AddAPIPermission(api_name)
.Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), mojom::ContextType::kPrivilegedExtension);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
v8::Local<v8::Value> chrome =
GetPropertyFromObject(context->Global(), context, "chrome");
ASSERT_FALSE(chrome.IsEmpty());
ASSERT_TRUE(chrome->IsObject());
v8::Local<v8::Value> api = GetPropertyFromObject(
v8::Local<v8::Object>::Cast(chrome), context, api_name);
ASSERT_FALSE(api.IsEmpty());
ASSERT_TRUE(api->IsObject());
// `api_name`.`api_method` should exist.
v8::Local<v8::Object> api_object = v8::Local<v8::Object>::Cast(api);
std::string api_method =
user_scripts_api_controlled_by_dev_mode() ? "register" : "getTargets";
v8::Local<v8::Value> api_method_call =
GetPropertyFromObject(api_object, context, api_method);
ASSERT_FALSE(api_method_call.IsEmpty());
{
v8::Local<v8::Function> call_api_method =
FunctionFromString(context, user_scripts_api_controlled_by_dev_mode()
? kCallUserScriptsRegister
: kCallDebuggerGetTargets);
RunFunctionOnGlobal(call_api_method, context, 0, nullptr);
}
// Validate the params that would be sent to the browser.
EXPECT_TRUE(last_params().has_callback);
if (user_scripts_api_controlled_by_dev_mode()) {
EXPECT_EQ(extension->id(), last_params().extension_id);
EXPECT_EQ("userScripts.register", last_params().name);
EXPECT_EQ(extension->url(), last_params().source_url);
// No need to look at the full arguments, but sanity check their general
// shape.
EXPECT_EQ(1u, last_params().arguments.size());
EXPECT_EQ(base::Value::Type::LIST, last_params().arguments[0].type());
} else {
EXPECT_EQ("debugger.getTargets", last_params().name);
// No need to look at the full arguments, but sanity check their general
// shape.
EXPECT_EQ(0u, last_params().arguments.size());
}
}
TEST_P(DeveloperModeBindingsSystemUnittest,
RestrictDeveloperModeAPIsUserIsNotInDeveloperModeAndHasPermission) {
// With kDeveloperModeRestriction enabled, developer mode-only APIs
// should not be available if the user is not in dev mode.
SetCurrentDeveloperMode(kRendererProfileId, false);
std::string api_name =
user_scripts_api_controlled_by_dev_mode() ? "userScripts" : "debugger";
scoped_refptr<const Extension> extension = ExtensionBuilder("foo")
.AddAPIPermission(api_name)
.Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), mojom::ContextType::kPrivilegedExtension);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
v8::Local<v8::Value> chrome =
GetPropertyFromObject(context->Global(), context, "chrome");
ASSERT_FALSE(chrome.IsEmpty());
ASSERT_TRUE(chrome->IsObject());
{
v8::Local<v8::Function> call_api_method =
FunctionFromString(context, user_scripts_api_controlled_by_dev_mode()
? kCallUserScriptsRegister
: kCallDebuggerGetTargets);
std::string expected_error = base::StringPrintf(
"Uncaught Error: The '%s' API is only "
"available for users in developer mode.",
api_name);
RunFunctionAndExpectError(call_api_method, context, 0, nullptr,
expected_error);
}
}
TEST_P(
DeveloperModeBindingsSystemUnittest,
RestrictDeveloperModeAPIsUserIsNotInDeveloperModeAndDoesNotHavePermission) {
SetCurrentDeveloperMode(kRendererProfileId, false);
scoped_refptr<const Extension> extension = ExtensionBuilder("foo").Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), mojom::ContextType::kPrivilegedExtension);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
v8::Local<v8::Value> chrome =
GetPropertyFromObject(context->Global(), context, "chrome");
ASSERT_FALSE(chrome.IsEmpty());
ASSERT_TRUE(chrome->IsObject());
std::string api_name =
user_scripts_api_controlled_by_dev_mode() ? "userScripts" : "debugger";
v8::Local<v8::Value> api = GetPropertyFromObject(
v8::Local<v8::Object>::Cast(chrome), context, api_name);
ASSERT_FALSE(api.IsEmpty());
EXPECT_TRUE(api->IsUndefined());
}
INSTANTIATE_TEST_SUITE_P(UserScriptsAPIControlledByDeveloperMode,
DeveloperModeBindingsSystemUnittest,
testing::ValuesIn({true}));
INSTANTIATE_TEST_SUITE_P(DebuggerAPIControlledByDeveloperMode,
DeveloperModeBindingsSystemUnittest,
testing::ValuesIn({false}));
} // namespace extensions