blob: ea5d5bbeff2a81b5618abebf9a4bc8572fcb2b2b [file] [log] [blame]
// Copyright 2016 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/native_extension_bindings_system.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "components/crx_file/id_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/manifest.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/value_builder.h"
#include "extensions/renderer/api_binding_test.h"
#include "extensions/renderer/api_binding_test_util.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_context_set.h"
namespace extensions {
namespace {
// Creates an extension with the given |name| and |permissions|.
scoped_refptr<Extension> CreateExtension(
const std::string& name,
const std::vector<std::string>& permissions) {
DictionaryBuilder manifest;
manifest.Set("name", name);
manifest.Set("manifest_version", 2);
manifest.Set("version", "0.1");
manifest.Set("description", "test extension");
{
ListBuilder permissions_builder;
for (const std::string& permission : permissions)
permissions_builder.Append(permission);
manifest.Set("permissions", permissions_builder.Build());
}
return ExtensionBuilder()
.SetManifest(manifest.Build())
.SetLocation(Manifest::INTERNAL)
.SetID(crx_file::id_util::GenerateId(name))
.Build();
}
} // namespace
class NativeExtensionBindingsSystemUnittest : public APIBindingTest {
public:
NativeExtensionBindingsSystemUnittest() {}
~NativeExtensionBindingsSystemUnittest() override {}
protected:
void SetUp() override {
script_context_set_ = base::MakeUnique<ScriptContextSet>(&extension_ids_);
bindings_system_ = base::MakeUnique<NativeExtensionBindingsSystem>(
base::Bind(&NativeExtensionBindingsSystemUnittest::MockSendIPC,
base::Unretained(this)));
APIBindingTest::SetUp();
}
void TearDown() override {
for (const auto& context : raw_script_contexts_)
script_context_set_->Remove(context);
base::RunLoop().RunUntilIdle();
script_context_set_.reset();
bindings_system_.reset();
APIBindingTest::TearDown();
}
void MockSendIPC(ScriptContext* context,
const ExtensionHostMsg_Request_Params& params) {
last_params_.name = params.name;
last_params_.arguments.Swap(params.arguments.CreateDeepCopy().get());
last_params_.extension_id = params.extension_id;
last_params_.source_url = params.source_url;
last_params_.request_id = params.request_id;
last_params_.has_callback = params.has_callback;
last_params_.user_gesture = params.user_gesture;
last_params_.worker_thread_id = params.worker_thread_id;
last_params_.service_worker_version_id = params.service_worker_version_id;
}
ScriptContext* CreateScriptContext(v8::Local<v8::Context> v8_context,
Extension* extension,
Feature::Context context_type) {
auto script_context = base::MakeUnique<ScriptContext>(
v8_context, nullptr, extension, context_type, extension, context_type);
ScriptContext* raw_script_context = script_context.get();
raw_script_contexts_.push_back(raw_script_context);
script_context_set_->AddForTesting(std::move(script_context));
return raw_script_context;
}
void RegisterExtension(const ExtensionId& id) { extension_ids_.insert(id); }
NativeExtensionBindingsSystem* bindings_system() {
return bindings_system_.get();
}
const ExtensionHostMsg_Request_Params& last_params() { return last_params_; }
private:
ExtensionIdSet extension_ids_;
std::unique_ptr<ScriptContextSet> script_context_set_;
std::vector<ScriptContext*> raw_script_contexts_;
std::unique_ptr<NativeExtensionBindingsSystem> bindings_system_;
ExtensionHostMsg_Request_Params last_params_;
DISALLOW_COPY_AND_ASSIGN(NativeExtensionBindingsSystemUnittest);
};
TEST_F(NativeExtensionBindingsSystemUnittest, Basic) {
scoped_refptr<Extension> extension =
CreateExtension("foo", {"idle", "power"});
RegisterExtension(extension->id());
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = ContextLocal();
ScriptContext* script_context = CreateScriptContext(
context, extension.get(), Feature::BLESSED_EXTENSION_CONTEXT);
script_context->set_url(extension->url());
bindings_system()->UpdateBindingsForContext(script_context);
// chrome.idle.queryState should exist.
v8::Local<v8::Value> chrome =
GetPropertyFromObject(context->Global(), context, "chrome");
ASSERT_FALSE(chrome.IsEmpty());
ASSERT_TRUE(chrome->IsObject());
v8::Local<v8::Value> idle = GetPropertyFromObject(
v8::Local<v8::Object>::Cast(chrome), context, "idle");
ASSERT_FALSE(idle.IsEmpty());
ASSERT_TRUE(idle->IsObject());
v8::Local<v8::Value> idle_query_state = GetPropertyFromObject(
v8::Local<v8::Object>::Cast(idle), context, "queryState");
ASSERT_FALSE(idle_query_state.IsEmpty());
{
// Try calling the function with an invalid invocation - an error should be
// thrown.
const char kCallIdleQueryStateInvalid[] =
"(function() {\n"
" chrome.idle.queryState('foo', function(state) {\n"
" this.responseState = state;\n"
" });\n"
"});";
v8::Local<v8::Function> function =
FunctionFromString(context, kCallIdleQueryStateInvalid);
ASSERT_FALSE(function.IsEmpty());
RunFunctionAndExpectError(function, context, 0, nullptr,
"Uncaught TypeError: Invalid invocation");
}
{
// Call the function correctly.
const char kCallIdleQueryState[] =
"(function() {\n"
" chrome.idle.queryState(30, function(state) {\n"
" this.responseState = state;\n"
" });\n"
"});";
v8::Local<v8::Function> call_idle_query_state =
FunctionFromString(context, kCallIdleQueryState);
RunFunctionOnGlobal(call_idle_query_state, context, 0, nullptr);
}
// Validate the params that would be sent to the browser.
EXPECT_EQ(extension->id(), last_params().extension_id);
EXPECT_EQ("idle.queryState", last_params().name);
EXPECT_EQ(extension->url(), last_params().source_url);
EXPECT_TRUE(last_params().has_callback);
EXPECT_TRUE(
last_params().arguments.Equals(ListValueFromString("[30]").get()));
// Respond and validate.
bindings_system()->HandleResponse(last_params().request_id, true,
*ListValueFromString("['active']"),
std::string());
std::unique_ptr<base::Value> result_value = GetBaseValuePropertyFromObject(
context->Global(), context, "responseState");
ASSERT_TRUE(result_value);
EXPECT_EQ("\"active\"", ValueToString(*result_value));
// Sanity-check that another API also exists as expected.
v8::Local<v8::Value> power_api =
V8ValueFromScriptSource(context, "chrome.power");
ASSERT_FALSE(power_api.IsEmpty());
ASSERT_TRUE(power_api->IsObject());
v8::Local<v8::Value> request_keep_awake = GetPropertyFromObject(
power_api.As<v8::Object>(), context, "requestKeepAwake");
ASSERT_FALSE(request_keep_awake.IsEmpty());
EXPECT_TRUE(request_keep_awake->IsFunction());
}
// Tests that referencing the same API multiple times returns the same object;
// i.e. chrome.foo === chrome.foo.
TEST_F(NativeExtensionBindingsSystemUnittest, APIObjectsAreEqual) {
scoped_refptr<Extension> extension = CreateExtension("foo", {"idle"});
RegisterExtension(extension->id());
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = ContextLocal();
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> first_idle_object =
V8ValueFromScriptSource(context, "chrome.idle");
ASSERT_FALSE(first_idle_object.IsEmpty());
EXPECT_TRUE(first_idle_object->IsObject());
EXPECT_FALSE(first_idle_object->IsUndefined());
v8::Local<v8::Value> second_idle_object =
V8ValueFromScriptSource(context, "chrome.idle");
EXPECT_TRUE(first_idle_object == second_idle_object);
}
// Tests that referencing APIs after the context data is disposed is safe (and
// returns undefined).
TEST_F(NativeExtensionBindingsSystemUnittest,
ReferencingAPIAfterDisposingContext) {
scoped_refptr<Extension> extension =
CreateExtension("foo", {"idle", "power"});
RegisterExtension(extension->id());
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Context> context = ContextLocal();
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> first_idle_object =
V8ValueFromScriptSource(context, "chrome.idle");
ASSERT_FALSE(first_idle_object.IsEmpty());
EXPECT_TRUE(first_idle_object->IsObject());
DisposeContext();
// Check an API that was instantiated....
v8::Local<v8::Value> second_idle_object =
V8ValueFromScriptSource(context, "chrome.idle");
ASSERT_FALSE(second_idle_object.IsEmpty());
EXPECT_TRUE(second_idle_object->IsUndefined());
// ... and also one that wasn't.
v8::Local<v8::Value> power_object =
V8ValueFromScriptSource(context, "chrome.power");
ASSERT_FALSE(power_object.IsEmpty());
EXPECT_TRUE(power_object->IsUndefined());
}
} // namespace extensions