blob: 2a04beee994fa789b5c910e7533ee993a32846c1 [file] [log] [blame]
// Copyright 2018 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 "extensions/renderer/storage_area.h"
#include "base/cxx17_backports.h"
#include "base/strings/stringprintf.h"
#include "components/version_info/channel.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "extensions/renderer/bindings/api_binding_util.h"
#include "extensions/renderer/bindings/api_invocation_errors.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 {
using StorageAreaTest = NativeExtensionBindingsSystemUnittest;
// A specialization of StorageAreaTest that pretends it's running
// on version_info::Channel::UNKNOWN.
class StorageAreaTrunkTest : public StorageAreaTest {
public:
StorageAreaTrunkTest() = default;
~StorageAreaTrunkTest() override = default;
StorageAreaTrunkTest(const StorageAreaTrunkTest& other) = delete;
StorageAreaTrunkTest& operator=(const StorageAreaTrunkTest& other) = delete;
private:
// TODO(crbug.com/1185226): Remove unknown channel when chrome.storage.session
// is released in stable.
ScopedCurrentChannel current_channel_{version_info::Channel::UNKNOWN};
};
// Test that trying to use StorageArea.get without a StorageArea `this` fails
// (with a helpful error message).
TEST_F(StorageAreaTest, TestUnboundedUse) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("foo").AddPermission("storage").Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
v8::Local<v8::Value> storage_get =
V8ValueFromScriptSource(context, "chrome.storage.local.get");
ASSERT_TRUE(storage_get->IsFunction());
constexpr char kRunStorageGet[] =
"(function(get) { get('foo', function() {}); })";
v8::Local<v8::Function> run_storage_get =
FunctionFromString(context, kRunStorageGet);
v8::Local<v8::Value> args[] = {storage_get};
RunFunctionAndExpectError(
run_storage_get, context, base::size(args), args,
"Uncaught TypeError: Illegal invocation: Function must be called on "
"an object of type StorageArea");
}
TEST_F(StorageAreaTest, TestUseAfterInvalidation) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("foo").AddPermission("storage").Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
v8::Local<v8::Value> storage =
V8ValueFromScriptSource(context, "chrome.storage.local");
ASSERT_TRUE(storage->IsObject());
constexpr char kRunStorageGet[] =
"(function(storage) { storage.get('foo', function() {}); })";
v8::Local<v8::Function> run_storage_get =
FunctionFromString(context, kRunStorageGet);
v8::Local<v8::Value> args[] = {storage};
RunFunction(run_storage_get, context, base::size(args), args);
DisposeContext(context);
EXPECT_FALSE(binding::IsContextValid(context));
RunFunctionAndExpectError(run_storage_get, context, base::size(args), args,
"Uncaught Error: Extension context invalidated.");
}
TEST_F(StorageAreaTest, InvalidInvocationError) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("foo").AddPermission("storage").Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
v8::Local<v8::Value> storage =
V8ValueFromScriptSource(context, "chrome.storage.local");
ASSERT_TRUE(storage->IsObject());
constexpr char kRunStorageGet[] =
"(function(storage) { storage.get(1, function() {}); })";
v8::Local<v8::Function> run_storage_get =
FunctionFromString(context, kRunStorageGet);
v8::Local<v8::Value> args[] = {storage};
RunFunctionAndExpectError(
run_storage_get, context, base::size(args), args,
"Uncaught TypeError: " +
api_errors::InvocationError(
"storage.get",
"optional [string|array|object] keys, function callback",
"No matching signature."));
}
TEST_F(StorageAreaTrunkTest, HasOnChanged) {
scoped_refptr<const Extension> extension =
ExtensionBuilder("foo")
.SetManifestKey("manifest_version", 3)
.AddPermission("storage")
.Build();
RegisterExtension(extension);
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = MainContext();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
const char* kStorages[] = {"sync", "local", "managed", "session"};
for (auto* kStorage : kStorages) {
const std::string kRegisterListener = base::StringPrintf(
R"((function() {
chrome.storage.%s.onChanged.addListener(
function(change) {
this.change = change;
});
}))",
kStorage);
v8::Local<v8::Function> add_listener =
FunctionFromString(context, kRegisterListener);
RunFunctionOnGlobal(add_listener, context, 0, nullptr);
bindings_system()->DispatchEventInContext(
base::StringPrintf("storage.%s.onChanged", kStorage).c_str(),
ListValueFromString("['foo']").get(), nullptr, script_context);
EXPECT_EQ("\"foo\"", GetStringPropertyFromObject(context->Global(), context,
"change"));
}
}
} // namespace extensions