api_bindings_system_unittest.cc 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. // Copyright 2016 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/bindings/api_bindings_system_unittest.h"
  5. #include "base/bind.h"
  6. #include "base/callback_helpers.h"
  7. #include "base/containers/contains.h"
  8. #include "base/strings/stringprintf.h"
  9. #include "base/values.h"
  10. #include "extensions/common/mojom/event_dispatcher.mojom.h"
  11. #include "extensions/renderer/bindings/api_binding.h"
  12. #include "extensions/renderer/bindings/api_binding_hooks.h"
  13. #include "extensions/renderer/bindings/api_binding_hooks_test_delegate.h"
  14. #include "extensions/renderer/bindings/api_binding_test_util.h"
  15. #include "extensions/renderer/bindings/api_binding_types.h"
  16. #include "extensions/renderer/bindings/api_bindings_system.h"
  17. #include "extensions/renderer/bindings/api_invocation_errors.h"
  18. #include "extensions/renderer/bindings/test_interaction_provider.h"
  19. #include "gin/arguments.h"
  20. #include "gin/converter.h"
  21. #include "gin/try_catch.h"
  22. #include "testing/gmock/include/gmock/gmock.h"
  23. namespace extensions {
  24. namespace {
  25. // Fake API for testing.
  26. const char kAlphaAPIName[] = "alpha";
  27. const char kAlphaAPISpec[] = R"(
  28. {
  29. "types": [{
  30. "id": "alpha.objRef",
  31. "type": "object",
  32. "properties": {
  33. "prop1": {"type": "string"},
  34. "prop2": {"type": "integer", "optional": true}
  35. }
  36. }, {
  37. "id": "alpha.enumRef",
  38. "type": "string",
  39. "enum": ["cat", "dog"]
  40. }],
  41. "functions": [{
  42. "name": "functionWithCallback",
  43. "parameters": [{
  44. "name": "str",
  45. "type": "string"
  46. }],
  47. "returns_async": {
  48. "name": "callback",
  49. "parameters": [{"name": "strResult", "type": "string"}]
  50. }
  51. }, {
  52. "name": "functionWithRefAndCallback",
  53. "parameters": [{
  54. "name": "ref",
  55. "$ref": "alpha.objRef"
  56. }],
  57. "returns_async": {
  58. "name": "callback",
  59. "parameters": []
  60. }
  61. }, {
  62. "name": "functionWithEnum",
  63. "parameters": [{"name": "e", "$ref": "alpha.enumRef"}]
  64. }],
  65. "events": [{
  66. "name": "alphaEvent",
  67. "parameters": [{
  68. "name": "eventArg",
  69. "type": "object",
  70. "properties": { "key": {"type": "integer"} }
  71. }]
  72. }, {
  73. "name": "alphaOtherEvent"
  74. }]
  75. })";
  76. // Another fake API for testing.
  77. const char kBetaAPIName[] = "beta";
  78. const char kBetaAPISpec[] = R"(
  79. {
  80. "functions": [{
  81. "name": "simpleFunc",
  82. "parameters": [{"name": "int", "type": "integer"}]
  83. }]
  84. })";
  85. const char kGammaAPIName[] = "gamma";
  86. const char kGammaAPISpec[] = R"(
  87. {
  88. "functions": [{
  89. "name": "functionWithExternalRef",
  90. "parameters": [{ "name": "someRef", "$ref": "alpha.objRef" }]
  91. }]
  92. })";
  93. // JS strings used for registering custom callbacks for testing.
  94. const char kCustomCallbackHook[] = R"(
  95. (function(hooks) {
  96. hooks.setCustomCallback(
  97. 'functionWithCallback', (originalCallback,
  98. firstResult, secondResult) => {
  99. this.results = [firstResult, secondResult];
  100. originalCallback(secondResult);
  101. });
  102. }))";
  103. const char kCustomCallbackThrowHook[] = R"(
  104. (function(hooks) {
  105. hooks.setCustomCallback(
  106. 'functionWithCallback', (name, originalCallback,
  107. firstResult, secondResult) => {
  108. throw new Error('Custom callback threw');
  109. });
  110. }))";
  111. bool AllowAllAPIs(v8::Local<v8::Context> context, const std::string& name) {
  112. return true;
  113. }
  114. bool AllowPromises(v8::Local<v8::Context> context) {
  115. return true;
  116. }
  117. } // namespace
  118. APIBindingsSystemTest::APIBindingsSystemTest() {}
  119. APIBindingsSystemTest::~APIBindingsSystemTest() = default;
  120. void APIBindingsSystemTest::SetUp() {
  121. APIBindingTest::SetUp();
  122. // Create the fake API schemas.
  123. for (const auto& api : GetAPIs()) {
  124. std::unique_ptr<base::DictionaryValue> api_schema =
  125. DeprecatedDictionaryValueFromString(api.spec);
  126. ASSERT_TRUE(api_schema);
  127. api_schemas_[api.name] = std::move(api_schema);
  128. }
  129. binding::AddConsoleError add_console_error(base::BindRepeating(
  130. &APIBindingsSystemTest::AddConsoleError, base::Unretained(this)));
  131. auto get_context_owner = [](v8::Local<v8::Context>) {
  132. return std::string("context");
  133. };
  134. bindings_system_ = std::make_unique<APIBindingsSystem>(
  135. base::BindRepeating(&APIBindingsSystemTest::GetAPISchema,
  136. base::Unretained(this)),
  137. base::BindRepeating(&AllowAllAPIs), base::BindRepeating(&AllowPromises),
  138. base::BindRepeating(&APIBindingsSystemTest::OnAPIRequest,
  139. base::Unretained(this)),
  140. std::make_unique<TestInteractionProvider>(),
  141. base::BindRepeating(&APIBindingsSystemTest::OnEventListenersChanged,
  142. base::Unretained(this)),
  143. base::BindRepeating(get_context_owner), base::DoNothing(),
  144. add_console_error,
  145. APILastError(
  146. base::BindRepeating(&APIBindingsSystemTest::GetLastErrorParent,
  147. base::Unretained(this)),
  148. add_console_error));
  149. }
  150. void APIBindingsSystemTest::TearDown() {
  151. // Dispose all contexts now so that we call WillReleaseContext().
  152. DisposeAllContexts();
  153. bindings_system_.reset();
  154. APIBindingTest::TearDown();
  155. }
  156. void APIBindingsSystemTest::OnWillDisposeContext(
  157. v8::Local<v8::Context> context) {
  158. bindings_system_->WillReleaseContext(context);
  159. }
  160. std::vector<APIBindingsSystemTest::FakeSpec> APIBindingsSystemTest::GetAPIs() {
  161. return {
  162. {kAlphaAPIName, kAlphaAPISpec},
  163. {kBetaAPIName, kBetaAPISpec},
  164. {kGammaAPIName, kGammaAPISpec},
  165. };
  166. }
  167. v8::Local<v8::Object> APIBindingsSystemTest::GetLastErrorParent(
  168. v8::Local<v8::Context> context,
  169. v8::Local<v8::Object>* secondary_parent) {
  170. return v8::Local<v8::Object>();
  171. }
  172. void APIBindingsSystemTest::AddConsoleError(v8::Local<v8::Context> context,
  173. const std::string& error) {
  174. console_errors_.push_back(error);
  175. }
  176. const base::DictionaryValue& APIBindingsSystemTest::GetAPISchema(
  177. const std::string& api_name) {
  178. EXPECT_TRUE(base::Contains(api_schemas_, api_name));
  179. return *api_schemas_[api_name];
  180. }
  181. void APIBindingsSystemTest::OnAPIRequest(
  182. std::unique_ptr<APIRequestHandler::Request> request,
  183. v8::Local<v8::Context> context) {
  184. ASSERT_FALSE(last_request_);
  185. last_request_ = std::move(request);
  186. }
  187. void APIBindingsSystemTest::OnEventListenersChanged(
  188. const std::string& event_name,
  189. binding::EventListenersChanged changed,
  190. const base::DictionaryValue* filter,
  191. bool was_manual,
  192. v8::Local<v8::Context> context) {}
  193. void APIBindingsSystemTest::ValidateLastRequest(
  194. const std::string& expected_name,
  195. const std::string& expected_arguments) {
  196. ASSERT_TRUE(last_request());
  197. // Note that even if no arguments are provided by the API call, we should
  198. // have an empty list.
  199. ASSERT_TRUE(last_request()->arguments_list);
  200. EXPECT_EQ(expected_name, last_request()->method_name);
  201. EXPECT_EQ(ReplaceSingleQuotes(expected_arguments),
  202. ValueToString(*last_request()->arguments_list));
  203. }
  204. v8::Local<v8::Value> APIBindingsSystemTest::CallFunctionOnObject(
  205. v8::Local<v8::Context> context,
  206. v8::Local<v8::Object> object,
  207. const std::string& script_source) {
  208. std::string wrapped_script_source =
  209. base::StringPrintf("(function(obj) { %s })", script_source.c_str());
  210. v8::Local<v8::Function> func =
  211. FunctionFromString(context, wrapped_script_source);
  212. // Use ADD_FAILURE() to avoid messing up the return type with ASSERT.
  213. if (func.IsEmpty()) {
  214. ADD_FAILURE() << script_source;
  215. return v8::Local<v8::Value>();
  216. }
  217. v8::Local<v8::Value> argv[] = {object};
  218. return RunFunction(func, context, 1, argv);
  219. }
  220. // Tests API object initialization, calling a method on the supplied APIs, and
  221. // triggering the callback for the request.
  222. TEST_F(APIBindingsSystemTest, TestInitializationAndCallbacks) {
  223. v8::HandleScope handle_scope(isolate());
  224. v8::Local<v8::Context> context = MainContext();
  225. v8::Local<v8::Object> alpha_api =
  226. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
  227. ASSERT_FALSE(alpha_api.IsEmpty());
  228. v8::Local<v8::Object> beta_api =
  229. bindings_system()->CreateAPIInstance(kBetaAPIName, context, nullptr);
  230. ASSERT_FALSE(beta_api.IsEmpty());
  231. {
  232. // Test a simple call -> response.
  233. const char kTestCall[] = R"(
  234. obj.functionWithCallback('foo', function() {
  235. this.callbackArguments = Array.from(arguments);
  236. });)";
  237. CallFunctionOnObject(context, alpha_api, kTestCall);
  238. ValidateLastRequest("alpha.functionWithCallback", "['foo']");
  239. const char kResponseArgsJson[] = R"(["response"])";
  240. bindings_system()->CompleteRequest(last_request()->request_id,
  241. ListValueFromString(kResponseArgsJson),
  242. std::string());
  243. EXPECT_EQ(kResponseArgsJson,
  244. GetStringPropertyFromObject(context->Global(), context,
  245. "callbackArguments"));
  246. reset_last_request();
  247. }
  248. {
  249. // Test a call with references -> response.
  250. const char kTestCall[] = R"(
  251. obj.functionWithRefAndCallback({prop1: 'alpha', prop2: 42},
  252. function() {
  253. this.callbackArguments = Array.from(arguments);
  254. });)";
  255. CallFunctionOnObject(context, alpha_api, kTestCall);
  256. ValidateLastRequest("alpha.functionWithRefAndCallback",
  257. "[{'prop1':'alpha','prop2':42}]");
  258. bindings_system()->CompleteRequest(last_request()->request_id,
  259. base::Value::List(), std::string());
  260. EXPECT_EQ("[]", GetStringPropertyFromObject(context->Global(), context,
  261. "callbackArguments"));
  262. reset_last_request();
  263. }
  264. {
  265. // Test an invalid invocation -> throwing error.
  266. const char kTestCall[] =
  267. "(function(obj) { obj.functionWithEnum('mouse') })";
  268. v8::Local<v8::Function> function = FunctionFromString(context, kTestCall);
  269. v8::Local<v8::Value> args[] = {alpha_api};
  270. RunFunctionAndExpectError(
  271. function, context, std::size(args), args,
  272. "Uncaught TypeError: " +
  273. api_errors::InvocationError(
  274. "alpha.functionWithEnum", "alpha.enumRef e",
  275. api_errors::ArgumentError(
  276. "e", api_errors::InvalidEnumValue({"cat", "dog"}))));
  277. EXPECT_FALSE(last_request());
  278. reset_last_request(); // Just to not pollute future results.
  279. }
  280. {
  281. // Test an event registration -> event occurrence.
  282. const char kTestCall[] = R"(
  283. obj.alphaEvent.addListener(function() {
  284. this.eventArguments = Array.from(arguments);
  285. });)";
  286. CallFunctionOnObject(context, alpha_api, kTestCall);
  287. const char kResponseArgsJson[] = R"([{"key":42}])";
  288. base::Value::List expected_args = ListValueFromString(kResponseArgsJson);
  289. bindings_system()->FireEventInContext("alpha.alphaEvent", context,
  290. expected_args, nullptr);
  291. EXPECT_EQ(kResponseArgsJson,
  292. GetStringPropertyFromObject(context->Global(), context,
  293. "eventArguments"));
  294. }
  295. {
  296. // Test a call -> response on the second API.
  297. const char kTestCall[] = "obj.simpleFunc(2)";
  298. CallFunctionOnObject(context, beta_api, kTestCall);
  299. ValidateLastRequest("beta.simpleFunc", "[2]");
  300. reset_last_request();
  301. }
  302. }
  303. // Tests adding a custom hook to an API.
  304. TEST_F(APIBindingsSystemTest, TestCustomHooks) {
  305. v8::HandleScope handle_scope(isolate());
  306. v8::Local<v8::Context> context = MainContext();
  307. bool did_call = false;
  308. auto hook = [](bool* did_call, const APISignature* signature,
  309. v8::Local<v8::Context> context,
  310. std::vector<v8::Local<v8::Value>>* arguments,
  311. const APITypeReferenceMap& type_refs) {
  312. *did_call = true;
  313. APIBindingHooks::RequestResult result(
  314. APIBindingHooks::RequestResult::HANDLED);
  315. if (arguments->size() != 2) { // ASSERT* messes with the return type.
  316. EXPECT_EQ(2u, arguments->size());
  317. return result;
  318. }
  319. std::string argument;
  320. EXPECT_EQ("foo", gin::V8ToString(context->GetIsolate(), arguments->at(0)));
  321. if (!arguments->at(1)->IsFunction()) {
  322. EXPECT_TRUE(arguments->at(1)->IsFunction());
  323. return result;
  324. }
  325. v8::Local<v8::String> response =
  326. gin::StringToV8(context->GetIsolate(), "bar");
  327. v8::Local<v8::Value> response_args[] = {response};
  328. RunFunctionOnGlobal(arguments->at(1).As<v8::Function>(),
  329. context, 1, response_args);
  330. return result;
  331. };
  332. auto test_hooks = std::make_unique<APIBindingHooksTestDelegate>();
  333. test_hooks->AddHandler("alpha.functionWithCallback",
  334. base::BindRepeating(hook, &did_call));
  335. APIBindingHooks* binding_hooks =
  336. bindings_system()->GetHooksForAPI(kAlphaAPIName);
  337. binding_hooks->SetDelegate(std::move(test_hooks));
  338. v8::Local<v8::Object> alpha_api =
  339. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
  340. ASSERT_FALSE(alpha_api.IsEmpty());
  341. {
  342. // Test a simple call -> response.
  343. const char kTestCall[] = R"(
  344. obj.functionWithCallback('foo', function() {
  345. this.callbackArguments = Array.from(arguments);
  346. });)";
  347. CallFunctionOnObject(context, alpha_api, kTestCall);
  348. EXPECT_TRUE(did_call);
  349. EXPECT_EQ(R"(["bar"])",
  350. GetStringPropertyFromObject(context->Global(), context,
  351. "callbackArguments"));
  352. }
  353. }
  354. // Tests a call with a callback into an API using a setCustomCallback hook
  355. // works as expected.
  356. TEST_F(APIBindingsSystemTest, TestSetCustomCallback_SuccessWithCallback) {
  357. v8::HandleScope handle_scope(isolate());
  358. v8::Local<v8::Context> context = MainContext();
  359. APIBindingHooks* hooks = nullptr;
  360. v8::Local<v8::Object> alpha_api =
  361. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, &hooks);
  362. ASSERT_FALSE(alpha_api.IsEmpty());
  363. ASSERT_TRUE(hooks);
  364. v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
  365. v8::Local<v8::Function> function =
  366. FunctionFromString(context, kCustomCallbackHook);
  367. v8::Local<v8::Value> args[] = {js_hooks};
  368. RunFunctionOnGlobal(function, context, std::size(args), args);
  369. const char kTestCall[] = R"(
  370. obj.functionWithCallback('foo', function() {
  371. this.callbackArguments = Array.from(arguments);
  372. });)";
  373. CallFunctionOnObject(context, alpha_api, kTestCall);
  374. ValidateLastRequest("alpha.functionWithCallback", "['foo']");
  375. // Although this response would violate the return on the spec, since this
  376. // method has a custom callback defined it skips response validation. We
  377. // expect the custom callback will transform the return to the correct form
  378. // when calling the original callback, but this is not currently enforced or
  379. // validated.
  380. // TODO(tjudkins): Now that we use the CustomCallbackAdaptor, we could
  381. // potentially send the response validator to the custom callback adaptor
  382. // and validate the result returned from the custom callback before sending
  383. // it on to the original callback.
  384. bindings_system()->CompleteRequest(last_request()->request_id,
  385. ListValueFromString(R"(["alpha","beta"])"),
  386. std::string());
  387. EXPECT_EQ(R"(["alpha","beta"])",
  388. GetStringPropertyFromObject(context->Global(), context, "results"));
  389. EXPECT_EQ(R"(["beta"])",
  390. GetStringPropertyFromObject(context->Global(), context,
  391. "callbackArguments"));
  392. }
  393. // Tests a call with a promise into an API using a setCustomCallback hook works
  394. // as expected.
  395. TEST_F(APIBindingsSystemTest, TestSetCustomCallback_SuccessWithPromise) {
  396. v8::HandleScope handle_scope(isolate());
  397. v8::Local<v8::Context> context = MainContext();
  398. APIBindingHooks* hooks = nullptr;
  399. v8::Local<v8::Object> alpha_api =
  400. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, &hooks);
  401. ASSERT_FALSE(alpha_api.IsEmpty());
  402. ASSERT_TRUE(hooks);
  403. v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
  404. v8::Local<v8::Function> function =
  405. FunctionFromString(context, kCustomCallbackHook);
  406. v8::Local<v8::Value> args[] = {js_hooks};
  407. RunFunctionOnGlobal(function, context, std::size(args), args);
  408. const char kTestCall[] = R"(return obj.functionWithCallback('bar');)";
  409. v8::Local<v8::Value> result =
  410. CallFunctionOnObject(context, alpha_api, kTestCall);
  411. ValidateLastRequest("alpha.functionWithCallback", "['bar']");
  412. v8::Local<v8::Promise> promise;
  413. ASSERT_TRUE(GetValueAs(result, &promise));
  414. EXPECT_EQ(v8::Promise::kPending, promise->State());
  415. bindings_system()->CompleteRequest(
  416. last_request()->request_id, ListValueFromString(R"(["gamma","delta"])"),
  417. std::string());
  418. EXPECT_EQ(R"(["gamma","delta"])",
  419. GetStringPropertyFromObject(context->Global(), context, "results"));
  420. EXPECT_EQ(v8::Promise::kFulfilled, promise->State());
  421. EXPECT_EQ(R"("delta")", V8ToString(promise->Result(), context));
  422. }
  423. // Tests that an error thrown in a setCustomCallback hook while using a callback
  424. // based call works as expected.
  425. TEST_F(APIBindingsSystemTest, TestSetCustomCallback_ErrorWithCallback) {
  426. v8::HandleScope handle_scope(isolate());
  427. v8::Local<v8::Context> context = MainContext();
  428. APIBindingHooks* hooks = nullptr;
  429. v8::Local<v8::Object> alpha_api =
  430. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, &hooks);
  431. ASSERT_FALSE(alpha_api.IsEmpty());
  432. ASSERT_TRUE(hooks);
  433. v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
  434. v8::Local<v8::Function> function =
  435. FunctionFromString(context, kCustomCallbackThrowHook);
  436. v8::Local<v8::Value> args[] = {js_hooks};
  437. RunFunctionOnGlobal(function, context, std::size(args), args);
  438. const char kTestCall[] = R"(
  439. obj.functionWithCallback('baz', function() {
  440. this.callbackCalled = true;
  441. });)";
  442. CallFunctionOnObject(context, alpha_api, kTestCall);
  443. ValidateLastRequest("alpha.functionWithCallback", "['baz']");
  444. ASSERT_TRUE(console_errors().empty());
  445. TestJSRunner::AllowErrors allow_errors;
  446. bindings_system()->CompleteRequest(
  447. last_request()->request_id, ListValueFromString(R"(["alpha", "beta"])"),
  448. std::string());
  449. // The callback should have never been called and there should now be a
  450. // console error logged.
  451. EXPECT_EQ("undefined", GetStringPropertyFromObject(context->Global(), context,
  452. "callbackCalled"));
  453. ASSERT_EQ(1u, console_errors().size());
  454. EXPECT_THAT(console_errors()[0],
  455. testing::StartsWith(
  456. "Error handling response: Error: Custom callback threw"));
  457. }
  458. // Tests that an error thrown in a setCustomCallback hook while using a promise
  459. // based call works as expected.
  460. TEST_F(APIBindingsSystemTest, TestSetCustomCallback_ErrorWithPromise) {
  461. v8::HandleScope handle_scope(isolate());
  462. v8::Local<v8::Context> context = MainContext();
  463. APIBindingHooks* hooks = nullptr;
  464. v8::Local<v8::Object> alpha_api =
  465. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, &hooks);
  466. ASSERT_FALSE(alpha_api.IsEmpty());
  467. ASSERT_TRUE(hooks);
  468. v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context);
  469. v8::Local<v8::Function> function =
  470. FunctionFromString(context, kCustomCallbackThrowHook);
  471. v8::Local<v8::Value> args[] = {js_hooks};
  472. RunFunctionOnGlobal(function, context, std::size(args), args);
  473. const char kTestCall[] = R"(return obj.functionWithCallback('boz');)";
  474. v8::Local<v8::Value> result =
  475. CallFunctionOnObject(context, alpha_api, kTestCall);
  476. ValidateLastRequest("alpha.functionWithCallback", "['boz']");
  477. v8::Local<v8::Promise> promise;
  478. ASSERT_TRUE(GetValueAs(result, &promise));
  479. EXPECT_EQ(v8::Promise::kPending, promise->State());
  480. ASSERT_TRUE(console_errors().empty());
  481. TestJSRunner::AllowErrors allow_errors;
  482. bindings_system()->CompleteRequest(
  483. last_request()->request_id, ListValueFromString(R"(["gamma", "delta"])"),
  484. std::string());
  485. // The promise will remain pending and there should now be a console error
  486. // logged.
  487. // TODO(tjudkins): Ideally we should be rejecting the promise here instead.
  488. EXPECT_EQ(v8::Promise::kPending, promise->State());
  489. ASSERT_EQ(1u, console_errors().size());
  490. EXPECT_THAT(console_errors()[0],
  491. testing::StartsWith(
  492. "Error handling response: Error: Custom callback threw"));
  493. }
  494. // Test that references to other API's types works.
  495. TEST_F(APIBindingsSystemTest, CrossAPIReferences) {
  496. v8::HandleScope handle_scope(isolate());
  497. v8::Local<v8::Context> context = MainContext();
  498. // Instantiate gamma API. Note: It's important that we haven't instantiated
  499. // alpha API yet, since this tests that we can lazily populate the type
  500. // information.
  501. v8::Local<v8::Object> gamma_api =
  502. bindings_system()->CreateAPIInstance(kGammaAPIName, context, nullptr);
  503. ASSERT_FALSE(gamma_api.IsEmpty());
  504. {
  505. // Test a simple call -> response.
  506. const char kTestCall[] = "obj.functionWithExternalRef({prop1: 'foo'});";
  507. CallFunctionOnObject(context, gamma_api, kTestCall);
  508. ValidateLastRequest("gamma.functionWithExternalRef", "[{'prop1':'foo'}]");
  509. reset_last_request();
  510. }
  511. }
  512. TEST_F(APIBindingsSystemTest, TestCustomEvent) {
  513. v8::HandleScope handle_scope(isolate());
  514. v8::Local<v8::Context> context = MainContext();
  515. auto create_custom_event = [](v8::Local<v8::Context> context,
  516. const std::string& event_name) {
  517. v8::Isolate* isolate = context->GetIsolate();
  518. v8::Local<v8::Object> ret = v8::Object::New(isolate);
  519. ret->Set(context, gin::StringToSymbol(isolate, "name"),
  520. gin::StringToSymbol(isolate, event_name))
  521. .ToChecked();
  522. return ret.As<v8::Value>();
  523. };
  524. auto test_hooks = std::make_unique<APIBindingHooksTestDelegate>();
  525. test_hooks->SetCustomEvent(base::BindRepeating(create_custom_event));
  526. APIBindingHooks* binding_hooks =
  527. bindings_system()->GetHooksForAPI(kAlphaAPIName);
  528. binding_hooks->SetDelegate(std::move(test_hooks));
  529. v8::Local<v8::Object> api =
  530. bindings_system()->CreateAPIInstance(kAlphaAPIName, context, nullptr);
  531. v8::Local<v8::Object> event;
  532. ASSERT_TRUE(GetPropertyFromObjectAs(api, context, "alphaEvent", &event));
  533. EXPECT_EQ(R"("alpha.alphaEvent")",
  534. GetStringPropertyFromObject(event, context, "name"));
  535. v8::Local<v8::Value> event2 =
  536. GetPropertyFromObject(api, context, "alphaEvent");
  537. EXPECT_EQ(event, event2);
  538. v8::Local<v8::Object> other_event;
  539. ASSERT_TRUE(
  540. GetPropertyFromObjectAs(api, context, "alphaOtherEvent", &other_event));
  541. EXPECT_EQ(R"("alpha.alphaOtherEvent")",
  542. GetStringPropertyFromObject(other_event, context, "name"));
  543. EXPECT_NE(event, other_event);
  544. }
  545. } // namespace extensions