| 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 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/logging.h" | |
| 11 #include "base/memory/ptr_util.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "base/strings/stringprintf.h" | |
| 14 #include "base/values.h" | |
| 15 #include "extensions/common/extension_api.h" | |
| 16 #include "extensions/renderer/api_binding_hooks.h" | |
| 17 #include "extensions/renderer/api_binding_types.h" | |
| 18 #include "extensions/renderer/api_event_handler.h" | |
| 19 #include "extensions/renderer/api_invocation_errors.h" | |
| 20 #include "extensions/renderer/api_request_handler.h" | |
| 21 #include "extensions/renderer/api_signature.h" | |
| 22 #include "extensions/renderer/api_type_reference_map.h" | |
| 23 #include "extensions/renderer/declarative_event.h" | |
| 24 #include "extensions/renderer/v8_helpers.h" | |
| 25 #include "gin/arguments.h" | |
| 26 #include "gin/handle.h" | |
| 27 #include "gin/per_context_data.h" | |
| 28 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h" | |
| 29 | |
| 30 namespace extensions { | |
| 31 | |
| 32 namespace { | |
| 33 | |
| 34 // Returns the name of the enum value for use in JavaScript; JS enum entries use | |
| 35 // SCREAMING_STYLE. | |
| 36 std::string GetJSEnumEntryName(const std::string& original) { | |
| 37 // The webstorePrivate API has an empty enum value for a result. | |
| 38 // TODO(devlin): Work with the webstore team to see if we can move them off | |
| 39 // this - they also already have a "success" result that they can use. | |
| 40 // See crbug.com/709120. | |
| 41 if (original.empty()) | |
| 42 return original; | |
| 43 | |
| 44 std::string result; | |
| 45 // If the original starts with a digit, prefix it with an underscore. | |
| 46 if (base::IsAsciiDigit(original[0])) | |
| 47 result.push_back('_'); | |
| 48 // Given 'myEnum-Foo': | |
| 49 for (size_t i = 0; i < original.size(); ++i) { | |
| 50 // Add an underscore between camelcased items: | |
| 51 // 'myEnum-Foo' -> 'mY_Enum-Foo' | |
| 52 if (i > 0 && base::IsAsciiLower(original[i - 1]) && | |
| 53 base::IsAsciiUpper(original[i])) { | |
| 54 result.push_back('_'); | |
| 55 result.push_back(original[i]); | |
| 56 } else if (original[i] == '-') { // 'mY_Enum-Foo' -> 'mY_Enum_Foo' | |
| 57 result.push_back('_'); | |
| 58 } else { // 'mY_Enum_Foo' -> 'MY_ENUM_FOO' | |
| 59 result.push_back(base::ToUpperASCII(original[i])); | |
| 60 } | |
| 61 } | |
| 62 return result; | |
| 63 } | |
| 64 | |
| 65 bool IsContextValid(v8::Local<v8::Context> context) { | |
| 66 // If the given context has been disposed, the per-context data has been | |
| 67 // deleted, and the context is no longer valid. The APIBinding (which owns | |
| 68 // various necessary pieces) should outlive all contexts, so if the context | |
| 69 // is valid, associated callbacks should be safe. | |
| 70 return gin::PerContextData::From(context) != nullptr; | |
| 71 } | |
| 72 | |
| 73 void CallbackHelper(const v8::FunctionCallbackInfo<v8::Value>& info) { | |
| 74 gin::Arguments args(info); | |
| 75 if (!IsContextValid(args.isolate()->GetCurrentContext())) | |
| 76 return; | |
| 77 | |
| 78 v8::Local<v8::External> external; | |
| 79 CHECK(args.GetData(&external)); | |
| 80 auto* callback = static_cast<APIBinding::HandlerCallback*>(external->Value()); | |
| 81 | |
| 82 callback->Run(&args); | |
| 83 } | |
| 84 | |
| 85 } // namespace | |
| 86 | |
| 87 struct APIBinding::MethodData { | |
| 88 MethodData(std::string full_name, | |
| 89 const APISignature* signature, | |
| 90 binding::RequestThread thread) | |
| 91 : full_name(std::move(full_name)), signature(signature), thread(thread) {} | |
| 92 | |
| 93 // The fully-qualified name of this api (e.g. runtime.sendMessage instead of | |
| 94 // sendMessage). | |
| 95 const std::string full_name; | |
| 96 // The expected API signature. | |
| 97 const APISignature* signature; | |
| 98 // Thread to invoke the method on. | |
| 99 const binding::RequestThread thread; | |
| 100 // The callback used by the v8 function. | |
| 101 APIBinding::HandlerCallback callback; | |
| 102 }; | |
| 103 | |
| 104 // TODO(devlin): Maybe separate EventData into two classes? Rules, actions, and | |
| 105 // conditions should never be present on vanilla events. | |
| 106 struct APIBinding::EventData { | |
| 107 EventData(std::string exposed_name, | |
| 108 std::string full_name, | |
| 109 bool supports_filters, | |
| 110 bool supports_rules, | |
| 111 int max_listeners, | |
| 112 bool notify_on_change, | |
| 113 std::vector<std::string> actions, | |
| 114 std::vector<std::string> conditions, | |
| 115 APIBinding* binding) | |
| 116 : exposed_name(std::move(exposed_name)), | |
| 117 full_name(std::move(full_name)), | |
| 118 supports_filters(supports_filters), | |
| 119 supports_rules(supports_rules), | |
| 120 max_listeners(max_listeners), | |
| 121 notify_on_change(notify_on_change), | |
| 122 actions(std::move(actions)), | |
| 123 conditions(std::move(conditions)), | |
| 124 binding(binding) {} | |
| 125 | |
| 126 // The name of the event on the API object (e.g. onCreated). | |
| 127 std::string exposed_name; | |
| 128 | |
| 129 // The fully-specified name of the event (e.g. tabs.onCreated). | |
| 130 std::string full_name; | |
| 131 | |
| 132 // Whether the event supports filters. | |
| 133 bool supports_filters; | |
| 134 | |
| 135 // Whether the event supports rules. | |
| 136 bool supports_rules; | |
| 137 | |
| 138 // The maximum number of listeners this event supports. | |
| 139 int max_listeners; | |
| 140 | |
| 141 // Whether to notify the browser of listener changes. | |
| 142 bool notify_on_change; | |
| 143 | |
| 144 // The associated actions and conditions for declarative events. | |
| 145 std::vector<std::string> actions; | |
| 146 std::vector<std::string> conditions; | |
| 147 | |
| 148 // The associated APIBinding. This raw pointer is safe because the | |
| 149 // EventData is only accessed from the callbacks associated with the | |
| 150 // APIBinding, and both the APIBinding and APIEventHandler are owned by the | |
| 151 // same object (the APIBindingsSystem). | |
| 152 APIBinding* binding; | |
| 153 }; | |
| 154 | |
| 155 struct APIBinding::CustomPropertyData { | |
| 156 CustomPropertyData(const std::string& type_name, | |
| 157 const std::string& property_name, | |
| 158 const base::ListValue* property_values, | |
| 159 const CreateCustomType& create_custom_type) | |
| 160 : type_name(type_name), | |
| 161 property_name(property_name), | |
| 162 property_values(property_values), | |
| 163 create_custom_type(create_custom_type) {} | |
| 164 | |
| 165 // The type of the property, e.g. 'storage.StorageArea'. | |
| 166 std::string type_name; | |
| 167 // The name of the property on the object, e.g. 'local' for | |
| 168 // chrome.storage.local. | |
| 169 std::string property_name; | |
| 170 // Values curried into this particular type from the schema. | |
| 171 const base::ListValue* property_values; | |
| 172 | |
| 173 CreateCustomType create_custom_type; | |
| 174 }; | |
| 175 | |
| 176 APIBinding::APIBinding(const std::string& api_name, | |
| 177 const base::ListValue* function_definitions, | |
| 178 const base::ListValue* type_definitions, | |
| 179 const base::ListValue* event_definitions, | |
| 180 const base::DictionaryValue* property_definitions, | |
| 181 const CreateCustomType& create_custom_type, | |
| 182 const AvailabilityCallback& is_available, | |
| 183 std::unique_ptr<APIBindingHooks> binding_hooks, | |
| 184 APITypeReferenceMap* type_refs, | |
| 185 APIRequestHandler* request_handler, | |
| 186 APIEventHandler* event_handler) | |
| 187 : api_name_(api_name), | |
| 188 property_definitions_(property_definitions), | |
| 189 create_custom_type_(create_custom_type), | |
| 190 is_available_(is_available), | |
| 191 binding_hooks_(std::move(binding_hooks)), | |
| 192 type_refs_(type_refs), | |
| 193 request_handler_(request_handler), | |
| 194 event_handler_(event_handler), | |
| 195 weak_factory_(this) { | |
| 196 // TODO(devlin): It might make sense to instantiate the object_template_ | |
| 197 // directly here, which would avoid the need to hold on to | |
| 198 // |property_definitions_| and |enums_|. However, there are *some* cases where | |
| 199 // we don't immediately stamp out an API from the template following | |
| 200 // construction. | |
| 201 | |
| 202 if (function_definitions) { | |
| 203 for (const auto& func : *function_definitions) { | |
| 204 const base::DictionaryValue* func_dict = nullptr; | |
| 205 CHECK(func.GetAsDictionary(&func_dict)); | |
| 206 std::string name; | |
| 207 CHECK(func_dict->GetString("name", &name)); | |
| 208 | |
| 209 const base::ListValue* params = nullptr; | |
| 210 CHECK(func_dict->GetList("parameters", ¶ms)); | |
| 211 | |
| 212 bool for_io_thread = false; | |
| 213 func_dict->GetBoolean("forIOThread", &for_io_thread); | |
| 214 | |
| 215 auto signature = base::MakeUnique<APISignature>(*params); | |
| 216 std::string full_name = | |
| 217 base::StringPrintf("%s.%s", api_name_.c_str(), name.c_str()); | |
| 218 methods_[name] = base::MakeUnique<MethodData>( | |
| 219 full_name, signature.get(), | |
| 220 for_io_thread ? binding::RequestThread::IO | |
| 221 : binding::RequestThread::UI); | |
| 222 type_refs->AddAPIMethodSignature(full_name, std::move(signature)); | |
| 223 } | |
| 224 } | |
| 225 | |
| 226 if (type_definitions) { | |
| 227 for (const auto& type : *type_definitions) { | |
| 228 const base::DictionaryValue* type_dict = nullptr; | |
| 229 CHECK(type.GetAsDictionary(&type_dict)); | |
| 230 std::string id; | |
| 231 CHECK(type_dict->GetString("id", &id)); | |
| 232 auto argument_spec = base::MakeUnique<ArgumentSpec>(*type_dict); | |
| 233 const std::set<std::string>& enum_values = argument_spec->enum_values(); | |
| 234 if (!enum_values.empty()) { | |
| 235 // Type names may be prefixed by the api name. If so, remove the prefix. | |
| 236 base::Optional<std::string> stripped_id; | |
| 237 if (base::StartsWith(id, api_name_, base::CompareCase::SENSITIVE)) | |
| 238 stripped_id = id.substr(api_name_.size() + 1); // +1 for trailing '.' | |
| 239 std::vector<EnumEntry>& entries = | |
| 240 enums_[stripped_id ? *stripped_id : id]; | |
| 241 entries.reserve(enum_values.size()); | |
| 242 for (const auto& enum_value : enum_values) { | |
| 243 entries.push_back( | |
| 244 std::make_pair(enum_value, GetJSEnumEntryName(enum_value))); | |
| 245 } | |
| 246 } | |
| 247 type_refs->AddSpec(id, std::move(argument_spec)); | |
| 248 // Some types, like storage.StorageArea, have functions associated with | |
| 249 // them. Cache the function signatures in the type map. | |
| 250 const base::ListValue* type_functions = nullptr; | |
| 251 if (type_dict->GetList("functions", &type_functions)) { | |
| 252 for (const auto& func : *type_functions) { | |
| 253 const base::DictionaryValue* func_dict = nullptr; | |
| 254 CHECK(func.GetAsDictionary(&func_dict)); | |
| 255 std::string function_name; | |
| 256 CHECK(func_dict->GetString("name", &function_name)); | |
| 257 | |
| 258 const base::ListValue* params = nullptr; | |
| 259 CHECK(func_dict->GetList("parameters", ¶ms)); | |
| 260 type_refs->AddTypeMethodSignature( | |
| 261 base::StringPrintf("%s.%s", id.c_str(), function_name.c_str()), | |
| 262 base::MakeUnique<APISignature>(*params)); | |
| 263 } | |
| 264 } | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 if (event_definitions) { | |
| 269 events_.reserve(event_definitions->GetSize()); | |
| 270 for (const auto& event : *event_definitions) { | |
| 271 const base::DictionaryValue* event_dict = nullptr; | |
| 272 CHECK(event.GetAsDictionary(&event_dict)); | |
| 273 std::string name; | |
| 274 CHECK(event_dict->GetString("name", &name)); | |
| 275 std::string full_name = | |
| 276 base::StringPrintf("%s.%s", api_name_.c_str(), name.c_str()); | |
| 277 const base::ListValue* filters = nullptr; | |
| 278 bool supports_filters = | |
| 279 event_dict->GetList("filters", &filters) && !filters->empty(); | |
| 280 | |
| 281 std::vector<std::string> rule_actions; | |
| 282 std::vector<std::string> rule_conditions; | |
| 283 const base::DictionaryValue* options = nullptr; | |
| 284 bool supports_rules = false; | |
| 285 bool notify_on_change = true; | |
| 286 int max_listeners = binding::kNoListenerMax; | |
| 287 if (event_dict->GetDictionary("options", &options)) { | |
| 288 bool temp_supports_filters = false; | |
| 289 // TODO(devlin): For some reason, schemas indicate supporting filters | |
| 290 // either through having a 'filters' property *or* through having | |
| 291 // a 'supportsFilters' property. We should clean that up. | |
| 292 supports_filters |= | |
| 293 (options->GetBoolean("supportsFilters", &temp_supports_filters) && | |
| 294 temp_supports_filters); | |
| 295 if (options->GetBoolean("supportsRules", &supports_rules) && | |
| 296 supports_rules) { | |
| 297 bool supports_listeners = false; | |
| 298 DCHECK(options->GetBoolean("supportsListeners", &supports_listeners)); | |
| 299 DCHECK(!supports_listeners) | |
| 300 << "Events cannot support rules and listeners."; | |
| 301 auto get_values = [options](base::StringPiece name, | |
| 302 std::vector<std::string>* out_value) { | |
| 303 const base::ListValue* list = nullptr; | |
| 304 CHECK(options->GetList(name, &list)); | |
| 305 for (const auto& entry : *list) { | |
| 306 DCHECK(entry.is_string()); | |
| 307 out_value->push_back(entry.GetString()); | |
| 308 } | |
| 309 }; | |
| 310 get_values("actions", &rule_actions); | |
| 311 get_values("conditions", &rule_conditions); | |
| 312 } | |
| 313 | |
| 314 options->GetInteger("maxListeners", &max_listeners); | |
| 315 bool unmanaged = false; | |
| 316 if (options->GetBoolean("unmanaged", &unmanaged)) | |
| 317 notify_on_change = !unmanaged; | |
| 318 } | |
| 319 | |
| 320 events_.push_back(base::MakeUnique<EventData>( | |
| 321 std::move(name), std::move(full_name), supports_filters, | |
| 322 supports_rules, max_listeners, notify_on_change, | |
| 323 std::move(rule_actions), std::move(rule_conditions), this)); | |
| 324 } | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 APIBinding::~APIBinding() {} | |
| 329 | |
| 330 v8::Local<v8::Object> APIBinding::CreateInstance( | |
| 331 v8::Local<v8::Context> context) { | |
| 332 DCHECK(IsContextValid(context)); | |
| 333 v8::Isolate* isolate = context->GetIsolate(); | |
| 334 if (object_template_.IsEmpty()) | |
| 335 InitializeTemplate(isolate); | |
| 336 DCHECK(!object_template_.IsEmpty()); | |
| 337 | |
| 338 v8::Local<v8::Object> object = | |
| 339 object_template_.Get(isolate)->NewInstance(context).ToLocalChecked(); | |
| 340 | |
| 341 // The object created from the template includes all methods, but some may | |
| 342 // be unavailable in this context. Iterate over them and delete any that | |
| 343 // aren't available. | |
| 344 // TODO(devlin): Ideally, we'd only do this check on the methods that are | |
| 345 // conditionally exposed. Or, we could have multiple templates for different | |
| 346 // configurations, assuming there are a small number of possibilities. | |
| 347 for (const auto& key_value : methods_) { | |
| 348 if (!is_available_.Run(context, key_value.second->full_name)) { | |
| 349 v8::Maybe<bool> success = object->Delete( | |
| 350 context, gin::StringToSymbol(isolate, key_value.first)); | |
| 351 CHECK(success.IsJust()); | |
| 352 CHECK(success.FromJust()); | |
| 353 } | |
| 354 } | |
| 355 for (const auto& event : events_) { | |
| 356 if (!is_available_.Run(context, event->full_name)) { | |
| 357 v8::Maybe<bool> success = object->Delete( | |
| 358 context, gin::StringToSymbol(isolate, event->exposed_name)); | |
| 359 CHECK(success.IsJust()); | |
| 360 CHECK(success.FromJust()); | |
| 361 } | |
| 362 } | |
| 363 | |
| 364 return object; | |
| 365 } | |
| 366 | |
| 367 void APIBinding::InitializeTemplate(v8::Isolate* isolate) { | |
| 368 DCHECK(object_template_.IsEmpty()); | |
| 369 v8::Local<v8::ObjectTemplate> object_template = | |
| 370 v8::ObjectTemplate::New(isolate); | |
| 371 | |
| 372 for (const auto& key_value : methods_) { | |
| 373 MethodData& method = *key_value.second; | |
| 374 DCHECK(method.callback.is_null()); | |
| 375 method.callback = | |
| 376 base::Bind(&APIBinding::HandleCall, weak_factory_.GetWeakPtr(), | |
| 377 method.full_name, method.signature, method.thread); | |
| 378 | |
| 379 object_template->Set( | |
| 380 gin::StringToSymbol(isolate, key_value.first), | |
| 381 v8::FunctionTemplate::New(isolate, &CallbackHelper, | |
| 382 v8::External::New(isolate, &method.callback), | |
| 383 v8::Local<v8::Signature>(), 0, | |
| 384 v8::ConstructorBehavior::kThrow)); | |
| 385 } | |
| 386 | |
| 387 for (const auto& event : events_) { | |
| 388 object_template->SetLazyDataProperty( | |
| 389 gin::StringToSymbol(isolate, event->exposed_name), | |
| 390 &APIBinding::GetEventObject, v8::External::New(isolate, event.get())); | |
| 391 } | |
| 392 | |
| 393 for (const auto& entry : enums_) { | |
| 394 v8::Local<v8::ObjectTemplate> enum_object = | |
| 395 v8::ObjectTemplate::New(isolate); | |
| 396 for (const auto& enum_entry : entry.second) { | |
| 397 enum_object->Set(gin::StringToSymbol(isolate, enum_entry.second), | |
| 398 gin::StringToSymbol(isolate, enum_entry.first)); | |
| 399 } | |
| 400 object_template->Set(isolate, entry.first.c_str(), enum_object); | |
| 401 } | |
| 402 | |
| 403 if (property_definitions_) { | |
| 404 DecorateTemplateWithProperties(isolate, object_template, | |
| 405 *property_definitions_); | |
| 406 } | |
| 407 | |
| 408 // Allow custom bindings a chance to tweak the template, such as to add | |
| 409 // additional properties or types. | |
| 410 binding_hooks_->InitializeTemplate(isolate, object_template, *type_refs_); | |
| 411 | |
| 412 object_template_.Set(isolate, object_template); | |
| 413 } | |
| 414 | |
| 415 void APIBinding::DecorateTemplateWithProperties( | |
| 416 v8::Isolate* isolate, | |
| 417 v8::Local<v8::ObjectTemplate> object_template, | |
| 418 const base::DictionaryValue& properties) { | |
| 419 static const char kValueKey[] = "value"; | |
| 420 for (base::DictionaryValue::Iterator iter(properties); !iter.IsAtEnd(); | |
| 421 iter.Advance()) { | |
| 422 const base::DictionaryValue* dict = nullptr; | |
| 423 CHECK(iter.value().GetAsDictionary(&dict)); | |
| 424 bool optional = false; | |
| 425 if (dict->GetBoolean("optional", &optional)) { | |
| 426 // TODO(devlin): What does optional even mean here? It's only used, it | |
| 427 // seems, for lastError and inIncognitoContext, which are both handled | |
| 428 // with custom bindings. Investigate, and remove. | |
| 429 continue; | |
| 430 } | |
| 431 | |
| 432 v8::Local<v8::String> v8_key = gin::StringToSymbol(isolate, iter.key()); | |
| 433 std::string ref; | |
| 434 if (dict->GetString("$ref", &ref)) { | |
| 435 const base::ListValue* property_values = nullptr; | |
| 436 CHECK(dict->GetList("value", &property_values)); | |
| 437 auto property_data = base::MakeUnique<CustomPropertyData>( | |
| 438 ref, iter.key(), property_values, create_custom_type_); | |
| 439 object_template->SetLazyDataProperty( | |
| 440 v8_key, &APIBinding::GetCustomPropertyObject, | |
| 441 v8::External::New(isolate, property_data.get())); | |
| 442 custom_properties_.push_back(std::move(property_data)); | |
| 443 continue; | |
| 444 } | |
| 445 | |
| 446 std::string type; | |
| 447 CHECK(dict->GetString("type", &type)); | |
| 448 if (type != "object" && !dict->HasKey(kValueKey)) { | |
| 449 // TODO(devlin): What does a fundamental property not having a value mean? | |
| 450 // It doesn't seem useful, and looks like it's only used by runtime.id, | |
| 451 // which is set by custom bindings. Investigate, and remove. | |
| 452 continue; | |
| 453 } | |
| 454 if (type == "integer") { | |
| 455 int val = 0; | |
| 456 CHECK(dict->GetInteger(kValueKey, &val)); | |
| 457 object_template->Set(v8_key, v8::Integer::New(isolate, val)); | |
| 458 } else if (type == "boolean") { | |
| 459 bool val = false; | |
| 460 CHECK(dict->GetBoolean(kValueKey, &val)); | |
| 461 object_template->Set(v8_key, v8::Boolean::New(isolate, val)); | |
| 462 } else if (type == "string") { | |
| 463 std::string val; | |
| 464 CHECK(dict->GetString(kValueKey, &val)) << iter.key(); | |
| 465 object_template->Set(v8_key, gin::StringToSymbol(isolate, val)); | |
| 466 } else if (type == "object" || !ref.empty()) { | |
| 467 v8::Local<v8::ObjectTemplate> property_template = | |
| 468 v8::ObjectTemplate::New(isolate); | |
| 469 const base::DictionaryValue* property_dict = nullptr; | |
| 470 CHECK(dict->GetDictionary("properties", &property_dict)); | |
| 471 DecorateTemplateWithProperties(isolate, property_template, | |
| 472 *property_dict); | |
| 473 object_template->Set(v8_key, property_template); | |
| 474 } | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 // static | |
| 479 void APIBinding::GetEventObject( | |
| 480 v8::Local<v8::Name> property, | |
| 481 const v8::PropertyCallbackInfo<v8::Value>& info) { | |
| 482 v8::Isolate* isolate = info.GetIsolate(); | |
| 483 v8::HandleScope handle_scope(isolate); | |
| 484 v8::Local<v8::Context> context = info.Holder()->CreationContext(); | |
| 485 if (!IsContextValid(context)) | |
| 486 return; | |
| 487 | |
| 488 CHECK(info.Data()->IsExternal()); | |
| 489 auto* event_data = | |
| 490 static_cast<EventData*>(info.Data().As<v8::External>()->Value()); | |
| 491 v8::Local<v8::Value> retval; | |
| 492 if (event_data->binding->binding_hooks_->CreateCustomEvent( | |
| 493 context, event_data->full_name, &retval)) { | |
| 494 // A custom event was created; our work is done. | |
| 495 } else if (event_data->supports_rules) { | |
| 496 gin::Handle<DeclarativeEvent> event = gin::CreateHandle( | |
| 497 isolate, new DeclarativeEvent( | |
| 498 event_data->full_name, event_data->binding->type_refs_, | |
| 499 event_data->binding->request_handler_, event_data->actions, | |
| 500 event_data->conditions, 0)); | |
| 501 retval = event.ToV8(); | |
| 502 } else { | |
| 503 retval = event_data->binding->event_handler_->CreateEventInstance( | |
| 504 event_data->full_name, event_data->supports_filters, | |
| 505 event_data->max_listeners, event_data->notify_on_change, context); | |
| 506 } | |
| 507 info.GetReturnValue().Set(retval); | |
| 508 } | |
| 509 | |
| 510 void APIBinding::GetCustomPropertyObject( | |
| 511 v8::Local<v8::Name> property_name, | |
| 512 const v8::PropertyCallbackInfo<v8::Value>& info) { | |
| 513 v8::Isolate* isolate = info.GetIsolate(); | |
| 514 v8::HandleScope handle_scope(isolate); | |
| 515 v8::Local<v8::Context> context = info.Holder()->CreationContext(); | |
| 516 if (!IsContextValid(context)) | |
| 517 return; | |
| 518 | |
| 519 v8::Context::Scope context_scope(context); | |
| 520 CHECK(info.Data()->IsExternal()); | |
| 521 auto* property_data = | |
| 522 static_cast<CustomPropertyData*>(info.Data().As<v8::External>()->Value()); | |
| 523 | |
| 524 v8::Local<v8::Object> property = property_data->create_custom_type.Run( | |
| 525 isolate, property_data->type_name, property_data->property_name, | |
| 526 property_data->property_values); | |
| 527 if (property.IsEmpty()) | |
| 528 return; | |
| 529 | |
| 530 info.GetReturnValue().Set(property); | |
| 531 } | |
| 532 | |
| 533 void APIBinding::HandleCall(const std::string& name, | |
| 534 const APISignature* signature, | |
| 535 const binding::RequestThread thread, | |
| 536 gin::Arguments* arguments) { | |
| 537 std::string error; | |
| 538 v8::Isolate* isolate = arguments->isolate(); | |
| 539 v8::HandleScope handle_scope(isolate); | |
| 540 | |
| 541 // Since this is called synchronously from the JS entry point, | |
| 542 // GetCurrentContext() should always be correct. | |
| 543 v8::Local<v8::Context> context = isolate->GetCurrentContext(); | |
| 544 | |
| 545 if (!is_available_.Run(context, name)) { | |
| 546 // TODO(devlin): Do we need handle this for events as well? I'm not sure the | |
| 547 // currrent system does (though perhaps it should). Investigate. | |
| 548 isolate->ThrowException(v8::Exception::Error(gin::StringToV8( | |
| 549 isolate, base::StringPrintf("'%s' is not available in this context.", | |
| 550 name.c_str())))); | |
| 551 return; | |
| 552 } | |
| 553 | |
| 554 std::vector<v8::Local<v8::Value>> argument_list = arguments->GetAll(); | |
| 555 | |
| 556 bool invalid_invocation = false; | |
| 557 v8::Local<v8::Function> custom_callback; | |
| 558 bool updated_args = false; | |
| 559 { | |
| 560 v8::TryCatch try_catch(isolate); | |
| 561 APIBindingHooks::RequestResult hooks_result = binding_hooks_->RunHooks( | |
| 562 name, context, signature, &argument_list, *type_refs_); | |
| 563 | |
| 564 switch (hooks_result.code) { | |
| 565 case APIBindingHooks::RequestResult::INVALID_INVOCATION: | |
| 566 invalid_invocation = true; | |
| 567 error = std::move(hooks_result.error); | |
| 568 // Throw a type error below so that it's not caught by our try-catch. | |
| 569 break; | |
| 570 case APIBindingHooks::RequestResult::THROWN: | |
| 571 DCHECK(try_catch.HasCaught()); | |
| 572 try_catch.ReThrow(); | |
| 573 return; | |
| 574 case APIBindingHooks::RequestResult::HANDLED: | |
| 575 if (!hooks_result.return_value.IsEmpty()) | |
| 576 arguments->Return(hooks_result.return_value); | |
| 577 return; // Our work here is done. | |
| 578 case APIBindingHooks::RequestResult::ARGUMENTS_UPDATED: | |
| 579 updated_args = true; // Intentional fall-through. | |
| 580 case APIBindingHooks::RequestResult::NOT_HANDLED: | |
| 581 break; // Handle in the default manner. | |
| 582 } | |
| 583 custom_callback = hooks_result.custom_callback; | |
| 584 } | |
| 585 | |
| 586 if (invalid_invocation) { | |
| 587 arguments->ThrowTypeError(api_errors::InvocationError( | |
| 588 name, signature->GetExpectedSignature(), error)); | |
| 589 return; | |
| 590 } | |
| 591 | |
| 592 std::unique_ptr<base::ListValue> converted_arguments; | |
| 593 v8::Local<v8::Function> callback; | |
| 594 { | |
| 595 v8::TryCatch try_catch(isolate); | |
| 596 | |
| 597 // If custom hooks updated the arguments post-validation, we just trust the | |
| 598 // values the hooks provide and convert them directly. This is because some | |
| 599 // APIs have one set of values they use for validation, and a second they | |
| 600 // use in the implementation of the function (see, e.g. | |
| 601 // fileSystem.getDisplayPath). | |
| 602 // TODO(devlin): That's unfortunate. Not only does it require special casing | |
| 603 // here, but it also means we can't auto-generate the params for the | |
| 604 // function on the browser side. | |
| 605 if (updated_args) { | |
| 606 bool success = signature->ConvertArgumentsIgnoringSchema( | |
| 607 context, argument_list, &converted_arguments, &callback); | |
| 608 if (!success) { | |
| 609 // Converted arguments passed to us by our bindings should never fail. | |
| 610 NOTREACHED(); | |
| 611 return; | |
| 612 } | |
| 613 } else { | |
| 614 invalid_invocation = !signature->ParseArgumentsToJSON( | |
| 615 context, argument_list, *type_refs_, &converted_arguments, &callback, | |
| 616 &error); | |
| 617 } | |
| 618 | |
| 619 if (try_catch.HasCaught()) { | |
| 620 DCHECK(!converted_arguments); | |
| 621 try_catch.ReThrow(); | |
| 622 return; | |
| 623 } | |
| 624 } | |
| 625 if (invalid_invocation) { | |
| 626 arguments->ThrowTypeError(api_errors::InvocationError( | |
| 627 name, signature->GetExpectedSignature(), error)); | |
| 628 return; | |
| 629 } | |
| 630 | |
| 631 request_handler_->StartRequest(context, name, std::move(converted_arguments), | |
| 632 callback, custom_callback, thread); | |
| 633 } | |
| 634 | |
| 635 } // namespace extensions | |
| OLD | NEW |