| OLD | NEW |
| (Empty) |
| 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 | |
| 5 #include "extensions/renderer/api_binding.h" | |
| 6 #include "base/bind.h" | |
| 7 #include "base/memory/ptr_util.h" | |
| 8 #include "base/stl_util.h" | |
| 9 #include "base/strings/stringprintf.h" | |
| 10 #include "base/values.h" | |
| 11 #include "extensions/renderer/api_binding_hooks.h" | |
| 12 #include "extensions/renderer/api_binding_hooks_test_delegate.h" | |
| 13 #include "extensions/renderer/api_binding_test.h" | |
| 14 #include "extensions/renderer/api_binding_test_util.h" | |
| 15 #include "extensions/renderer/api_event_handler.h" | |
| 16 #include "extensions/renderer/api_invocation_errors.h" | |
| 17 #include "extensions/renderer/api_request_handler.h" | |
| 18 #include "extensions/renderer/api_type_reference_map.h" | |
| 19 #include "gin/arguments.h" | |
| 20 #include "gin/converter.h" | |
| 21 #include "gin/public/context_holder.h" | |
| 22 #include "testing/gtest/include/gtest/gtest.h" | |
| 23 #include "third_party/WebKit/public/web/WebScopedUserGesture.h" | |
| 24 #include "v8/include/v8.h" | |
| 25 | |
| 26 namespace extensions { | |
| 27 | |
| 28 using namespace api_errors; | |
| 29 | |
| 30 namespace { | |
| 31 | |
| 32 const char kBindingName[] = "test"; | |
| 33 | |
| 34 // Function spec; we use single quotes for readability and then replace them. | |
| 35 const char kFunctions[] = | |
| 36 "[{" | |
| 37 " 'name': 'oneString'," | |
| 38 " 'parameters': [{" | |
| 39 " 'type': 'string'," | |
| 40 " 'name': 'str'" | |
| 41 " }]" | |
| 42 "}, {" | |
| 43 " 'name': 'stringAndInt'," | |
| 44 " 'parameters': [{" | |
| 45 " 'type': 'string'," | |
| 46 " 'name': 'str'" | |
| 47 " }, {" | |
| 48 " 'type': 'integer'," | |
| 49 " 'name': 'int'" | |
| 50 " }]" | |
| 51 "}, {" | |
| 52 " 'name': 'oneObject'," | |
| 53 " 'parameters': [{" | |
| 54 " 'type': 'object'," | |
| 55 " 'name': 'foo'," | |
| 56 " 'properties': {" | |
| 57 " 'prop1': {'type': 'string'}," | |
| 58 " 'prop2': {'type': 'string', 'optional': true}" | |
| 59 " }" | |
| 60 " }]" | |
| 61 "}, {" | |
| 62 " 'name': 'intAndCallback'," | |
| 63 " 'parameters': [{" | |
| 64 " 'name': 'int'," | |
| 65 " 'type': 'integer'" | |
| 66 " }, {" | |
| 67 " 'name': 'callback'," | |
| 68 " 'type': 'function'" | |
| 69 " }]" | |
| 70 "}]"; | |
| 71 | |
| 72 bool AllowAllFeatures(v8::Local<v8::Context> context, const std::string& name) { | |
| 73 return true; | |
| 74 } | |
| 75 | |
| 76 void OnEventListenersChanged(const std::string& event_name, | |
| 77 binding::EventListenersChanged change, | |
| 78 const base::DictionaryValue* filter, | |
| 79 bool was_manual, | |
| 80 v8::Local<v8::Context> context) {} | |
| 81 | |
| 82 } // namespace | |
| 83 | |
| 84 class APIBindingUnittest : public APIBindingTest { | |
| 85 public: | |
| 86 void OnFunctionCall(std::unique_ptr<APIRequestHandler::Request> request, | |
| 87 v8::Local<v8::Context> context) { | |
| 88 last_request_ = std::move(request); | |
| 89 } | |
| 90 | |
| 91 protected: | |
| 92 APIBindingUnittest() | |
| 93 : type_refs_(APITypeReferenceMap::InitializeTypeCallback()) {} | |
| 94 void SetUp() override { | |
| 95 APIBindingTest::SetUp(); | |
| 96 request_handler_ = base::MakeUnique<APIRequestHandler>( | |
| 97 base::Bind(&APIBindingUnittest::OnFunctionCall, base::Unretained(this)), | |
| 98 base::Bind(&RunFunctionOnGlobalAndIgnoreResult), | |
| 99 APILastError(APILastError::GetParent(), | |
| 100 APILastError::AddConsoleError())); | |
| 101 } | |
| 102 | |
| 103 void TearDown() override { | |
| 104 DisposeAllContexts(); | |
| 105 request_handler_.reset(); | |
| 106 event_handler_.reset(); | |
| 107 binding_.reset(); | |
| 108 APIBindingTest::TearDown(); | |
| 109 } | |
| 110 | |
| 111 void OnWillDisposeContext(v8::Local<v8::Context> context) override { | |
| 112 event_handler_->InvalidateContext(context); | |
| 113 request_handler_->InvalidateContext(context); | |
| 114 } | |
| 115 | |
| 116 void SetFunctions(const char* functions) { | |
| 117 binding_functions_ = ListValueFromString(functions); | |
| 118 ASSERT_TRUE(binding_functions_); | |
| 119 } | |
| 120 | |
| 121 void SetEvents(const char* events) { | |
| 122 binding_events_ = ListValueFromString(events); | |
| 123 ASSERT_TRUE(binding_events_); | |
| 124 } | |
| 125 | |
| 126 void SetTypes(const char* types) { | |
| 127 binding_types_ = ListValueFromString(types); | |
| 128 ASSERT_TRUE(binding_types_); | |
| 129 } | |
| 130 | |
| 131 void SetProperties(const char* properties) { | |
| 132 binding_properties_ = DictionaryValueFromString(properties); | |
| 133 ASSERT_TRUE(binding_properties_); | |
| 134 } | |
| 135 | |
| 136 void SetHooks(std::unique_ptr<APIBindingHooks> hooks) { | |
| 137 binding_hooks_ = std::move(hooks); | |
| 138 ASSERT_TRUE(binding_hooks_); | |
| 139 } | |
| 140 | |
| 141 void SetHooksDelegate( | |
| 142 std::unique_ptr<APIBindingHooksDelegate> hooks_delegate) { | |
| 143 binding_hooks_delegate_ = std::move(hooks_delegate); | |
| 144 ASSERT_TRUE(binding_hooks_delegate_); | |
| 145 } | |
| 146 | |
| 147 void SetCreateCustomType(const APIBinding::CreateCustomType& callback) { | |
| 148 create_custom_type_ = callback; | |
| 149 } | |
| 150 | |
| 151 void SetAvailabilityCallback( | |
| 152 const APIBinding::AvailabilityCallback& callback) { | |
| 153 availability_callback_ = callback; | |
| 154 } | |
| 155 | |
| 156 void InitializeBinding() { | |
| 157 if (!binding_hooks_) { | |
| 158 binding_hooks_ = base::MakeUnique<APIBindingHooks>( | |
| 159 kBindingName, binding::RunJSFunctionSync()); | |
| 160 } | |
| 161 if (binding_hooks_delegate_) | |
| 162 binding_hooks_->SetDelegate(std::move(binding_hooks_delegate_)); | |
| 163 if (!availability_callback_) | |
| 164 availability_callback_ = base::Bind(&AllowAllFeatures); | |
| 165 event_handler_ = base::MakeUnique<APIEventHandler>( | |
| 166 base::Bind(&RunFunctionOnGlobalAndIgnoreResult), | |
| 167 base::Bind(&RunFunctionOnGlobalAndReturnHandle), | |
| 168 base::Bind(&OnEventListenersChanged)); | |
| 169 binding_ = base::MakeUnique<APIBinding>( | |
| 170 kBindingName, binding_functions_.get(), binding_types_.get(), | |
| 171 binding_events_.get(), binding_properties_.get(), create_custom_type_, | |
| 172 availability_callback_, std::move(binding_hooks_), &type_refs_, | |
| 173 request_handler_.get(), event_handler_.get()); | |
| 174 EXPECT_EQ(!binding_types_.get(), type_refs_.empty()); | |
| 175 } | |
| 176 | |
| 177 void ExpectPass(v8::Local<v8::Object> object, | |
| 178 const std::string& script_source, | |
| 179 const std::string& expected_json_arguments_single_quotes, | |
| 180 bool expect_callback) { | |
| 181 ExpectPass(MainContext(), object, script_source, | |
| 182 expected_json_arguments_single_quotes, expect_callback); | |
| 183 } | |
| 184 | |
| 185 void ExpectPass(v8::Local<v8::Context> context, | |
| 186 v8::Local<v8::Object> object, | |
| 187 const std::string& script_source, | |
| 188 const std::string& expected_json_arguments_single_quotes, | |
| 189 bool expect_callback) { | |
| 190 RunTest(context, object, script_source, true, | |
| 191 ReplaceSingleQuotes(expected_json_arguments_single_quotes), | |
| 192 expect_callback, std::string()); | |
| 193 } | |
| 194 | |
| 195 void ExpectFailure(v8::Local<v8::Object> object, | |
| 196 const std::string& script_source, | |
| 197 const std::string& expected_error) { | |
| 198 RunTest(MainContext(), object, script_source, false, std::string(), false, | |
| 199 "Uncaught TypeError: " + expected_error); | |
| 200 } | |
| 201 | |
| 202 void ExpectThrow(v8::Local<v8::Object> object, | |
| 203 const std::string& script_source, | |
| 204 const std::string& expected_error) { | |
| 205 RunTest(MainContext(), object, script_source, false, std::string(), false, | |
| 206 "Uncaught Error: " + expected_error); | |
| 207 } | |
| 208 | |
| 209 bool HandlerWasInvoked() const { return last_request_ != nullptr; } | |
| 210 const APIRequestHandler::Request* last_request() const { | |
| 211 return last_request_.get(); | |
| 212 } | |
| 213 void reset_last_request() { last_request_.reset(); } | |
| 214 APIBinding* binding() { return binding_.get(); } | |
| 215 APIEventHandler* event_handler() { return event_handler_.get(); } | |
| 216 APIRequestHandler* request_handler() { return request_handler_.get(); } | |
| 217 const APITypeReferenceMap& type_refs() const { return type_refs_; } | |
| 218 | |
| 219 private: | |
| 220 void RunTest(v8::Local<v8::Context> context, | |
| 221 v8::Local<v8::Object> object, | |
| 222 const std::string& script_source, | |
| 223 bool should_pass, | |
| 224 const std::string& expected_json_arguments, | |
| 225 bool expect_callback, | |
| 226 const std::string& expected_error); | |
| 227 | |
| 228 std::unique_ptr<APIRequestHandler::Request> last_request_; | |
| 229 std::unique_ptr<APIBinding> binding_; | |
| 230 std::unique_ptr<APIEventHandler> event_handler_; | |
| 231 std::unique_ptr<APIRequestHandler> request_handler_; | |
| 232 APITypeReferenceMap type_refs_; | |
| 233 | |
| 234 std::unique_ptr<base::ListValue> binding_functions_; | |
| 235 std::unique_ptr<base::ListValue> binding_events_; | |
| 236 std::unique_ptr<base::ListValue> binding_types_; | |
| 237 std::unique_ptr<base::DictionaryValue> binding_properties_; | |
| 238 std::unique_ptr<APIBindingHooks> binding_hooks_; | |
| 239 std::unique_ptr<APIBindingHooksDelegate> binding_hooks_delegate_; | |
| 240 APIBinding::CreateCustomType create_custom_type_; | |
| 241 APIBinding::AvailabilityCallback availability_callback_; | |
| 242 | |
| 243 DISALLOW_COPY_AND_ASSIGN(APIBindingUnittest); | |
| 244 }; | |
| 245 | |
| 246 void APIBindingUnittest::RunTest(v8::Local<v8::Context> context, | |
| 247 v8::Local<v8::Object> object, | |
| 248 const std::string& script_source, | |
| 249 bool should_pass, | |
| 250 const std::string& expected_json_arguments, | |
| 251 bool expect_callback, | |
| 252 const std::string& expected_error) { | |
| 253 EXPECT_FALSE(last_request_); | |
| 254 std::string wrapped_script_source = | |
| 255 base::StringPrintf("(function(obj) { %s })", script_source.c_str()); | |
| 256 | |
| 257 v8::Local<v8::Function> func = | |
| 258 FunctionFromString(context, wrapped_script_source); | |
| 259 ASSERT_FALSE(func.IsEmpty()); | |
| 260 | |
| 261 v8::Local<v8::Value> argv[] = {object}; | |
| 262 | |
| 263 if (should_pass) { | |
| 264 RunFunction(func, context, 1, argv); | |
| 265 ASSERT_TRUE(last_request_) << script_source; | |
| 266 EXPECT_EQ(expected_json_arguments, | |
| 267 ValueToString(*last_request_->arguments)); | |
| 268 EXPECT_EQ(expect_callback, last_request_->has_callback) << script_source; | |
| 269 } else { | |
| 270 RunFunctionAndExpectError(func, context, 1, argv, expected_error); | |
| 271 EXPECT_FALSE(last_request_); | |
| 272 } | |
| 273 | |
| 274 last_request_.reset(); | |
| 275 } | |
| 276 | |
| 277 TEST_F(APIBindingUnittest, TestEmptyAPI) { | |
| 278 InitializeBinding(); | |
| 279 | |
| 280 v8::HandleScope handle_scope(isolate()); | |
| 281 v8::Local<v8::Context> context = MainContext(); | |
| 282 | |
| 283 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 284 EXPECT_EQ( | |
| 285 0u, | |
| 286 binding_object->GetOwnPropertyNames(context).ToLocalChecked()->Length()); | |
| 287 } | |
| 288 | |
| 289 // Tests the basic call -> request flow of the API binding (ensuring that | |
| 290 // functions are set up correctly and correctly enforced). | |
| 291 TEST_F(APIBindingUnittest, TestBasicAPICalls) { | |
| 292 SetFunctions(kFunctions); | |
| 293 InitializeBinding(); | |
| 294 | |
| 295 v8::HandleScope handle_scope(isolate()); | |
| 296 v8::Local<v8::Context> context = MainContext(); | |
| 297 | |
| 298 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 299 | |
| 300 // Argument parsing is tested primarily in APISignature and ArgumentSpec | |
| 301 // tests, so do a few quick sanity checks... | |
| 302 ExpectPass(binding_object, "obj.oneString('foo');", "['foo']", false); | |
| 303 ExpectFailure( | |
| 304 binding_object, "obj.oneString(1);", | |
| 305 InvocationError( | |
| 306 "test.oneString", "string str", | |
| 307 ArgumentError("str", InvalidType(kTypeString, kTypeInteger)))); | |
| 308 ExpectPass(binding_object, "obj.stringAndInt('foo', 1)", "['foo',1]", false); | |
| 309 ExpectFailure( | |
| 310 binding_object, "obj.stringAndInt(1)", | |
| 311 InvocationError( | |
| 312 "test.stringAndInt", "string str, integer int", | |
| 313 ArgumentError("str", InvalidType(kTypeString, kTypeInteger)))); | |
| 314 ExpectPass(binding_object, "obj.intAndCallback(1, function() {})", "[1]", | |
| 315 true); | |
| 316 ExpectFailure( | |
| 317 binding_object, "obj.intAndCallback(function() {})", | |
| 318 InvocationError( | |
| 319 "test.intAndCallback", "integer int, function callback", | |
| 320 ArgumentError("int", InvalidType(kTypeInteger, kTypeFunction)))); | |
| 321 | |
| 322 // ...And an interesting case (throwing an error during parsing). | |
| 323 ExpectThrow(binding_object, | |
| 324 "obj.oneObject({ get prop1() { throw new Error('Badness'); } });", | |
| 325 "Badness"); | |
| 326 } | |
| 327 | |
| 328 // Test that "forIOThread" property in a function schema is respected. | |
| 329 TEST_F(APIBindingUnittest, IOThreadCalls) { | |
| 330 const char kFunctions[] = | |
| 331 "[{" | |
| 332 " 'name' : 'uiFunc1'," | |
| 333 " 'parameters' : []" | |
| 334 "}, {" | |
| 335 " 'name' : 'uiFunc2'," | |
| 336 " 'parameters' : []," | |
| 337 " 'forIOThread' : false" | |
| 338 "}, {" | |
| 339 " 'name' : 'ioFunc'," | |
| 340 " 'parameters' : []," | |
| 341 " 'forIOThread' : true" | |
| 342 "}]"; | |
| 343 SetFunctions(kFunctions); | |
| 344 InitializeBinding(); | |
| 345 | |
| 346 v8::HandleScope handle_scope(isolate()); | |
| 347 v8::Local<v8::Context> context = MainContext(); | |
| 348 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 349 | |
| 350 struct { | |
| 351 const char* func_name; | |
| 352 binding::RequestThread thread; | |
| 353 } test_cases[] = { | |
| 354 {"uiFunc1", binding::RequestThread::UI}, | |
| 355 {"uiFunc2", binding::RequestThread::UI}, | |
| 356 {"ioFunc", binding::RequestThread::IO}, | |
| 357 }; | |
| 358 const char kFunctionCall[] = "(function(obj) { obj.%s(); })"; | |
| 359 v8::Local<v8::Value> argv[] = {binding_object}; | |
| 360 | |
| 361 for (const auto& test_case : test_cases) { | |
| 362 SCOPED_TRACE(base::StringPrintf("Testing case-%s", test_case.func_name)); | |
| 363 v8::Local<v8::Function> func = FunctionFromString( | |
| 364 context, base::StringPrintf(kFunctionCall, test_case.func_name)); | |
| 365 RunFunction(func, context, arraysize(argv), argv); | |
| 366 ASSERT_TRUE(last_request()); | |
| 367 EXPECT_EQ(test_case.thread, last_request()->thread); | |
| 368 reset_last_request(); | |
| 369 } | |
| 370 } | |
| 371 | |
| 372 // Test that enum values are properly exposed on the binding object. | |
| 373 TEST_F(APIBindingUnittest, EnumValues) { | |
| 374 const char kTypes[] = | |
| 375 "[{" | |
| 376 " 'id': 'first'," | |
| 377 " 'type': 'string'," | |
| 378 " 'enum': ['alpha', 'camelCase', 'Hyphen-ated'," | |
| 379 " 'SCREAMING', 'nums123', '42nums']" | |
| 380 "}, {" | |
| 381 " 'id': 'last'," | |
| 382 " 'type': 'string'," | |
| 383 " 'enum': [{'name': 'omega'}]" | |
| 384 "}]"; | |
| 385 | |
| 386 SetTypes(kTypes); | |
| 387 InitializeBinding(); | |
| 388 | |
| 389 v8::HandleScope handle_scope(isolate()); | |
| 390 v8::Local<v8::Context> context = MainContext(); | |
| 391 | |
| 392 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 393 | |
| 394 const char kExpected[] = | |
| 395 "{'ALPHA':'alpha','CAMEL_CASE':'camelCase','HYPHEN_ATED':'Hyphen-ated'," | |
| 396 "'NUMS123':'nums123','SCREAMING':'SCREAMING','_42NUMS':'42nums'}"; | |
| 397 EXPECT_EQ(ReplaceSingleQuotes(kExpected), | |
| 398 GetStringPropertyFromObject(binding_object, context, "first")); | |
| 399 EXPECT_EQ(ReplaceSingleQuotes("{'OMEGA':'omega'}"), | |
| 400 GetStringPropertyFromObject(binding_object, context, "last")); | |
| 401 } | |
| 402 | |
| 403 // Test that empty enum entries are (unfortunately) allowed. | |
| 404 TEST_F(APIBindingUnittest, EnumWithEmptyEntry) { | |
| 405 const char kTypes[] = | |
| 406 "[{" | |
| 407 " 'id': 'enumWithEmpty'," | |
| 408 " 'type': 'string'," | |
| 409 " 'enum': [{'name': ''}, {'name': 'other'}]" | |
| 410 "}]"; | |
| 411 | |
| 412 SetTypes(kTypes); | |
| 413 InitializeBinding(); | |
| 414 | |
| 415 v8::HandleScope handle_scope(isolate()); | |
| 416 v8::Local<v8::Context> context = MainContext(); | |
| 417 | |
| 418 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 419 | |
| 420 EXPECT_EQ( | |
| 421 "{\"\":\"\",\"OTHER\":\"other\"}", | |
| 422 GetStringPropertyFromObject(binding_object, context, "enumWithEmpty")); | |
| 423 } | |
| 424 | |
| 425 // Test that type references are correctly set up in the API. | |
| 426 TEST_F(APIBindingUnittest, TypeRefsTest) { | |
| 427 const char kTypes[] = | |
| 428 "[{" | |
| 429 " 'id': 'refObj'," | |
| 430 " 'type': 'object'," | |
| 431 " 'properties': {" | |
| 432 " 'prop1': {'type': 'string'}," | |
| 433 " 'prop2': {'type': 'integer', 'optional': true}" | |
| 434 " }" | |
| 435 "}, {" | |
| 436 " 'id': 'refEnum'," | |
| 437 " 'type': 'string'," | |
| 438 " 'enum': ['alpha', 'beta']" | |
| 439 "}]"; | |
| 440 const char kRefFunctions[] = | |
| 441 "[{" | |
| 442 " 'name': 'takesRefObj'," | |
| 443 " 'parameters': [{" | |
| 444 " 'name': 'o'," | |
| 445 " '$ref': 'refObj'" | |
| 446 " }]" | |
| 447 "}, {" | |
| 448 " 'name': 'takesRefEnum'," | |
| 449 " 'parameters': [{" | |
| 450 " 'name': 'e'," | |
| 451 " '$ref': 'refEnum'" | |
| 452 " }]" | |
| 453 "}]"; | |
| 454 | |
| 455 SetFunctions(kRefFunctions); | |
| 456 SetTypes(kTypes); | |
| 457 InitializeBinding(); | |
| 458 EXPECT_EQ(2u, type_refs().size()); | |
| 459 EXPECT_TRUE(type_refs().GetSpec("refObj")); | |
| 460 EXPECT_TRUE(type_refs().GetSpec("refEnum")); | |
| 461 | |
| 462 v8::HandleScope handle_scope(isolate()); | |
| 463 v8::Local<v8::Context> context = MainContext(); | |
| 464 | |
| 465 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 466 | |
| 467 // Parsing in general is tested in APISignature and ArgumentSpec tests, but | |
| 468 // we test that the binding a) correctly finds the definitions, and b) accepts | |
| 469 // properties from the API object. | |
| 470 ExpectPass(binding_object, "obj.takesRefObj({prop1: 'foo'})", | |
| 471 "[{'prop1':'foo'}]", false); | |
| 472 ExpectFailure( | |
| 473 binding_object, "obj.takesRefObj({prop1: 'foo', prop2: 'a'})", | |
| 474 InvocationError( | |
| 475 "test.takesRefObj", "refObj o", | |
| 476 ArgumentError( | |
| 477 "o", | |
| 478 PropertyError("prop2", InvalidType(kTypeInteger, kTypeString))))); | |
| 479 ExpectPass(binding_object, "obj.takesRefEnum('alpha')", "['alpha']", false); | |
| 480 ExpectPass(binding_object, "obj.takesRefEnum(obj.refEnum.BETA)", "['beta']", | |
| 481 false); | |
| 482 ExpectFailure( | |
| 483 binding_object, "obj.takesRefEnum('gamma')", | |
| 484 InvocationError("test.takesRefEnum", "refEnum e", | |
| 485 ArgumentError("e", InvalidEnumValue({"alpha", "beta"})))); | |
| 486 } | |
| 487 | |
| 488 TEST_F(APIBindingUnittest, RestrictedAPIs) { | |
| 489 const char kFunctions[] = | |
| 490 "[{" | |
| 491 " 'name': 'allowedOne'," | |
| 492 " 'parameters': []" | |
| 493 "}, {" | |
| 494 " 'name': 'allowedTwo'," | |
| 495 " 'parameters': []" | |
| 496 "}, {" | |
| 497 " 'name': 'restrictedOne'," | |
| 498 " 'parameters': []" | |
| 499 "}, {" | |
| 500 " 'name': 'restrictedTwo'," | |
| 501 " 'parameters': []" | |
| 502 "}]"; | |
| 503 SetFunctions(kFunctions); | |
| 504 const char kEvents[] = | |
| 505 "[{'name': 'allowedEvent'}, {'name': 'restrictedEvent'}]"; | |
| 506 SetEvents(kEvents); | |
| 507 auto is_available = [](v8::Local<v8::Context> context, | |
| 508 const std::string& name) { | |
| 509 std::set<std::string> allowed = {"test.allowedOne", "test.allowedTwo", | |
| 510 "test.allowedEvent"}; | |
| 511 std::set<std::string> restricted = { | |
| 512 "test.restrictedOne", "test.restrictedTwo", "test.restrictedEvent"}; | |
| 513 EXPECT_TRUE(allowed.count(name) || restricted.count(name)) << name; | |
| 514 return allowed.count(name) != 0; | |
| 515 }; | |
| 516 SetAvailabilityCallback(base::Bind(is_available)); | |
| 517 | |
| 518 InitializeBinding(); | |
| 519 | |
| 520 v8::HandleScope handle_scope(isolate()); | |
| 521 v8::Local<v8::Context> context = MainContext(); | |
| 522 | |
| 523 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 524 | |
| 525 auto is_defined = [&binding_object, context](const std::string& name) { | |
| 526 v8::Local<v8::Value> val = | |
| 527 GetPropertyFromObject(binding_object, context, name); | |
| 528 EXPECT_FALSE(val.IsEmpty()); | |
| 529 return !val->IsUndefined() && !val->IsNull(); | |
| 530 }; | |
| 531 | |
| 532 EXPECT_TRUE(is_defined("allowedOne")); | |
| 533 EXPECT_TRUE(is_defined("allowedTwo")); | |
| 534 EXPECT_TRUE(is_defined("allowedEvent")); | |
| 535 EXPECT_FALSE(is_defined("restrictedOne")); | |
| 536 EXPECT_FALSE(is_defined("restrictedTwo")); | |
| 537 EXPECT_FALSE(is_defined("restrictedEvent")); | |
| 538 } | |
| 539 | |
| 540 // Tests that events specified in the API are created as properties of the API | |
| 541 // object. | |
| 542 TEST_F(APIBindingUnittest, TestEventCreation) { | |
| 543 SetEvents( | |
| 544 R"([ | |
| 545 {'name': 'onFoo'}, | |
| 546 {'name': 'onBar'}, | |
| 547 {'name': 'onBaz', 'options': {'maxListeners': 1}} | |
| 548 ])"); | |
| 549 SetFunctions(kFunctions); | |
| 550 InitializeBinding(); | |
| 551 | |
| 552 v8::HandleScope handle_scope(isolate()); | |
| 553 v8::Local<v8::Context> context = MainContext(); | |
| 554 | |
| 555 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 556 | |
| 557 // Event behavior is tested in the APIEventHandler unittests as well as the | |
| 558 // APIBindingsSystem tests, so we really only need to check that the events | |
| 559 // are being initialized on the object. | |
| 560 v8::Maybe<bool> has_on_foo = | |
| 561 binding_object->Has(context, gin::StringToV8(isolate(), "onFoo")); | |
| 562 EXPECT_TRUE(has_on_foo.IsJust()); | |
| 563 EXPECT_TRUE(has_on_foo.FromJust()); | |
| 564 | |
| 565 v8::Maybe<bool> has_on_bar = | |
| 566 binding_object->Has(context, gin::StringToV8(isolate(), "onBar")); | |
| 567 EXPECT_TRUE(has_on_bar.IsJust()); | |
| 568 EXPECT_TRUE(has_on_bar.FromJust()); | |
| 569 | |
| 570 v8::Maybe<bool> has_on_baz = | |
| 571 binding_object->Has(context, gin::StringToV8(isolate(), "onBaz")); | |
| 572 EXPECT_TRUE(has_on_baz.IsJust()); | |
| 573 EXPECT_TRUE(has_on_baz.FromJust()); | |
| 574 | |
| 575 // Test that the maxListeners property is correctly used. | |
| 576 v8::Local<v8::Function> add_listener = FunctionFromString( | |
| 577 context, "(function(e) { e.addListener(function() {}); })"); | |
| 578 v8::Local<v8::Value> args[] = { | |
| 579 GetPropertyFromObject(binding_object, context, "onBaz")}; | |
| 580 RunFunction(add_listener, context, arraysize(args), args); | |
| 581 EXPECT_EQ(1u, event_handler()->GetNumEventListenersForTesting("test.onBaz", | |
| 582 context)); | |
| 583 RunFunctionAndExpectError(add_listener, context, arraysize(args), args, | |
| 584 "Uncaught TypeError: Too many listeners."); | |
| 585 EXPECT_EQ(1u, event_handler()->GetNumEventListenersForTesting("test.onBaz", | |
| 586 context)); | |
| 587 | |
| 588 v8::Maybe<bool> has_nonexistent_event = binding_object->Has( | |
| 589 context, gin::StringToV8(isolate(), "onNonexistentEvent")); | |
| 590 EXPECT_TRUE(has_nonexistent_event.IsJust()); | |
| 591 EXPECT_FALSE(has_nonexistent_event.FromJust()); | |
| 592 } | |
| 593 | |
| 594 TEST_F(APIBindingUnittest, TestProperties) { | |
| 595 SetProperties( | |
| 596 "{" | |
| 597 " 'prop1': { 'value': 17, 'type': 'integer' }," | |
| 598 " 'prop2': {" | |
| 599 " 'type': 'object'," | |
| 600 " 'properties': {" | |
| 601 " 'subprop1': { 'value': 'some value', 'type': 'string' }," | |
| 602 " 'subprop2': { 'value': true, 'type': 'boolean' }" | |
| 603 " }" | |
| 604 " }" | |
| 605 "}"); | |
| 606 InitializeBinding(); | |
| 607 | |
| 608 v8::HandleScope handle_scope(isolate()); | |
| 609 v8::Local<v8::Context> context = MainContext(); | |
| 610 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 611 EXPECT_EQ("17", | |
| 612 GetStringPropertyFromObject(binding_object, context, "prop1")); | |
| 613 EXPECT_EQ(R"({"subprop1":"some value","subprop2":true})", | |
| 614 GetStringPropertyFromObject(binding_object, context, "prop2")); | |
| 615 } | |
| 616 | |
| 617 TEST_F(APIBindingUnittest, TestRefProperties) { | |
| 618 SetProperties( | |
| 619 "{" | |
| 620 " 'alpha': {" | |
| 621 " '$ref': 'AlphaRef'," | |
| 622 " 'value': ['a']" | |
| 623 " }," | |
| 624 " 'beta': {" | |
| 625 " '$ref': 'BetaRef'," | |
| 626 " 'value': ['b']" | |
| 627 " }" | |
| 628 "}"); | |
| 629 auto create_custom_type = [](v8::Isolate* isolate, | |
| 630 const std::string& type_name, | |
| 631 const std::string& property_name, | |
| 632 const base::ListValue* property_values) { | |
| 633 v8::Local<v8::Context> context = isolate->GetCurrentContext(); | |
| 634 v8::Local<v8::Object> result = v8::Object::New(isolate); | |
| 635 if (type_name == "AlphaRef") { | |
| 636 EXPECT_EQ("alpha", property_name); | |
| 637 EXPECT_EQ("[\"a\"]", ValueToString(*property_values)); | |
| 638 result | |
| 639 ->Set(context, gin::StringToSymbol(isolate, "alphaProp"), | |
| 640 gin::StringToV8(isolate, "alphaVal")) | |
| 641 .ToChecked(); | |
| 642 } else if (type_name == "BetaRef") { | |
| 643 EXPECT_EQ("beta", property_name); | |
| 644 EXPECT_EQ("[\"b\"]", ValueToString(*property_values)); | |
| 645 result | |
| 646 ->Set(context, gin::StringToSymbol(isolate, "betaProp"), | |
| 647 gin::StringToV8(isolate, "betaVal")) | |
| 648 .ToChecked(); | |
| 649 } else { | |
| 650 EXPECT_TRUE(false) << type_name; | |
| 651 } | |
| 652 return result; | |
| 653 }; | |
| 654 | |
| 655 SetCreateCustomType(base::Bind(create_custom_type)); | |
| 656 | |
| 657 InitializeBinding(); | |
| 658 | |
| 659 v8::HandleScope handle_scope(isolate()); | |
| 660 v8::Local<v8::Context> context = MainContext(); | |
| 661 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 662 EXPECT_EQ(R"({"alphaProp":"alphaVal"})", | |
| 663 GetStringPropertyFromObject(binding_object, context, "alpha")); | |
| 664 EXPECT_EQ( | |
| 665 R"({"betaProp":"betaVal"})", | |
| 666 GetStringPropertyFromObject(binding_object, context, "beta")); | |
| 667 } | |
| 668 | |
| 669 TEST_F(APIBindingUnittest, TestDisposedContext) { | |
| 670 SetFunctions(kFunctions); | |
| 671 InitializeBinding(); | |
| 672 | |
| 673 v8::HandleScope handle_scope(isolate()); | |
| 674 v8::Local<v8::Context> context = MainContext(); | |
| 675 | |
| 676 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 677 | |
| 678 v8::Local<v8::Function> func = | |
| 679 FunctionFromString(context, "(function(obj) { obj.oneString('foo'); })"); | |
| 680 v8::Local<v8::Value> argv[] = {binding_object}; | |
| 681 DisposeContext(context); | |
| 682 RunFunction(func, context, arraysize(argv), argv); | |
| 683 EXPECT_FALSE(HandlerWasInvoked()); | |
| 684 // This test passes if this does not crash, even under AddressSanitizer | |
| 685 // builds. | |
| 686 } | |
| 687 | |
| 688 TEST_F(APIBindingUnittest, MultipleContexts) { | |
| 689 v8::HandleScope handle_scope(isolate()); | |
| 690 v8::Local<v8::Context> context_a = MainContext(); | |
| 691 v8::Local<v8::Context> context_b = AddContext(); | |
| 692 | |
| 693 SetFunctions(kFunctions); | |
| 694 InitializeBinding(); | |
| 695 | |
| 696 v8::Local<v8::Object> binding_object_a = binding()->CreateInstance(context_a); | |
| 697 v8::Local<v8::Object> binding_object_b = binding()->CreateInstance(context_b); | |
| 698 | |
| 699 ExpectPass(context_a, binding_object_a, "obj.oneString('foo');", "['foo']", | |
| 700 false); | |
| 701 ExpectPass(context_b, binding_object_b, "obj.oneString('foo');", "['foo']", | |
| 702 false); | |
| 703 DisposeContext(context_a); | |
| 704 ExpectPass(context_b, binding_object_b, "obj.oneString('foo');", "['foo']", | |
| 705 false); | |
| 706 } | |
| 707 | |
| 708 // Tests adding custom hooks for an API method. | |
| 709 TEST_F(APIBindingUnittest, TestCustomHooks) { | |
| 710 SetFunctions(kFunctions); | |
| 711 | |
| 712 // Register a hook for the test.oneString method. | |
| 713 auto hooks = base::MakeUnique<APIBindingHooksTestDelegate>(); | |
| 714 bool did_call = false; | |
| 715 auto hook = [](bool* did_call, const APISignature* signature, | |
| 716 v8::Local<v8::Context> context, | |
| 717 std::vector<v8::Local<v8::Value>>* arguments, | |
| 718 const APITypeReferenceMap& ref_map) { | |
| 719 *did_call = true; | |
| 720 APIBindingHooks::RequestResult result( | |
| 721 APIBindingHooks::RequestResult::HANDLED); | |
| 722 if (arguments->size() != 1u) { // ASSERT* messes with the return type. | |
| 723 EXPECT_EQ(1u, arguments->size()); | |
| 724 return result; | |
| 725 } | |
| 726 EXPECT_EQ("foo", gin::V8ToString(arguments->at(0))); | |
| 727 return result; | |
| 728 }; | |
| 729 hooks->AddHandler("test.oneString", base::Bind(hook, &did_call)); | |
| 730 SetHooksDelegate(std::move(hooks)); | |
| 731 | |
| 732 InitializeBinding(); | |
| 733 | |
| 734 v8::HandleScope handle_scope(isolate()); | |
| 735 v8::Local<v8::Context> context = MainContext(); | |
| 736 | |
| 737 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 738 | |
| 739 // First try calling the oneString() method, which has a custom hook | |
| 740 // installed. | |
| 741 v8::Local<v8::Function> func = | |
| 742 FunctionFromString(context, "(function(obj) { obj.oneString('foo'); })"); | |
| 743 v8::Local<v8::Value> args[] = {binding_object}; | |
| 744 RunFunction(func, context, 1, args); | |
| 745 EXPECT_TRUE(did_call); | |
| 746 | |
| 747 // Other methods, like stringAndInt(), should behave normally. | |
| 748 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]", | |
| 749 false); | |
| 750 } | |
| 751 | |
| 752 TEST_F(APIBindingUnittest, TestJSCustomHook) { | |
| 753 // Register a hook for the test.oneString method. | |
| 754 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 755 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle)); | |
| 756 | |
| 757 v8::HandleScope handle_scope(isolate()); | |
| 758 v8::Local<v8::Context> context = MainContext(); | |
| 759 { | |
| 760 const char kRegisterHook[] = | |
| 761 "(function(hooks) {\n" | |
| 762 " hooks.setHandleRequest('oneString', function() {\n" | |
| 763 " this.requestArguments = Array.from(arguments);\n" | |
| 764 " });\n" | |
| 765 "})"; | |
| 766 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 767 v8::Local<v8::Function> function = | |
| 768 FunctionFromString(context, kRegisterHook); | |
| 769 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 770 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 771 } | |
| 772 | |
| 773 SetFunctions(kFunctions); | |
| 774 SetHooks(std::move(hooks)); | |
| 775 InitializeBinding(); | |
| 776 | |
| 777 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 778 | |
| 779 // First try calling with an invalid invocation. An error should be raised and | |
| 780 // the hook should never have been called, since the arguments didn't match. | |
| 781 ExpectFailure( | |
| 782 binding_object, "obj.oneString(1);", | |
| 783 InvocationError( | |
| 784 "test.oneString", "string str", | |
| 785 ArgumentError("str", InvalidType(kTypeString, kTypeInteger)))); | |
| 786 v8::Local<v8::Value> property = | |
| 787 GetPropertyFromObject(context->Global(), context, "requestArguments"); | |
| 788 ASSERT_FALSE(property.IsEmpty()); | |
| 789 EXPECT_TRUE(property->IsUndefined()); | |
| 790 | |
| 791 // Try calling the oneString() method with valid arguments. The hook should | |
| 792 // be called. | |
| 793 v8::Local<v8::Function> func = | |
| 794 FunctionFromString(context, "(function(obj) { obj.oneString('foo'); })"); | |
| 795 v8::Local<v8::Value> args[] = {binding_object}; | |
| 796 RunFunction(func, context, 1, args); | |
| 797 | |
| 798 EXPECT_EQ("[\"foo\"]", GetStringPropertyFromObject( | |
| 799 context->Global(), context, "requestArguments")); | |
| 800 | |
| 801 // Other methods, like stringAndInt(), should behave normally. | |
| 802 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]", | |
| 803 false); | |
| 804 } | |
| 805 | |
| 806 // Tests the updateArgumentsPreValidate hook. | |
| 807 TEST_F(APIBindingUnittest, TestUpdateArgumentsPreValidate) { | |
| 808 // Register a hook for the test.oneString method. | |
| 809 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 810 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle)); | |
| 811 | |
| 812 v8::HandleScope handle_scope(isolate()); | |
| 813 v8::Local<v8::Context> context = MainContext(); | |
| 814 const char kRegisterHook[] = | |
| 815 "(function(hooks) {\n" | |
| 816 " hooks.setUpdateArgumentsPreValidate('oneString', function() {\n" | |
| 817 " this.requestArguments = Array.from(arguments);\n" | |
| 818 " if (this.requestArguments[0] === true)\n" | |
| 819 " return ['hooked']\n" | |
| 820 " return this.requestArguments\n" | |
| 821 " });\n" | |
| 822 "})"; | |
| 823 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 824 v8::Local<v8::Function> function = FunctionFromString(context, kRegisterHook); | |
| 825 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 826 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 827 | |
| 828 SetHooks(std::move(hooks)); | |
| 829 SetFunctions(kFunctions); | |
| 830 InitializeBinding(); | |
| 831 | |
| 832 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 833 | |
| 834 // Call the method with a hook. Since the hook updates arguments before | |
| 835 // validation, we should be able to pass in invalid arguments and still | |
| 836 // have the hook called. | |
| 837 ExpectFailure( | |
| 838 binding_object, "obj.oneString(false);", | |
| 839 InvocationError( | |
| 840 "test.oneString", "string str", | |
| 841 ArgumentError("str", InvalidType(kTypeString, kTypeBoolean)))); | |
| 842 EXPECT_EQ("[false]", GetStringPropertyFromObject( | |
| 843 context->Global(), context, "requestArguments")); | |
| 844 | |
| 845 ExpectPass(binding_object, "obj.oneString(true);", "['hooked']", false); | |
| 846 EXPECT_EQ("[true]", GetStringPropertyFromObject( | |
| 847 context->Global(), context, "requestArguments")); | |
| 848 | |
| 849 // Other methods, like stringAndInt(), should behave normally. | |
| 850 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]", | |
| 851 false); | |
| 852 } | |
| 853 | |
| 854 // Tests the updateArgumentsPreValidate hook. | |
| 855 TEST_F(APIBindingUnittest, TestThrowInUpdateArgumentsPreValidate) { | |
| 856 auto run_js_and_allow_error = [](v8::Local<v8::Function> function, | |
| 857 v8::Local<v8::Context> context, | |
| 858 int argc, | |
| 859 v8::Local<v8::Value> argv[]) { | |
| 860 v8::MaybeLocal<v8::Value> maybe_result = | |
| 861 function->Call(context, context->Global(), argc, argv); | |
| 862 v8::Global<v8::Value> result; | |
| 863 v8::Local<v8::Value> local; | |
| 864 if (maybe_result.ToLocal(&local)) | |
| 865 result.Reset(context->GetIsolate(), local); | |
| 866 return result; | |
| 867 }; | |
| 868 | |
| 869 // Register a hook for the test.oneString method. | |
| 870 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 871 kBindingName, base::Bind(run_js_and_allow_error)); | |
| 872 | |
| 873 v8::HandleScope handle_scope(isolate()); | |
| 874 v8::Local<v8::Context> context = MainContext(); | |
| 875 { | |
| 876 const char kRegisterHook[] = | |
| 877 "(function(hooks) {\n" | |
| 878 " hooks.setUpdateArgumentsPreValidate('oneString', function() {\n" | |
| 879 " throw new Error('Custom Hook Error');\n" | |
| 880 " });\n" | |
| 881 "})"; | |
| 882 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 883 v8::Local<v8::Function> function = | |
| 884 FunctionFromString(context, kRegisterHook); | |
| 885 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 886 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 887 } | |
| 888 | |
| 889 SetHooks(std::move(hooks)); | |
| 890 SetFunctions(kFunctions); | |
| 891 InitializeBinding(); | |
| 892 | |
| 893 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 894 | |
| 895 v8::Local<v8::Function> function = | |
| 896 FunctionFromString(context, | |
| 897 "(function(obj) { return obj.oneString('ping'); })"); | |
| 898 v8::Local<v8::Value> args[] = {binding_object}; | |
| 899 RunFunctionAndExpectError(function, context, v8::Undefined(isolate()), | |
| 900 arraysize(args), args, | |
| 901 "Uncaught Error: Custom Hook Error"); | |
| 902 | |
| 903 // Other methods, like stringAndInt(), should behave normally. | |
| 904 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", "['foo',42]", | |
| 905 false); | |
| 906 } | |
| 907 | |
| 908 // Tests that custom JS hooks can return results synchronously. | |
| 909 TEST_F(APIBindingUnittest, TestReturningResultFromCustomJSHook) { | |
| 910 // Register a hook for the test.oneString method. | |
| 911 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 912 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle)); | |
| 913 | |
| 914 v8::HandleScope handle_scope(isolate()); | |
| 915 v8::Local<v8::Context> context = MainContext(); | |
| 916 { | |
| 917 const char kRegisterHook[] = | |
| 918 "(function(hooks) {\n" | |
| 919 " hooks.setHandleRequest('oneString', str => {\n" | |
| 920 " return str + ' pong';\n" | |
| 921 " });\n" | |
| 922 "})"; | |
| 923 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 924 v8::Local<v8::Function> function = | |
| 925 FunctionFromString(context, kRegisterHook); | |
| 926 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 927 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 928 } | |
| 929 | |
| 930 SetHooks(std::move(hooks)); | |
| 931 SetFunctions(kFunctions); | |
| 932 InitializeBinding(); | |
| 933 | |
| 934 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 935 | |
| 936 v8::Local<v8::Function> function = | |
| 937 FunctionFromString(context, | |
| 938 "(function(obj) { return obj.oneString('ping'); })"); | |
| 939 v8::Local<v8::Value> args[] = {binding_object}; | |
| 940 v8::Local<v8::Value> result = | |
| 941 RunFunction(function, context, arraysize(args), args); | |
| 942 ASSERT_FALSE(result.IsEmpty()); | |
| 943 std::unique_ptr<base::Value> json_result = V8ToBaseValue(result, context); | |
| 944 ASSERT_TRUE(json_result); | |
| 945 EXPECT_EQ("\"ping pong\"", ValueToString(*json_result)); | |
| 946 } | |
| 947 | |
| 948 // Tests that JS custom hooks can throw exceptions for bad invocations. | |
| 949 TEST_F(APIBindingUnittest, TestThrowingFromCustomJSHook) { | |
| 950 // Our testing handlers for running functions expect a pre-determined success | |
| 951 // or failure. Since we're testing throwing exceptions here, we need a way of | |
| 952 // running that allows exceptions to be thrown, but we still expect most JS | |
| 953 // calls to succeed. | |
| 954 // TODO(devlin): This is a bit clunky. If we need to do this enough, we could | |
| 955 // figure out a different solution, like having a stack object for allowing | |
| 956 // errors/exceptions. But given this is the only place we need it so far, this | |
| 957 // is sufficient. | |
| 958 auto run_js_and_expect_error = [](v8::Local<v8::Function> function, | |
| 959 v8::Local<v8::Context> context, | |
| 960 int argc, | |
| 961 v8::Local<v8::Value> argv[]) { | |
| 962 v8::MaybeLocal<v8::Value> maybe_result = | |
| 963 function->Call(context, context->Global(), argc, argv); | |
| 964 v8::Global<v8::Value> result; | |
| 965 v8::Local<v8::Value> local; | |
| 966 if (maybe_result.ToLocal(&local)) | |
| 967 result.Reset(context->GetIsolate(), local); | |
| 968 return result; | |
| 969 }; | |
| 970 // Register a hook for the test.oneString method. | |
| 971 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 972 kBindingName, base::Bind(run_js_and_expect_error)); | |
| 973 | |
| 974 v8::HandleScope handle_scope(isolate()); | |
| 975 v8::Local<v8::Context> context = MainContext(); | |
| 976 { | |
| 977 const char kRegisterHook[] = | |
| 978 "(function(hooks) {\n" | |
| 979 " hooks.setHandleRequest('oneString', str => {\n" | |
| 980 " throw new Error('Custom Hook Error');\n" | |
| 981 " });\n" | |
| 982 "})"; | |
| 983 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 984 v8::Local<v8::Function> function = | |
| 985 FunctionFromString(context, kRegisterHook); | |
| 986 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 987 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 988 } | |
| 989 | |
| 990 SetHooks(std::move(hooks)); | |
| 991 SetFunctions(kFunctions); | |
| 992 InitializeBinding(); | |
| 993 | |
| 994 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 995 | |
| 996 v8::Local<v8::Function> function = | |
| 997 FunctionFromString(context, | |
| 998 "(function(obj) { return obj.oneString('ping'); })"); | |
| 999 v8::Local<v8::Value> args[] = {binding_object}; | |
| 1000 RunFunctionAndExpectError(function, context, v8::Undefined(isolate()), | |
| 1001 arraysize(args), args, | |
| 1002 "Uncaught Error: Custom Hook Error"); | |
| 1003 } | |
| 1004 | |
| 1005 // Tests that native custom hooks can return results synchronously, or throw | |
| 1006 // exceptions for bad invocations. | |
| 1007 TEST_F(APIBindingUnittest, | |
| 1008 TestReturningResultAndThrowingExceptionFromCustomNativeHook) { | |
| 1009 v8::HandleScope handle_scope(isolate()); | |
| 1010 v8::Local<v8::Context> context = MainContext(); | |
| 1011 | |
| 1012 // Register a hook for the test.oneString method. | |
| 1013 auto hooks = base::MakeUnique<APIBindingHooksTestDelegate>(); | |
| 1014 bool did_call = false; | |
| 1015 auto hook = [](bool* did_call, const APISignature* signature, | |
| 1016 v8::Local<v8::Context> context, | |
| 1017 std::vector<v8::Local<v8::Value>>* arguments, | |
| 1018 const APITypeReferenceMap& ref_map) { | |
| 1019 APIBindingHooks::RequestResult result( | |
| 1020 APIBindingHooks::RequestResult::HANDLED); | |
| 1021 if (arguments->size() != 1u) { // ASSERT* messes with the return type. | |
| 1022 EXPECT_EQ(1u, arguments->size()); | |
| 1023 return result; | |
| 1024 } | |
| 1025 v8::Isolate* isolate = context->GetIsolate(); | |
| 1026 std::string arg_value = gin::V8ToString(arguments->at(0)); | |
| 1027 if (arg_value == "throw") { | |
| 1028 isolate->ThrowException(v8::Exception::Error( | |
| 1029 gin::StringToV8(isolate, "Custom Hook Error"))); | |
| 1030 result.code = APIBindingHooks::RequestResult::THROWN; | |
| 1031 return result; | |
| 1032 } | |
| 1033 result.return_value = | |
| 1034 gin::StringToV8(context->GetIsolate(), arg_value + " pong"); | |
| 1035 return result; | |
| 1036 }; | |
| 1037 hooks->AddHandler("test.oneString", base::Bind(hook, &did_call)); | |
| 1038 | |
| 1039 SetHooksDelegate(std::move(hooks)); | |
| 1040 SetFunctions(kFunctions); | |
| 1041 InitializeBinding(); | |
| 1042 | |
| 1043 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 1044 | |
| 1045 { | |
| 1046 // Test an invocation that we expect to throw an exception. | |
| 1047 v8::Local<v8::Function> function = | |
| 1048 FunctionFromString( | |
| 1049 context, "(function(obj) { return obj.oneString('throw'); })"); | |
| 1050 v8::Local<v8::Value> args[] = {binding_object}; | |
| 1051 RunFunctionAndExpectError(function, context, v8::Undefined(isolate()), | |
| 1052 arraysize(args), args, | |
| 1053 "Uncaught Error: Custom Hook Error"); | |
| 1054 } | |
| 1055 | |
| 1056 { | |
| 1057 // Test an invocation we expect to succeed. | |
| 1058 v8::Local<v8::Function> function = | |
| 1059 FunctionFromString(context, | |
| 1060 "(function(obj) { return obj.oneString('ping'); })"); | |
| 1061 v8::Local<v8::Value> args[] = {binding_object}; | |
| 1062 v8::Local<v8::Value> result = | |
| 1063 RunFunction(function, context, arraysize(args), args); | |
| 1064 ASSERT_FALSE(result.IsEmpty()); | |
| 1065 std::unique_ptr<base::Value> json_result = V8ToBaseValue(result, context); | |
| 1066 ASSERT_TRUE(json_result); | |
| 1067 EXPECT_EQ("\"ping pong\"", ValueToString(*json_result)); | |
| 1068 } | |
| 1069 } | |
| 1070 | |
| 1071 // Tests the updateArgumentsPostValidate hook. | |
| 1072 TEST_F(APIBindingUnittest, TestUpdateArgumentsPostValidate) { | |
| 1073 // Register a hook for the test.oneString method. | |
| 1074 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 1075 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle)); | |
| 1076 | |
| 1077 v8::HandleScope handle_scope(isolate()); | |
| 1078 v8::Local<v8::Context> context = MainContext(); | |
| 1079 { | |
| 1080 const char kRegisterHook[] = | |
| 1081 "(function(hooks) {\n" | |
| 1082 " hooks.setUpdateArgumentsPostValidate('oneString', function() {\n" | |
| 1083 " this.requestArguments = Array.from(arguments);\n" | |
| 1084 " return ['pong'];\n" | |
| 1085 " });\n" | |
| 1086 "})"; | |
| 1087 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 1088 v8::Local<v8::Function> function = | |
| 1089 FunctionFromString(context, kRegisterHook); | |
| 1090 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 1091 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 1092 } | |
| 1093 | |
| 1094 SetHooks(std::move(hooks)); | |
| 1095 SetFunctions(kFunctions); | |
| 1096 InitializeBinding(); | |
| 1097 | |
| 1098 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 1099 | |
| 1100 // Try calling the method with an invalid signature. Since it's invalid, we | |
| 1101 // should never enter the hook. | |
| 1102 ExpectFailure( | |
| 1103 binding_object, "obj.oneString(false);", | |
| 1104 InvocationError( | |
| 1105 "test.oneString", "string str", | |
| 1106 ArgumentError("str", InvalidType(kTypeString, kTypeBoolean)))); | |
| 1107 EXPECT_EQ("undefined", GetStringPropertyFromObject( | |
| 1108 context->Global(), context, "requestArguments")); | |
| 1109 | |
| 1110 // Call the method with a valid signature. The hook should be entered and | |
| 1111 // manipulate the arguments. | |
| 1112 ExpectPass(binding_object, "obj.oneString('ping');", "['pong']", false); | |
| 1113 EXPECT_EQ("[\"ping\"]", GetStringPropertyFromObject( | |
| 1114 context->Global(), context, "requestArguments")); | |
| 1115 | |
| 1116 // Other methods, like stringAndInt(), should behave normally. | |
| 1117 ExpectPass(binding_object, "obj.stringAndInt('foo', 42);", | |
| 1118 "['foo',42]", false); | |
| 1119 } | |
| 1120 | |
| 1121 // Tests using setUpdateArgumentsPostValidate to return a list of arguments | |
| 1122 // that violates the function schema. Sadly, this should succeed. :( | |
| 1123 // See comment in api_binding.cc. | |
| 1124 TEST_F(APIBindingUnittest, TestUpdateArgumentsPostValidateViolatingSchema) { | |
| 1125 // Register a hook for the test.oneString method. | |
| 1126 auto hooks = base::MakeUnique<APIBindingHooks>( | |
| 1127 kBindingName, base::Bind(&RunFunctionOnGlobalAndReturnHandle)); | |
| 1128 | |
| 1129 v8::HandleScope handle_scope(isolate()); | |
| 1130 v8::Local<v8::Context> context = MainContext(); | |
| 1131 { | |
| 1132 const char kRegisterHook[] = | |
| 1133 "(function(hooks) {\n" | |
| 1134 " hooks.setUpdateArgumentsPostValidate('oneString', function() {\n" | |
| 1135 " return [{}];\n" | |
| 1136 " });\n" | |
| 1137 "})"; | |
| 1138 v8::Local<v8::Object> js_hooks = hooks->GetJSHookInterface(context); | |
| 1139 v8::Local<v8::Function> function = | |
| 1140 FunctionFromString(context, kRegisterHook); | |
| 1141 v8::Local<v8::Value> args[] = {js_hooks}; | |
| 1142 RunFunctionOnGlobal(function, context, arraysize(args), args); | |
| 1143 } | |
| 1144 | |
| 1145 SetHooks(std::move(hooks)); | |
| 1146 SetFunctions(kFunctions); | |
| 1147 InitializeBinding(); | |
| 1148 | |
| 1149 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 1150 | |
| 1151 // Call the method with a valid signature. The hook should be entered and | |
| 1152 // manipulate the arguments. | |
| 1153 ExpectPass(binding_object, "obj.oneString('ping');", "[{}]", false); | |
| 1154 } | |
| 1155 | |
| 1156 // Test that user gestures are properly recorded when calling APIs. | |
| 1157 TEST_F(APIBindingUnittest, TestUserGestures) { | |
| 1158 SetFunctions(kFunctions); | |
| 1159 InitializeBinding(); | |
| 1160 | |
| 1161 v8::HandleScope handle_scope(isolate()); | |
| 1162 v8::Local<v8::Context> context = MainContext(); | |
| 1163 | |
| 1164 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 1165 | |
| 1166 v8::Local<v8::Function> function = | |
| 1167 FunctionFromString(context, "(function(obj) { obj.oneString('foo');})"); | |
| 1168 ASSERT_FALSE(function.IsEmpty()); | |
| 1169 | |
| 1170 v8::Local<v8::Value> argv[] = {binding_object}; | |
| 1171 RunFunction(function, context, arraysize(argv), argv); | |
| 1172 ASSERT_TRUE(last_request()); | |
| 1173 EXPECT_FALSE(last_request()->has_user_gesture); | |
| 1174 reset_last_request(); | |
| 1175 | |
| 1176 blink::WebScopedUserGesture user_gesture(nullptr); | |
| 1177 RunFunction(function, context, arraysize(argv), argv); | |
| 1178 ASSERT_TRUE(last_request()); | |
| 1179 EXPECT_TRUE(last_request()->has_user_gesture); | |
| 1180 | |
| 1181 reset_last_request(); | |
| 1182 } | |
| 1183 | |
| 1184 TEST_F(APIBindingUnittest, FilteredEvents) { | |
| 1185 const char kEvents[] = | |
| 1186 "[{" | |
| 1187 " 'name': 'unfilteredOne'," | |
| 1188 " 'parameters': []" | |
| 1189 "}, {" | |
| 1190 " 'name': 'unfilteredTwo'," | |
| 1191 " 'filters': []," | |
| 1192 " 'parameters': []" | |
| 1193 "}, {" | |
| 1194 " 'name': 'unfilteredThree'," | |
| 1195 " 'options': {'supportsFilters': false}," | |
| 1196 " 'parameters': []" | |
| 1197 "}, {" | |
| 1198 " 'name': 'filteredOne'," | |
| 1199 " 'options': {'supportsFilters': true}," | |
| 1200 " 'parameters': []" | |
| 1201 "}, {" | |
| 1202 " 'name': 'filteredTwo'," | |
| 1203 " 'filters': [" | |
| 1204 " {'name': 'url', 'type': 'array', 'items': {'type': 'any'}}" | |
| 1205 " ]," | |
| 1206 " 'parameters': []" | |
| 1207 "}]"; | |
| 1208 SetEvents(kEvents); | |
| 1209 InitializeBinding(); | |
| 1210 | |
| 1211 v8::HandleScope handle_scope(isolate()); | |
| 1212 v8::Local<v8::Context> context = MainContext(); | |
| 1213 | |
| 1214 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 1215 | |
| 1216 const char kAddFilteredListener[] = | |
| 1217 "(function(evt) {\n" | |
| 1218 " evt.addListener(function() {},\n" | |
| 1219 " {url: [{pathContains: 'simple2.html'}]});\n" | |
| 1220 "})"; | |
| 1221 v8::Local<v8::Function> function = | |
| 1222 FunctionFromString(context, kAddFilteredListener); | |
| 1223 ASSERT_FALSE(function.IsEmpty()); | |
| 1224 | |
| 1225 auto check_supports_filters = [context, binding_object, function]( | |
| 1226 base::StringPiece name, | |
| 1227 bool expect_supports) { | |
| 1228 SCOPED_TRACE(name); | |
| 1229 v8::Local<v8::Value> event = | |
| 1230 GetPropertyFromObject(binding_object, context, name); | |
| 1231 v8::Local<v8::Value> args[] = {event}; | |
| 1232 if (expect_supports) { | |
| 1233 RunFunction(function, context, context->Global(), arraysize(args), args); | |
| 1234 } else { | |
| 1235 RunFunctionAndExpectError( | |
| 1236 function, context, context->Global(), arraysize(args), args, | |
| 1237 "Uncaught TypeError: This event does not support filters"); | |
| 1238 } | |
| 1239 }; | |
| 1240 | |
| 1241 check_supports_filters("unfilteredOne", false); | |
| 1242 check_supports_filters("unfilteredTwo", false); | |
| 1243 check_supports_filters("unfilteredThree", false); | |
| 1244 check_supports_filters("filteredOne", true); | |
| 1245 check_supports_filters("filteredTwo", true); | |
| 1246 } | |
| 1247 | |
| 1248 TEST_F(APIBindingUnittest, HooksTemplateInitializer) { | |
| 1249 SetFunctions(kFunctions); | |
| 1250 | |
| 1251 // Register a hook for the test.oneString method. | |
| 1252 auto hooks = base::MakeUnique<APIBindingHooksTestDelegate>(); | |
| 1253 auto hook = [](v8::Isolate* isolate, | |
| 1254 v8::Local<v8::ObjectTemplate> object_template, | |
| 1255 const APITypeReferenceMap& type_refs) { | |
| 1256 object_template->Set(gin::StringToSymbol(isolate, "hookedProperty"), | |
| 1257 gin::ConvertToV8(isolate, 42)); | |
| 1258 }; | |
| 1259 hooks->SetTemplateInitializer(base::Bind(hook)); | |
| 1260 SetHooksDelegate(std::move(hooks)); | |
| 1261 | |
| 1262 InitializeBinding(); | |
| 1263 | |
| 1264 v8::HandleScope handle_scope(isolate()); | |
| 1265 v8::Local<v8::Context> context = MainContext(); | |
| 1266 | |
| 1267 v8::Local<v8::Object> binding_object = binding()->CreateInstance(context); | |
| 1268 | |
| 1269 // The extra property should be present on the binding object. | |
| 1270 EXPECT_EQ("42", GetStringPropertyFromObject(binding_object, context, | |
| 1271 "hookedProperty")); | |
| 1272 // Sanity check: other values should still be there. | |
| 1273 EXPECT_EQ("function", | |
| 1274 GetStringPropertyFromObject(binding_object, context, "oneString")); | |
| 1275 } | |
| 1276 | |
| 1277 } // namespace extensions | |
| OLD | NEW |