123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- // Copyright 2017 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/runtime_hooks_delegate.h"
- #include <memory>
- #include "base/strings/stringprintf.h"
- #include "components/crx_file/id_util.h"
- #include "content/public/common/child_process_host.h"
- #include "extensions/common/api/messaging/serialization_format.h"
- #include "extensions/common/extension.h"
- #include "extensions/common/extension_builder.h"
- #include "extensions/common/extension_messages.h"
- #include "extensions/common/value_builder.h"
- #include "extensions/renderer/bindings/api_binding_test_util.h"
- #include "extensions/renderer/message_target.h"
- #include "extensions/renderer/native_extension_bindings_system.h"
- #include "extensions/renderer/native_extension_bindings_system_test_base.h"
- #include "extensions/renderer/native_renderer_messaging_service.h"
- #include "extensions/renderer/script_context.h"
- #include "extensions/renderer/script_context_set.h"
- #include "extensions/renderer/send_message_tester.h"
- namespace extensions {
- namespace {
- void CallAPIAndExpectError(v8::Local<v8::Context> context,
- base::StringPiece method_name,
- base::StringPiece args) {
- SCOPED_TRACE(base::StringPrintf("Args: `%s`", args.data()));
- constexpr char kTemplate[] = "(function() { chrome.runtime.%s(%s); })";
- v8::Isolate* isolate = context->GetIsolate();
- // Just verify some error was thrown. Expecting the exact error message
- // tends to rely too much on our argument spec code, which is tested
- // separately.
- v8::Local<v8::Function> function = FunctionFromString(
- context, base::StringPrintf(kTemplate, method_name.data(), args.data()));
- v8::TryCatch try_catch(isolate);
- v8::MaybeLocal<v8::Value> result =
- function->Call(context, v8::Undefined(isolate), 0, nullptr);
- EXPECT_TRUE(result.IsEmpty());
- EXPECT_TRUE(try_catch.HasCaught());
- }
- } // namespace
- class RuntimeHooksDelegateTest : public NativeExtensionBindingsSystemUnittest {
- public:
- RuntimeHooksDelegateTest() {}
- RuntimeHooksDelegateTest(const RuntimeHooksDelegateTest&) = delete;
- RuntimeHooksDelegateTest& operator=(const RuntimeHooksDelegateTest&) = delete;
- ~RuntimeHooksDelegateTest() override {}
- // NativeExtensionBindingsSystemUnittest:
- void SetUp() override {
- NativeExtensionBindingsSystemUnittest::SetUp();
- messaging_service_ =
- std::make_unique<NativeRendererMessagingService>(bindings_system());
- bindings_system()->api_system()->GetHooksForAPI("runtime")->SetDelegate(
- std::make_unique<RuntimeHooksDelegate>(messaging_service_.get()));
- extension_ = BuildExtension();
- RegisterExtension(extension_);
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- script_context_ = CreateScriptContext(context, extension_.get(),
- Feature::BLESSED_EXTENSION_CONTEXT);
- script_context_->set_url(extension_->url());
- bindings_system()->UpdateBindingsForContext(script_context_);
- }
- void TearDown() override {
- script_context_ = nullptr;
- extension_ = nullptr;
- messaging_service_.reset();
- NativeExtensionBindingsSystemUnittest::TearDown();
- }
- bool UseStrictIPCMessageSender() override { return true; }
- virtual scoped_refptr<const Extension> BuildExtension() {
- return ExtensionBuilder("foo").Build();
- }
- NativeRendererMessagingService* messaging_service() {
- return messaging_service_.get();
- }
- ScriptContext* script_context() { return script_context_; }
- const Extension* extension() { return extension_.get(); }
- private:
- std::unique_ptr<NativeRendererMessagingService> messaging_service_;
- ScriptContext* script_context_ = nullptr;
- scoped_refptr<const Extension> extension_;
- };
- TEST_F(RuntimeHooksDelegateTest, RuntimeId) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- {
- scoped_refptr<const Extension> connectable_extension =
- ExtensionBuilder("connectable")
- .SetManifestPath({"externally_connectable", "matches"},
- ListBuilder().Append("*://example.com/*").Build())
- .Build();
- RegisterExtension(connectable_extension);
- }
- auto get_id = [](v8::Local<v8::Context> context) {
- v8::Local<v8::Function> get_id = FunctionFromString(
- context, "(function() { return chrome.runtime.id; })");
- return RunFunction(get_id, context, 0, nullptr);
- };
- {
- v8::Local<v8::Value> id = get_id(context);
- EXPECT_EQ(extension()->id(), gin::V8ToString(isolate(), id));
- }
- {
- // In order for chrome.runtime to be available to web pages, we need to have
- // an associated connectable extension, so pretend to be example.com.
- v8::Local<v8::Context> web_context = AddContext();
- ScriptContext* script_context =
- CreateScriptContext(web_context, nullptr, Feature::WEB_PAGE_CONTEXT);
- script_context->set_url(GURL("http://example.com"));
- bindings_system()->UpdateBindingsForContext(script_context);
- v8::Local<v8::Value> id = get_id(web_context);
- EXPECT_TRUE(id->IsUndefined());
- }
- }
- TEST_F(RuntimeHooksDelegateTest, GetManifest) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- v8::Local<v8::Function> get_manifest = FunctionFromString(
- context, "(function() { return chrome.runtime.getManifest(); })");
- v8::Local<v8::Value> manifest =
- RunFunction(get_manifest, context, 0, nullptr);
- ASSERT_FALSE(manifest.IsEmpty());
- ASSERT_TRUE(manifest->IsObject());
- EXPECT_EQ(ValueToString(*extension()->manifest()->value()),
- V8ToString(manifest, context));
- }
- TEST_F(RuntimeHooksDelegateTest, GetURL) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- auto get_url = [this, context](const char* args, const GURL& expected_url) {
- SCOPED_TRACE(base::StringPrintf("Args: `%s`", args));
- constexpr char kGetUrlTemplate[] =
- "(function() { return chrome.runtime.getURL(%s); })";
- v8::Local<v8::Function> get_url =
- FunctionFromString(context, base::StringPrintf(kGetUrlTemplate, args));
- v8::Local<v8::Value> url = RunFunction(get_url, context, 0, nullptr);
- ASSERT_FALSE(url.IsEmpty());
- ASSERT_TRUE(url->IsString());
- EXPECT_EQ(expected_url.spec(), gin::V8ToString(isolate(), url));
- };
- get_url("''", extension()->url());
- get_url("'foo'", extension()->GetResourceURL("foo"));
- get_url("'/foo'", extension()->GetResourceURL("foo"));
- get_url("'https://www.google.com'",
- GURL(extension()->url().spec() + "https://www.google.com"));
- }
- TEST_F(RuntimeHooksDelegateTest, Connect) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- {
- // Sanity check: connectNative is unavailable (missing permission).
- v8::Local<v8::Value> connect_native =
- V8ValueFromScriptSource(context, "chrome.runtime.connectNative");
- ASSERT_FALSE(connect_native.IsEmpty());
- EXPECT_TRUE(connect_native->IsUndefined());
- }
- SendMessageTester tester(ipc_message_sender(), script_context(), 0,
- "runtime");
- MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
- tester.TestConnect("", "", self_target);
- tester.TestConnect("{name: 'channel'}", "channel", self_target);
- tester.TestConnect("{includeTlsChannelId: true}", "", self_target);
- tester.TestConnect("{includeTlsChannelId: true, name: 'channel'}", "channel",
- self_target);
- std::string other_id = crx_file::id_util::GenerateId("other");
- MessageTarget other_target = MessageTarget::ForExtension(other_id);
- tester.TestConnect(base::StringPrintf("'%s'", other_id.c_str()), "",
- other_target);
- tester.TestConnect(
- base::StringPrintf("'%s', {name: 'channel'}", other_id.c_str()),
- "channel", other_target);
- }
- // Tests the end-to-end (renderer) flow for a call to runtime.sendMessage
- // from the call in JS to the expected IPCs. The intricacies of sendMessage
- // are also tested more in the renderer messaging service unittests.
- TEST_F(RuntimeHooksDelegateTest, SendMessage) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- {
- // Sanity check: sendNativeMessage is unavailable (missing permission).
- v8::Local<v8::Value> send_native_message =
- V8ValueFromScriptSource(context, "chrome.runtime.sendNativeMessage");
- ASSERT_FALSE(send_native_message.IsEmpty());
- EXPECT_TRUE(send_native_message->IsUndefined());
- }
- SendMessageTester tester(ipc_message_sender(), script_context(), 0,
- "runtime");
- MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
- tester.TestSendMessage("''", R"("")", self_target, SendMessageTester::CLOSED);
- constexpr char kStandardMessage[] = R"({"data":"hello"})";
- tester.TestSendMessage("{data: 'hello'}", kStandardMessage, self_target,
- SendMessageTester::CLOSED);
- tester.TestSendMessage("{data: 'hello'}, function() {}", kStandardMessage,
- self_target, SendMessageTester::OPEN);
- tester.TestSendMessage("{data: 'hello'}, {includeTlsChannelId: true}",
- kStandardMessage, self_target,
- SendMessageTester::CLOSED);
- tester.TestSendMessage(
- "{data: 'hello'}, {includeTlsChannelId: true}, function() {}",
- kStandardMessage, self_target, SendMessageTester::OPEN);
- std::string other_id_str = crx_file::id_util::GenerateId("other");
- const char* other_id = other_id_str.c_str(); // For easy StringPrintf()ing.
- MessageTarget other_target = MessageTarget::ForExtension(other_id_str);
- tester.TestSendMessage(base::StringPrintf("'%s', {data: 'hello'}", other_id),
- kStandardMessage, other_target,
- SendMessageTester::CLOSED);
- tester.TestSendMessage(
- base::StringPrintf("'%s', {data: 'hello'}, function() {}", other_id),
- kStandardMessage, other_target, SendMessageTester::OPEN);
- tester.TestSendMessage(base::StringPrintf("'%s', 'string message'", other_id),
- R"("string message")", other_target,
- SendMessageTester::CLOSED);
- // The sender could omit the ID by passing null or undefined explicitly.
- // Regression tests for https://crbug.com/828664.
- tester.TestSendMessage("null, {data: 'hello'}, function() {}",
- kStandardMessage, self_target,
- SendMessageTester::OPEN);
- tester.TestSendMessage("null, 'test', function() {}", R"("test")",
- self_target, SendMessageTester::OPEN);
- tester.TestSendMessage("null, 'test'", R"("test")", self_target,
- SendMessageTester::CLOSED);
- tester.TestSendMessage("undefined, 'test', function() {}", R"("test")",
- self_target, SendMessageTester::OPEN);
- // Funny case. The only required argument is `message`, which can be any type.
- // This means that if an extension provides a <string, object> pair for the
- // first three arguments, it could apply to either the target id and the
- // message or to the message and the connect options.
- // In this case, we *always* pick the arguments as target id and message,
- // because they were the first options (connectOptions was added later), and
- // because connectOptions is pretty rarely used.
- // TODO(devlin): This is the determination JS has always made, but we could be
- // a bit more intelligent about it. We could examine the string to see if it's
- // a valid extension id as well as looking at the properties on the object.
- // But probably not worth it at this time.
- tester.TestSendMessage(
- base::StringPrintf("'%s', {includeTlsChannelId: true}", other_id),
- R"({"includeTlsChannelId":true})", other_target,
- SendMessageTester::CLOSED);
- tester.TestSendMessage(
- base::StringPrintf("'%s', {includeTlsChannelId: true}, function() {}",
- other_id),
- R"({"includeTlsChannelId":true})", other_target, SendMessageTester::OPEN);
- }
- // Test that some incorrect invocations of sendMessage() throw errors.
- TEST_F(RuntimeHooksDelegateTest, SendMessageErrors) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- auto send_message = [context](base::StringPiece args) {
- CallAPIAndExpectError(context, "sendMessage", args);
- };
- send_message("{data: 'hi'}, {unknownProp: true}");
- send_message("'some id', 'some message', 'some other string'");
- send_message("'some id', 'some message', {}, {}");
- }
- TEST_F(RuntimeHooksDelegateTest, ConnectWithTrickyOptions) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- SendMessageTester tester(ipc_message_sender(), script_context(), 0,
- "runtime");
- MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
- {
- // Even though we parse the message options separately, we do a conversion
- // of the object passed into the API. This means that something subtle like
- // this, which throws on the second access of a property, shouldn't trip us
- // up.
- constexpr char kTrickyConnectOptions[] =
- R"({
- get name() {
- if (this.checkedOnce)
- throw new Error('tricked!');
- this.checkedOnce = true;
- return 'foo';
- }
- })";
- tester.TestConnect(kTrickyConnectOptions, "foo", self_target);
- }
- {
- // A different form of trickiness: the options object doesn't have the
- // name key (which is acceptable, since its optional), but
- // any attempt to access the key on an object without a value for it results
- // in an error. Our argument parsing code should protect us from this.
- constexpr const char kMessWithObjectPrototype[] =
- R"((function() {
- Object.defineProperty(
- Object.prototype, 'name',
- { get() { throw new Error('tricked!'); } });
- }))";
- v8::Local<v8::Function> mess_with_proto =
- FunctionFromString(context, kMessWithObjectPrototype);
- RunFunction(mess_with_proto, context, 0, nullptr);
- tester.TestConnect("{}", "", self_target);
- }
- }
- class RuntimeHooksDelegateNativeMessagingTest
- : public RuntimeHooksDelegateTest {
- public:
- RuntimeHooksDelegateNativeMessagingTest() {}
- ~RuntimeHooksDelegateNativeMessagingTest() override {}
- scoped_refptr<const Extension> BuildExtension() override {
- return ExtensionBuilder("foo").AddPermission("nativeMessaging").Build();
- }
- };
- TEST_F(RuntimeHooksDelegateNativeMessagingTest, ConnectNative) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- int next_context_port_id = 0;
- auto run_connect_native = [this, context, &next_context_port_id](
- const std::string& args,
- const std::string& expected_app_name) {
- // connectNative() doesn't name channels.
- const std::string kEmptyExpectedChannel;
- SCOPED_TRACE(base::StringPrintf("Args: '%s'", args.c_str()));
- constexpr char kAddPortTemplate[] =
- "(function() { return chrome.runtime.connectNative(%s); })";
- PortId expected_port_id(script_context()->context_id(),
- next_context_port_id++, true,
- SerializationFormat::kJson);
- MessageTarget expected_target(
- MessageTarget::ForNativeApp(expected_app_name));
- EXPECT_CALL(*ipc_message_sender(),
- SendOpenMessageChannel(script_context(), expected_port_id,
- expected_target, kEmptyExpectedChannel));
- v8::Local<v8::Function> add_port = FunctionFromString(
- context, base::StringPrintf(kAddPortTemplate, args.c_str()));
- v8::Local<v8::Value> port = RunFunction(add_port, context, 0, nullptr);
- ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
- ASSERT_FALSE(port.IsEmpty());
- ASSERT_TRUE(port->IsObject());
- };
- run_connect_native("'native_app'", "native_app");
- run_connect_native("'some_other_native_app'", "some_other_native_app");
- auto connect_native_error = [context](base::StringPiece args) {
- CallAPIAndExpectError(context, "connectNative", args);
- };
- connect_native_error("'native_app', {name: 'name'}");
- }
- TEST_F(RuntimeHooksDelegateNativeMessagingTest, SendNativeMessage) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- SendMessageTester tester(ipc_message_sender(), script_context(), 0,
- "runtime");
- tester.TestSendNativeMessage("'native_app', {hi:'bye'}", R"({"hi":"bye"})",
- "native_app");
- tester.TestSendNativeMessage(
- "'another_native_app', {alpha: 2}, function() {}", R"({"alpha":2})",
- "another_native_app");
- auto send_native_message_error = [context](base::StringPiece args) {
- CallAPIAndExpectError(context, "sendNativeMessage", args);
- };
- send_native_message_error("{data: 'hi'}, function() {}");
- send_native_message_error(
- "'native_app', 'some message', {includeTlsChannelId: true}");
- }
- class RuntimeHooksDelegateMV3Test : public RuntimeHooksDelegateTest {
- public:
- RuntimeHooksDelegateMV3Test() = default;
- ~RuntimeHooksDelegateMV3Test() override = default;
- scoped_refptr<const Extension> BuildExtension() override {
- return ExtensionBuilder("foo")
- .SetManifestKey("manifest_version", 3)
- .Build();
- }
- };
- TEST_F(RuntimeHooksDelegateMV3Test, SendMessageUsingPromise) {
- v8::HandleScope handle_scope(isolate());
- SendMessageTester tester(ipc_message_sender(), script_context(), 0,
- "runtime");
- // The port remains open here after the call because in MV3 we return a
- // promise if the callback parameter is omitted, so we can't use the presence/
- // lack of the callback to determine if the caller is/isn't going to handle
- // the response.
- MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
- tester.TestSendMessage("''", R"("")", self_target, SendMessageTester::OPEN);
- constexpr char kStandardMessage[] = R"({"data":"hello"})";
- {
- // Calling sendMessage with a callback should result in no value returned.
- v8::Local<v8::Value> result = tester.TestSendMessage(
- "{data: 'hello'}, function() {}", kStandardMessage, self_target,
- SendMessageTester::OPEN);
- EXPECT_TRUE(result->IsUndefined());
- }
- {
- // Calling sendMessage without the callback should result in a promise
- // returned.
- v8::Local<v8::Value> result =
- tester.TestSendMessage("{data: 'hello'}", kStandardMessage, self_target,
- SendMessageTester::OPEN);
- v8::Local<v8::Promise> promise;
- ASSERT_TRUE(GetValueAs(result, &promise));
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- }
- }
- class RuntimeHooksDelegateNativeMessagingMV3Test
- : public RuntimeHooksDelegateTest {
- public:
- RuntimeHooksDelegateNativeMessagingMV3Test() = default;
- ~RuntimeHooksDelegateNativeMessagingMV3Test() override = default;
- scoped_refptr<const Extension> BuildExtension() override {
- return ExtensionBuilder("foo")
- .SetManifestKey("manifest_version", 3)
- .AddPermission("nativeMessaging")
- .Build();
- }
- };
- TEST_F(RuntimeHooksDelegateNativeMessagingMV3Test, SendNativeMessage) {
- v8::HandleScope handle_scope(isolate());
- v8::Local<v8::Context> context = MainContext();
- SendMessageTester tester(ipc_message_sender(), script_context(), 0,
- "runtime");
- {
- // Calling sendNativeMessage without the callback should result in a promise
- // returned.
- v8::Local<v8::Value> result = tester.TestSendNativeMessage(
- "'native_app', {hi:'bye'}", R"({"hi":"bye"})", "native_app");
- v8::Local<v8::Promise> promise;
- ASSERT_TRUE(GetValueAs(result, &promise));
- EXPECT_EQ(v8::Promise::kPending, promise->State());
- }
- {
- // Calling sendNativeMessage with a callback should result in no value
- // returned.
- v8::Local<v8::Value> result = tester.TestSendNativeMessage(
- "'another_native_app', {alpha: 2}, function() {}", R"({"alpha":2})",
- "another_native_app");
- EXPECT_TRUE(result->IsUndefined());
- }
- auto send_native_message_error = [context](base::StringPiece args) {
- CallAPIAndExpectError(context, "sendNativeMessage", args);
- };
- // Invoking the API with incorrect parameters should emit errors.
- send_native_message_error("{data: 'hi'}, function() {}");
- send_native_message_error(
- "'native_app', 'some message', {includeTlsChannelId: true}");
- }
- } // namespace extensions
|