runtime_hooks_delegate_unittest.cc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. // Copyright 2017 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "extensions/renderer/runtime_hooks_delegate.h"
  5. #include <memory>
  6. #include "base/strings/stringprintf.h"
  7. #include "components/crx_file/id_util.h"
  8. #include "content/public/common/child_process_host.h"
  9. #include "extensions/common/api/messaging/serialization_format.h"
  10. #include "extensions/common/extension.h"
  11. #include "extensions/common/extension_builder.h"
  12. #include "extensions/common/extension_messages.h"
  13. #include "extensions/common/value_builder.h"
  14. #include "extensions/renderer/bindings/api_binding_test_util.h"
  15. #include "extensions/renderer/message_target.h"
  16. #include "extensions/renderer/native_extension_bindings_system.h"
  17. #include "extensions/renderer/native_extension_bindings_system_test_base.h"
  18. #include "extensions/renderer/native_renderer_messaging_service.h"
  19. #include "extensions/renderer/script_context.h"
  20. #include "extensions/renderer/script_context_set.h"
  21. #include "extensions/renderer/send_message_tester.h"
  22. namespace extensions {
  23. namespace {
  24. void CallAPIAndExpectError(v8::Local<v8::Context> context,
  25. base::StringPiece method_name,
  26. base::StringPiece args) {
  27. SCOPED_TRACE(base::StringPrintf("Args: `%s`", args.data()));
  28. constexpr char kTemplate[] = "(function() { chrome.runtime.%s(%s); })";
  29. v8::Isolate* isolate = context->GetIsolate();
  30. // Just verify some error was thrown. Expecting the exact error message
  31. // tends to rely too much on our argument spec code, which is tested
  32. // separately.
  33. v8::Local<v8::Function> function = FunctionFromString(
  34. context, base::StringPrintf(kTemplate, method_name.data(), args.data()));
  35. v8::TryCatch try_catch(isolate);
  36. v8::MaybeLocal<v8::Value> result =
  37. function->Call(context, v8::Undefined(isolate), 0, nullptr);
  38. EXPECT_TRUE(result.IsEmpty());
  39. EXPECT_TRUE(try_catch.HasCaught());
  40. }
  41. } // namespace
  42. class RuntimeHooksDelegateTest : public NativeExtensionBindingsSystemUnittest {
  43. public:
  44. RuntimeHooksDelegateTest() {}
  45. RuntimeHooksDelegateTest(const RuntimeHooksDelegateTest&) = delete;
  46. RuntimeHooksDelegateTest& operator=(const RuntimeHooksDelegateTest&) = delete;
  47. ~RuntimeHooksDelegateTest() override {}
  48. // NativeExtensionBindingsSystemUnittest:
  49. void SetUp() override {
  50. NativeExtensionBindingsSystemUnittest::SetUp();
  51. messaging_service_ =
  52. std::make_unique<NativeRendererMessagingService>(bindings_system());
  53. bindings_system()->api_system()->GetHooksForAPI("runtime")->SetDelegate(
  54. std::make_unique<RuntimeHooksDelegate>(messaging_service_.get()));
  55. extension_ = BuildExtension();
  56. RegisterExtension(extension_);
  57. v8::HandleScope handle_scope(isolate());
  58. v8::Local<v8::Context> context = MainContext();
  59. script_context_ = CreateScriptContext(context, extension_.get(),
  60. Feature::BLESSED_EXTENSION_CONTEXT);
  61. script_context_->set_url(extension_->url());
  62. bindings_system()->UpdateBindingsForContext(script_context_);
  63. }
  64. void TearDown() override {
  65. script_context_ = nullptr;
  66. extension_ = nullptr;
  67. messaging_service_.reset();
  68. NativeExtensionBindingsSystemUnittest::TearDown();
  69. }
  70. bool UseStrictIPCMessageSender() override { return true; }
  71. virtual scoped_refptr<const Extension> BuildExtension() {
  72. return ExtensionBuilder("foo").Build();
  73. }
  74. NativeRendererMessagingService* messaging_service() {
  75. return messaging_service_.get();
  76. }
  77. ScriptContext* script_context() { return script_context_; }
  78. const Extension* extension() { return extension_.get(); }
  79. private:
  80. std::unique_ptr<NativeRendererMessagingService> messaging_service_;
  81. ScriptContext* script_context_ = nullptr;
  82. scoped_refptr<const Extension> extension_;
  83. };
  84. TEST_F(RuntimeHooksDelegateTest, RuntimeId) {
  85. v8::HandleScope handle_scope(isolate());
  86. v8::Local<v8::Context> context = MainContext();
  87. {
  88. scoped_refptr<const Extension> connectable_extension =
  89. ExtensionBuilder("connectable")
  90. .SetManifestPath({"externally_connectable", "matches"},
  91. ListBuilder().Append("*://example.com/*").Build())
  92. .Build();
  93. RegisterExtension(connectable_extension);
  94. }
  95. auto get_id = [](v8::Local<v8::Context> context) {
  96. v8::Local<v8::Function> get_id = FunctionFromString(
  97. context, "(function() { return chrome.runtime.id; })");
  98. return RunFunction(get_id, context, 0, nullptr);
  99. };
  100. {
  101. v8::Local<v8::Value> id = get_id(context);
  102. EXPECT_EQ(extension()->id(), gin::V8ToString(isolate(), id));
  103. }
  104. {
  105. // In order for chrome.runtime to be available to web pages, we need to have
  106. // an associated connectable extension, so pretend to be example.com.
  107. v8::Local<v8::Context> web_context = AddContext();
  108. ScriptContext* script_context =
  109. CreateScriptContext(web_context, nullptr, Feature::WEB_PAGE_CONTEXT);
  110. script_context->set_url(GURL("http://example.com"));
  111. bindings_system()->UpdateBindingsForContext(script_context);
  112. v8::Local<v8::Value> id = get_id(web_context);
  113. EXPECT_TRUE(id->IsUndefined());
  114. }
  115. }
  116. TEST_F(RuntimeHooksDelegateTest, GetManifest) {
  117. v8::HandleScope handle_scope(isolate());
  118. v8::Local<v8::Context> context = MainContext();
  119. v8::Local<v8::Function> get_manifest = FunctionFromString(
  120. context, "(function() { return chrome.runtime.getManifest(); })");
  121. v8::Local<v8::Value> manifest =
  122. RunFunction(get_manifest, context, 0, nullptr);
  123. ASSERT_FALSE(manifest.IsEmpty());
  124. ASSERT_TRUE(manifest->IsObject());
  125. EXPECT_EQ(ValueToString(*extension()->manifest()->value()),
  126. V8ToString(manifest, context));
  127. }
  128. TEST_F(RuntimeHooksDelegateTest, GetURL) {
  129. v8::HandleScope handle_scope(isolate());
  130. v8::Local<v8::Context> context = MainContext();
  131. auto get_url = [this, context](const char* args, const GURL& expected_url) {
  132. SCOPED_TRACE(base::StringPrintf("Args: `%s`", args));
  133. constexpr char kGetUrlTemplate[] =
  134. "(function() { return chrome.runtime.getURL(%s); })";
  135. v8::Local<v8::Function> get_url =
  136. FunctionFromString(context, base::StringPrintf(kGetUrlTemplate, args));
  137. v8::Local<v8::Value> url = RunFunction(get_url, context, 0, nullptr);
  138. ASSERT_FALSE(url.IsEmpty());
  139. ASSERT_TRUE(url->IsString());
  140. EXPECT_EQ(expected_url.spec(), gin::V8ToString(isolate(), url));
  141. };
  142. get_url("''", extension()->url());
  143. get_url("'foo'", extension()->GetResourceURL("foo"));
  144. get_url("'/foo'", extension()->GetResourceURL("foo"));
  145. get_url("'https://www.google.com'",
  146. GURL(extension()->url().spec() + "https://www.google.com"));
  147. }
  148. TEST_F(RuntimeHooksDelegateTest, Connect) {
  149. v8::HandleScope handle_scope(isolate());
  150. v8::Local<v8::Context> context = MainContext();
  151. {
  152. // Sanity check: connectNative is unavailable (missing permission).
  153. v8::Local<v8::Value> connect_native =
  154. V8ValueFromScriptSource(context, "chrome.runtime.connectNative");
  155. ASSERT_FALSE(connect_native.IsEmpty());
  156. EXPECT_TRUE(connect_native->IsUndefined());
  157. }
  158. SendMessageTester tester(ipc_message_sender(), script_context(), 0,
  159. "runtime");
  160. MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
  161. tester.TestConnect("", "", self_target);
  162. tester.TestConnect("{name: 'channel'}", "channel", self_target);
  163. tester.TestConnect("{includeTlsChannelId: true}", "", self_target);
  164. tester.TestConnect("{includeTlsChannelId: true, name: 'channel'}", "channel",
  165. self_target);
  166. std::string other_id = crx_file::id_util::GenerateId("other");
  167. MessageTarget other_target = MessageTarget::ForExtension(other_id);
  168. tester.TestConnect(base::StringPrintf("'%s'", other_id.c_str()), "",
  169. other_target);
  170. tester.TestConnect(
  171. base::StringPrintf("'%s', {name: 'channel'}", other_id.c_str()),
  172. "channel", other_target);
  173. }
  174. // Tests the end-to-end (renderer) flow for a call to runtime.sendMessage
  175. // from the call in JS to the expected IPCs. The intricacies of sendMessage
  176. // are also tested more in the renderer messaging service unittests.
  177. TEST_F(RuntimeHooksDelegateTest, SendMessage) {
  178. v8::HandleScope handle_scope(isolate());
  179. v8::Local<v8::Context> context = MainContext();
  180. {
  181. // Sanity check: sendNativeMessage is unavailable (missing permission).
  182. v8::Local<v8::Value> send_native_message =
  183. V8ValueFromScriptSource(context, "chrome.runtime.sendNativeMessage");
  184. ASSERT_FALSE(send_native_message.IsEmpty());
  185. EXPECT_TRUE(send_native_message->IsUndefined());
  186. }
  187. SendMessageTester tester(ipc_message_sender(), script_context(), 0,
  188. "runtime");
  189. MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
  190. tester.TestSendMessage("''", R"("")", self_target, SendMessageTester::CLOSED);
  191. constexpr char kStandardMessage[] = R"({"data":"hello"})";
  192. tester.TestSendMessage("{data: 'hello'}", kStandardMessage, self_target,
  193. SendMessageTester::CLOSED);
  194. tester.TestSendMessage("{data: 'hello'}, function() {}", kStandardMessage,
  195. self_target, SendMessageTester::OPEN);
  196. tester.TestSendMessage("{data: 'hello'}, {includeTlsChannelId: true}",
  197. kStandardMessage, self_target,
  198. SendMessageTester::CLOSED);
  199. tester.TestSendMessage(
  200. "{data: 'hello'}, {includeTlsChannelId: true}, function() {}",
  201. kStandardMessage, self_target, SendMessageTester::OPEN);
  202. std::string other_id_str = crx_file::id_util::GenerateId("other");
  203. const char* other_id = other_id_str.c_str(); // For easy StringPrintf()ing.
  204. MessageTarget other_target = MessageTarget::ForExtension(other_id_str);
  205. tester.TestSendMessage(base::StringPrintf("'%s', {data: 'hello'}", other_id),
  206. kStandardMessage, other_target,
  207. SendMessageTester::CLOSED);
  208. tester.TestSendMessage(
  209. base::StringPrintf("'%s', {data: 'hello'}, function() {}", other_id),
  210. kStandardMessage, other_target, SendMessageTester::OPEN);
  211. tester.TestSendMessage(base::StringPrintf("'%s', 'string message'", other_id),
  212. R"("string message")", other_target,
  213. SendMessageTester::CLOSED);
  214. // The sender could omit the ID by passing null or undefined explicitly.
  215. // Regression tests for https://crbug.com/828664.
  216. tester.TestSendMessage("null, {data: 'hello'}, function() {}",
  217. kStandardMessage, self_target,
  218. SendMessageTester::OPEN);
  219. tester.TestSendMessage("null, 'test', function() {}", R"("test")",
  220. self_target, SendMessageTester::OPEN);
  221. tester.TestSendMessage("null, 'test'", R"("test")", self_target,
  222. SendMessageTester::CLOSED);
  223. tester.TestSendMessage("undefined, 'test', function() {}", R"("test")",
  224. self_target, SendMessageTester::OPEN);
  225. // Funny case. The only required argument is `message`, which can be any type.
  226. // This means that if an extension provides a <string, object> pair for the
  227. // first three arguments, it could apply to either the target id and the
  228. // message or to the message and the connect options.
  229. // In this case, we *always* pick the arguments as target id and message,
  230. // because they were the first options (connectOptions was added later), and
  231. // because connectOptions is pretty rarely used.
  232. // TODO(devlin): This is the determination JS has always made, but we could be
  233. // a bit more intelligent about it. We could examine the string to see if it's
  234. // a valid extension id as well as looking at the properties on the object.
  235. // But probably not worth it at this time.
  236. tester.TestSendMessage(
  237. base::StringPrintf("'%s', {includeTlsChannelId: true}", other_id),
  238. R"({"includeTlsChannelId":true})", other_target,
  239. SendMessageTester::CLOSED);
  240. tester.TestSendMessage(
  241. base::StringPrintf("'%s', {includeTlsChannelId: true}, function() {}",
  242. other_id),
  243. R"({"includeTlsChannelId":true})", other_target, SendMessageTester::OPEN);
  244. }
  245. // Test that some incorrect invocations of sendMessage() throw errors.
  246. TEST_F(RuntimeHooksDelegateTest, SendMessageErrors) {
  247. v8::HandleScope handle_scope(isolate());
  248. v8::Local<v8::Context> context = MainContext();
  249. auto send_message = [context](base::StringPiece args) {
  250. CallAPIAndExpectError(context, "sendMessage", args);
  251. };
  252. send_message("{data: 'hi'}, {unknownProp: true}");
  253. send_message("'some id', 'some message', 'some other string'");
  254. send_message("'some id', 'some message', {}, {}");
  255. }
  256. TEST_F(RuntimeHooksDelegateTest, ConnectWithTrickyOptions) {
  257. v8::HandleScope handle_scope(isolate());
  258. v8::Local<v8::Context> context = MainContext();
  259. SendMessageTester tester(ipc_message_sender(), script_context(), 0,
  260. "runtime");
  261. MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
  262. {
  263. // Even though we parse the message options separately, we do a conversion
  264. // of the object passed into the API. This means that something subtle like
  265. // this, which throws on the second access of a property, shouldn't trip us
  266. // up.
  267. constexpr char kTrickyConnectOptions[] =
  268. R"({
  269. get name() {
  270. if (this.checkedOnce)
  271. throw new Error('tricked!');
  272. this.checkedOnce = true;
  273. return 'foo';
  274. }
  275. })";
  276. tester.TestConnect(kTrickyConnectOptions, "foo", self_target);
  277. }
  278. {
  279. // A different form of trickiness: the options object doesn't have the
  280. // name key (which is acceptable, since its optional), but
  281. // any attempt to access the key on an object without a value for it results
  282. // in an error. Our argument parsing code should protect us from this.
  283. constexpr const char kMessWithObjectPrototype[] =
  284. R"((function() {
  285. Object.defineProperty(
  286. Object.prototype, 'name',
  287. { get() { throw new Error('tricked!'); } });
  288. }))";
  289. v8::Local<v8::Function> mess_with_proto =
  290. FunctionFromString(context, kMessWithObjectPrototype);
  291. RunFunction(mess_with_proto, context, 0, nullptr);
  292. tester.TestConnect("{}", "", self_target);
  293. }
  294. }
  295. class RuntimeHooksDelegateNativeMessagingTest
  296. : public RuntimeHooksDelegateTest {
  297. public:
  298. RuntimeHooksDelegateNativeMessagingTest() {}
  299. ~RuntimeHooksDelegateNativeMessagingTest() override {}
  300. scoped_refptr<const Extension> BuildExtension() override {
  301. return ExtensionBuilder("foo").AddPermission("nativeMessaging").Build();
  302. }
  303. };
  304. TEST_F(RuntimeHooksDelegateNativeMessagingTest, ConnectNative) {
  305. v8::HandleScope handle_scope(isolate());
  306. v8::Local<v8::Context> context = MainContext();
  307. int next_context_port_id = 0;
  308. auto run_connect_native = [this, context, &next_context_port_id](
  309. const std::string& args,
  310. const std::string& expected_app_name) {
  311. // connectNative() doesn't name channels.
  312. const std::string kEmptyExpectedChannel;
  313. SCOPED_TRACE(base::StringPrintf("Args: '%s'", args.c_str()));
  314. constexpr char kAddPortTemplate[] =
  315. "(function() { return chrome.runtime.connectNative(%s); })";
  316. PortId expected_port_id(script_context()->context_id(),
  317. next_context_port_id++, true,
  318. SerializationFormat::kJson);
  319. MessageTarget expected_target(
  320. MessageTarget::ForNativeApp(expected_app_name));
  321. EXPECT_CALL(*ipc_message_sender(),
  322. SendOpenMessageChannel(script_context(), expected_port_id,
  323. expected_target, kEmptyExpectedChannel));
  324. v8::Local<v8::Function> add_port = FunctionFromString(
  325. context, base::StringPrintf(kAddPortTemplate, args.c_str()));
  326. v8::Local<v8::Value> port = RunFunction(add_port, context, 0, nullptr);
  327. ::testing::Mock::VerifyAndClearExpectations(ipc_message_sender());
  328. ASSERT_FALSE(port.IsEmpty());
  329. ASSERT_TRUE(port->IsObject());
  330. };
  331. run_connect_native("'native_app'", "native_app");
  332. run_connect_native("'some_other_native_app'", "some_other_native_app");
  333. auto connect_native_error = [context](base::StringPiece args) {
  334. CallAPIAndExpectError(context, "connectNative", args);
  335. };
  336. connect_native_error("'native_app', {name: 'name'}");
  337. }
  338. TEST_F(RuntimeHooksDelegateNativeMessagingTest, SendNativeMessage) {
  339. v8::HandleScope handle_scope(isolate());
  340. v8::Local<v8::Context> context = MainContext();
  341. SendMessageTester tester(ipc_message_sender(), script_context(), 0,
  342. "runtime");
  343. tester.TestSendNativeMessage("'native_app', {hi:'bye'}", R"({"hi":"bye"})",
  344. "native_app");
  345. tester.TestSendNativeMessage(
  346. "'another_native_app', {alpha: 2}, function() {}", R"({"alpha":2})",
  347. "another_native_app");
  348. auto send_native_message_error = [context](base::StringPiece args) {
  349. CallAPIAndExpectError(context, "sendNativeMessage", args);
  350. };
  351. send_native_message_error("{data: 'hi'}, function() {}");
  352. send_native_message_error(
  353. "'native_app', 'some message', {includeTlsChannelId: true}");
  354. }
  355. class RuntimeHooksDelegateMV3Test : public RuntimeHooksDelegateTest {
  356. public:
  357. RuntimeHooksDelegateMV3Test() = default;
  358. ~RuntimeHooksDelegateMV3Test() override = default;
  359. scoped_refptr<const Extension> BuildExtension() override {
  360. return ExtensionBuilder("foo")
  361. .SetManifestKey("manifest_version", 3)
  362. .Build();
  363. }
  364. };
  365. TEST_F(RuntimeHooksDelegateMV3Test, SendMessageUsingPromise) {
  366. v8::HandleScope handle_scope(isolate());
  367. SendMessageTester tester(ipc_message_sender(), script_context(), 0,
  368. "runtime");
  369. // The port remains open here after the call because in MV3 we return a
  370. // promise if the callback parameter is omitted, so we can't use the presence/
  371. // lack of the callback to determine if the caller is/isn't going to handle
  372. // the response.
  373. MessageTarget self_target = MessageTarget::ForExtension(extension()->id());
  374. tester.TestSendMessage("''", R"("")", self_target, SendMessageTester::OPEN);
  375. constexpr char kStandardMessage[] = R"({"data":"hello"})";
  376. {
  377. // Calling sendMessage with a callback should result in no value returned.
  378. v8::Local<v8::Value> result = tester.TestSendMessage(
  379. "{data: 'hello'}, function() {}", kStandardMessage, self_target,
  380. SendMessageTester::OPEN);
  381. EXPECT_TRUE(result->IsUndefined());
  382. }
  383. {
  384. // Calling sendMessage without the callback should result in a promise
  385. // returned.
  386. v8::Local<v8::Value> result =
  387. tester.TestSendMessage("{data: 'hello'}", kStandardMessage, self_target,
  388. SendMessageTester::OPEN);
  389. v8::Local<v8::Promise> promise;
  390. ASSERT_TRUE(GetValueAs(result, &promise));
  391. EXPECT_EQ(v8::Promise::kPending, promise->State());
  392. }
  393. }
  394. class RuntimeHooksDelegateNativeMessagingMV3Test
  395. : public RuntimeHooksDelegateTest {
  396. public:
  397. RuntimeHooksDelegateNativeMessagingMV3Test() = default;
  398. ~RuntimeHooksDelegateNativeMessagingMV3Test() override = default;
  399. scoped_refptr<const Extension> BuildExtension() override {
  400. return ExtensionBuilder("foo")
  401. .SetManifestKey("manifest_version", 3)
  402. .AddPermission("nativeMessaging")
  403. .Build();
  404. }
  405. };
  406. TEST_F(RuntimeHooksDelegateNativeMessagingMV3Test, SendNativeMessage) {
  407. v8::HandleScope handle_scope(isolate());
  408. v8::Local<v8::Context> context = MainContext();
  409. SendMessageTester tester(ipc_message_sender(), script_context(), 0,
  410. "runtime");
  411. {
  412. // Calling sendNativeMessage without the callback should result in a promise
  413. // returned.
  414. v8::Local<v8::Value> result = tester.TestSendNativeMessage(
  415. "'native_app', {hi:'bye'}", R"({"hi":"bye"})", "native_app");
  416. v8::Local<v8::Promise> promise;
  417. ASSERT_TRUE(GetValueAs(result, &promise));
  418. EXPECT_EQ(v8::Promise::kPending, promise->State());
  419. }
  420. {
  421. // Calling sendNativeMessage with a callback should result in no value
  422. // returned.
  423. v8::Local<v8::Value> result = tester.TestSendNativeMessage(
  424. "'another_native_app', {alpha: 2}, function() {}", R"({"alpha":2})",
  425. "another_native_app");
  426. EXPECT_TRUE(result->IsUndefined());
  427. }
  428. auto send_native_message_error = [context](base::StringPiece args) {
  429. CallAPIAndExpectError(context, "sendNativeMessage", args);
  430. };
  431. // Invoking the API with incorrect parameters should emit errors.
  432. send_native_message_error("{data: 'hi'}, function() {}");
  433. send_native_message_error(
  434. "'native_app', 'some message', {includeTlsChannelId: true}");
  435. }
  436. } // namespace extensions