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