|  | // Copyright 2016 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "extensions/renderer/bindings/api_binding.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <string_view> | 
|  |  | 
|  | #include "base/check.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/strings/strcat.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/values.h" | 
|  | #include "extensions/renderer/bindings/api_binding_hooks.h" | 
|  | #include "extensions/renderer/bindings/api_binding_types.h" | 
|  | #include "extensions/renderer/bindings/api_binding_util.h" | 
|  | #include "extensions/renderer/bindings/api_event_handler.h" | 
|  | #include "extensions/renderer/bindings/api_invocation_errors.h" | 
|  | #include "extensions/renderer/bindings/api_request_handler.h" | 
|  | #include "extensions/renderer/bindings/api_signature.h" | 
|  | #include "extensions/renderer/bindings/api_type_reference_map.h" | 
|  | #include "extensions/renderer/bindings/binding_access_checker.h" | 
|  | #include "extensions/renderer/bindings/declarative_event.h" | 
|  | #include "gin/arguments.h" | 
|  | #include "gin/per_context_data.h" | 
|  | #include "gin/public/gin_embedders.h" | 
|  | #include "v8/include/cppgc/allocation.h" | 
|  | #include "v8/include/v8-cppgc.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Returns the name of the enum value for use in JavaScript; JS enum entries use | 
|  | // SCREAMING_STYLE. | 
|  | std::string GetJSEnumEntryName(const std::string& original) { | 
|  | // The webstorePrivate API has an empty enum value for a result. | 
|  | // TODO(devlin): Work with the webstore team to see if we can move them off | 
|  | // this - they also already have a "success" result that they can use. | 
|  | // See crbug.com/709120. | 
|  | if (original.empty()) | 
|  | return original; | 
|  |  | 
|  | std::string result; | 
|  | // If the original starts with a digit, prefix it with an underscore. | 
|  | if (base::IsAsciiDigit(original[0])) | 
|  | result.push_back('_'); | 
|  | // Given 'myEnum-Foo': | 
|  | for (size_t i = 0; i < original.size(); ++i) { | 
|  | // Add an underscore between camelcased items: | 
|  | // 'myEnum-Foo' -> 'mY_Enum-Foo' | 
|  | if (i > 0 && base::IsAsciiLower(original[i - 1]) && | 
|  | base::IsAsciiUpper(original[i])) { | 
|  | result.push_back('_'); | 
|  | result.push_back(original[i]); | 
|  | } else if (original[i] == '-') {  // 'mY_Enum-Foo' -> 'mY_Enum_Foo' | 
|  | result.push_back('_'); | 
|  | } else {  // 'mY_Enum_Foo' -> 'MY_ENUM_FOO' | 
|  | result.push_back(base::ToUpperASCII(original[i])); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<APISignature> GetAPISignatureFromDictionary( | 
|  | const base::Value::Dict* dict, | 
|  | BindingAccessChecker* access_checker) { | 
|  | const base::Value* params = dict->Find("parameters"); | 
|  | if (params && !params->is_list()) | 
|  | params = nullptr; | 
|  | CHECK(params); | 
|  |  | 
|  | const base::Value* returns_async = dict->Find("returns_async"); | 
|  | if (returns_async && !returns_async->is_dict()) | 
|  | returns_async = nullptr; | 
|  |  | 
|  | return APISignature::CreateFromValues(*params, returns_async, access_checker); | 
|  | } | 
|  |  | 
|  | void RunAPIBindingHandlerCallback( | 
|  | const v8::FunctionCallbackInfo<v8::Value>& info) { | 
|  | gin::Arguments args(info); | 
|  | if (!binding::IsContextValidOrThrowError(args.isolate()->GetCurrentContext())) | 
|  | return; | 
|  |  | 
|  | v8::Local<v8::External> external; | 
|  | CHECK(args.GetData(&external)); | 
|  | auto* callback = static_cast<APIBinding::HandlerCallback*>( | 
|  | external->Value(gin::kAPIBindingHandlerCallbackTag)); | 
|  |  | 
|  | callback->Run(&args); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | struct APIBinding::MethodData { | 
|  | MethodData(std::string full_name, const APISignature* signature) | 
|  | : full_name(std::move(full_name)), signature(signature) {} | 
|  |  | 
|  | // The fully-qualified name of this api (e.g. runtime.sendMessage instead of | 
|  | // sendMessage). | 
|  | const std::string full_name; | 
|  | // The expected API signature. | 
|  | raw_ptr<const APISignature> signature; | 
|  | // The callback used by the v8 function. | 
|  | APIBinding::HandlerCallback callback; | 
|  | }; | 
|  |  | 
|  | // TODO(devlin): Maybe separate EventData into two classes? Rules, actions, and | 
|  | // conditions should never be present on vanilla events. | 
|  | struct APIBinding::EventData { | 
|  | EventData(std::string exposed_name, | 
|  | std::string full_name, | 
|  | bool supports_filters, | 
|  | bool supports_rules, | 
|  | bool supports_lazy_listeners, | 
|  | int max_listeners, | 
|  | bool notify_on_change, | 
|  | std::vector<std::string> actions, | 
|  | std::vector<std::string> conditions, | 
|  | APIBinding* binding) | 
|  | : exposed_name(std::move(exposed_name)), | 
|  | full_name(std::move(full_name)), | 
|  | supports_filters(supports_filters), | 
|  | supports_rules(supports_rules), | 
|  | supports_lazy_listeners(supports_lazy_listeners), | 
|  | max_listeners(max_listeners), | 
|  | notify_on_change(notify_on_change), | 
|  | actions(std::move(actions)), | 
|  | conditions(std::move(conditions)), | 
|  | binding(binding) {} | 
|  |  | 
|  | // The name of the event on the API object (e.g. onCreated). | 
|  | std::string exposed_name; | 
|  |  | 
|  | // The fully-specified name of the event (e.g. tabs.onCreated). | 
|  | std::string full_name; | 
|  |  | 
|  | // Whether the event supports filters. | 
|  | bool supports_filters; | 
|  |  | 
|  | // Whether the event supports rules. | 
|  | bool supports_rules; | 
|  |  | 
|  | // Whether the event supports lazy listeners. | 
|  | bool supports_lazy_listeners; | 
|  |  | 
|  | // The maximum number of listeners this event supports. | 
|  | int max_listeners; | 
|  |  | 
|  | // Whether to notify the browser of listener changes. | 
|  | bool notify_on_change; | 
|  |  | 
|  | // The associated actions and conditions for declarative events. | 
|  | std::vector<std::string> actions; | 
|  | std::vector<std::string> conditions; | 
|  |  | 
|  | // The associated APIBinding. This raw pointer is safe because the | 
|  | // EventData is only accessed from the callbacks associated with the | 
|  | // APIBinding, and both the APIBinding and APIEventHandler are owned by the | 
|  | // same object (the APIBindingsSystem). | 
|  | raw_ptr<APIBinding> binding; | 
|  | }; | 
|  |  | 
|  | struct APIBinding::CustomPropertyData { | 
|  | CustomPropertyData(const std::string& type_name, | 
|  | const std::string& property_name, | 
|  | const base::Value::List* property_values, | 
|  | const CreateCustomType& create_custom_type) | 
|  | : type_name(type_name), | 
|  | property_name(property_name), | 
|  | property_values(property_values), | 
|  | create_custom_type(create_custom_type) {} | 
|  |  | 
|  | // The type of the property, e.g. 'storage.StorageArea'. | 
|  | std::string type_name; | 
|  | // The name of the property on the object, e.g. 'local' for | 
|  | // chrome.storage.local. | 
|  | std::string property_name; | 
|  | // Values curried into this particular type from the schema. | 
|  | raw_ptr<const base::Value::List> property_values; | 
|  |  | 
|  | CreateCustomType create_custom_type; | 
|  | }; | 
|  |  | 
|  | APIBinding::APIBinding(const std::string& api_name, | 
|  | const base::Value::List* function_definitions, | 
|  | const base::Value::List* type_definitions, | 
|  | const base::Value::List* event_definitions, | 
|  | const base::Value::Dict* property_definitions, | 
|  | CreateCustomType create_custom_type, | 
|  | OnSilentRequest on_silent_request, | 
|  | std::unique_ptr<APIBindingHooks> binding_hooks, | 
|  | APITypeReferenceMap* type_refs, | 
|  | APIRequestHandler* request_handler, | 
|  | APIEventHandler* event_handler, | 
|  | BindingAccessChecker* access_checker) | 
|  | : api_name_(api_name), | 
|  | property_definitions_(property_definitions), | 
|  | create_custom_type_(std::move(create_custom_type)), | 
|  | on_silent_request_(std::move(on_silent_request)), | 
|  | binding_hooks_(std::move(binding_hooks)), | 
|  | type_refs_(type_refs), | 
|  | request_handler_(request_handler), | 
|  | event_handler_(event_handler), | 
|  | access_checker_(access_checker) { | 
|  | // TODO(devlin): It might make sense to instantiate the object_template_ | 
|  | // directly here, which would avoid the need to hold on to | 
|  | // |property_definitions_| and |enums_|. However, there are *some* cases where | 
|  | // we don't immediately stamp out an API from the template following | 
|  | // construction. | 
|  |  | 
|  | if (function_definitions) { | 
|  | for (const auto& func : *function_definitions) { | 
|  | const base::Value::Dict* func_dict = func.GetIfDict(); | 
|  | CHECK(func_dict); | 
|  | const std::string* name = func_dict->FindString("name"); | 
|  | CHECK(name); | 
|  | std::string full_name = | 
|  | base::StringPrintf("%s.%s", api_name_.c_str(), name->c_str()); | 
|  |  | 
|  | auto signature = GetAPISignatureFromDictionary(func_dict, access_checker); | 
|  |  | 
|  | methods_[*name] = | 
|  | std::make_unique<MethodData>(full_name, signature.get()); | 
|  | type_refs->AddAPIMethodSignature(full_name, std::move(signature)); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (type_definitions) { | 
|  | for (const auto& type : *type_definitions) { | 
|  | const base::Value::Dict& type_dict = type.GetDict(); | 
|  | const std::string* id = type_dict.FindString("id"); | 
|  | CHECK(id); | 
|  | auto argument_spec = std::make_unique<ArgumentSpec>(type_dict); | 
|  | const std::set<std::string>& enum_values = argument_spec->enum_values(); | 
|  | if (!enum_values.empty()) { | 
|  | // Type names may be prefixed by the api name. If so, remove the prefix. | 
|  | std::optional<std::string> stripped_id; | 
|  | if (base::StartsWith(*id, api_name_, base::CompareCase::SENSITIVE)) | 
|  | stripped_id = | 
|  | id->substr(api_name_.size() + 1);  // +1 for trailing '.' | 
|  | std::vector<EnumEntry>& entries = | 
|  | enums_[stripped_id ? *stripped_id : *id]; | 
|  | entries.reserve(enum_values.size()); | 
|  | for (const auto& enum_value : enum_values) { | 
|  | entries.push_back( | 
|  | std::make_pair(enum_value, GetJSEnumEntryName(enum_value))); | 
|  | } | 
|  | } | 
|  | type_refs->AddSpec(*id, std::move(argument_spec)); | 
|  | // Some types, like storage.StorageArea, have functions associated with | 
|  | // them. Cache the function signatures in the type map. | 
|  | const base::Value::List* type_functions = type_dict.FindList("functions"); | 
|  | if (type_functions) { | 
|  | for (const auto& func : *type_functions) { | 
|  | const base::Value::Dict* func_dict = func.GetIfDict(); | 
|  | CHECK(func_dict); | 
|  | const std::string* function_name = func_dict->FindString("name"); | 
|  | CHECK(function_name); | 
|  | std::string full_name = | 
|  | base::StringPrintf("%s.%s", id->c_str(), function_name->c_str()); | 
|  |  | 
|  | auto signature = | 
|  | GetAPISignatureFromDictionary(func_dict, access_checker); | 
|  |  | 
|  | type_refs->AddTypeMethodSignature(full_name, std::move(signature)); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (event_definitions) { | 
|  | events_.reserve(event_definitions->size()); | 
|  | for (const auto& event : *event_definitions) { | 
|  | const base::Value::Dict* event_dict = event.GetIfDict(); | 
|  | CHECK(event_dict); | 
|  | const std::string* name = event_dict->FindString("name"); | 
|  | CHECK(name); | 
|  | std::string full_name = | 
|  | base::StringPrintf("%s.%s", api_name_.c_str(), name->c_str()); | 
|  | const base::Value::List* filters = event_dict->FindList("filters"); | 
|  | bool supports_filters = filters && !filters->empty(); | 
|  |  | 
|  | std::vector<std::string> rule_actions; | 
|  | std::vector<std::string> rule_conditions; | 
|  | const base::Value::Dict* options = event_dict->FindDict("options"); | 
|  | bool supports_rules = false; | 
|  | bool notify_on_change = true; | 
|  | bool supports_lazy_listeners = true; | 
|  | int max_listeners = binding::kNoListenerMax; | 
|  | if (options) { | 
|  | // TODO(devlin): For some reason, schemas indicate supporting filters | 
|  | // either through having a 'filters' property *or* through having | 
|  | // a 'supportsFilters' property. We should clean that up. | 
|  | supports_filters |= | 
|  | options->FindBool("supportsFilters").value_or(false); | 
|  | supports_rules = options->FindBool("supportsRules").value_or(false); | 
|  | if (supports_rules) { | 
|  | std::optional<bool> supports_listeners = | 
|  | options->FindBool("supportsListeners"); | 
|  | DCHECK(supports_listeners); | 
|  | DCHECK(!*supports_listeners) | 
|  | << "Events cannot support rules and listeners."; | 
|  | auto get_values = [options](std::string_view name, | 
|  | std::vector<std::string>* out_value) { | 
|  | const base::Value::List* list = options->FindList(name); | 
|  | CHECK(list); | 
|  | for (const auto& entry : *list) { | 
|  | DCHECK(entry.is_string()); | 
|  | out_value->push_back(entry.GetString()); | 
|  | } | 
|  | }; | 
|  | get_values("actions", &rule_actions); | 
|  | get_values("conditions", &rule_conditions); | 
|  | } | 
|  |  | 
|  | std::optional<int> max_listeners_option = | 
|  | options->FindInt("maxListeners"); | 
|  | if (max_listeners_option) | 
|  | max_listeners = *max_listeners_option; | 
|  | std::optional<bool> unmanaged = options->FindBool("unmanaged"); | 
|  | if (unmanaged) | 
|  | notify_on_change = !*unmanaged; | 
|  |  | 
|  | std::optional<bool> supports_lazy_listeners_value = | 
|  | options->FindBool("supportsLazyListeners"); | 
|  | if (supports_lazy_listeners_value) { | 
|  | supports_lazy_listeners = *supports_lazy_listeners_value; | 
|  | DCHECK(!supports_lazy_listeners) | 
|  | << "Don't specify supportsLazyListeners: true; it's the default."; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (binding::IsResponseValidationEnabled()) { | 
|  | const base::Value* params = event_dict->Find("parameters"); | 
|  | if (params && !params->is_list()) | 
|  | params = nullptr; | 
|  | // NOTE: At least in tests, events may omit "parameters". It's unclear | 
|  | // if real schemas do, too. For now, sub in an empty list if necessary. | 
|  | // TODO(devlin): Track this down and CHECK(params). | 
|  | base::Value empty_params(base::Value::Type::LIST); | 
|  | std::unique_ptr<APISignature> event_signature = | 
|  | APISignature::CreateFromValues(params ? *params : empty_params, | 
|  | nullptr /*returns_async*/, | 
|  | access_checker); | 
|  | DCHECK(!event_signature->has_async_return()); | 
|  | type_refs_->AddEventSignature(full_name, std::move(event_signature)); | 
|  | } | 
|  |  | 
|  | events_.push_back(std::make_unique<EventData>( | 
|  | *name, std::move(full_name), supports_filters, supports_rules, | 
|  | supports_lazy_listeners, max_listeners, notify_on_change, | 
|  | std::move(rule_actions), std::move(rule_conditions), this)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | APIBinding::~APIBinding() = default; | 
|  |  | 
|  | v8::Local<v8::Object> APIBinding::CreateInstance( | 
|  | v8::Local<v8::Context> context) { | 
|  | DCHECK(binding::IsContextValid(context)); | 
|  | v8::Isolate* isolate = v8::Isolate::GetCurrent(); | 
|  | if (object_template_.IsEmpty()) | 
|  | InitializeTemplate(isolate); | 
|  | DCHECK(!object_template_.IsEmpty()); | 
|  |  | 
|  | v8::Local<v8::Object> object = | 
|  | object_template_.Get(isolate)->NewInstance(context).ToLocalChecked(); | 
|  |  | 
|  | // The object created from the template includes all methods, but some may | 
|  | // be unavailable in this context. Iterate over them and delete any that | 
|  | // aren't available. | 
|  | // TODO(devlin): Ideally, we'd only do this check on the methods that are | 
|  | // conditionally exposed. Or, we could have multiple templates for different | 
|  | // configurations, assuming there are a small number of possibilities. | 
|  | for (const auto& key_value : methods_) { | 
|  | if (!access_checker_->HasAccess(context, key_value.second->full_name)) { | 
|  | v8::Maybe<bool> success = object->Delete( | 
|  | context, gin::StringToSymbol(isolate, key_value.first)); | 
|  | CHECK(success.IsJust()); | 
|  | CHECK(success.FromJust()); | 
|  | } | 
|  | } | 
|  | for (const auto& event : events_) { | 
|  | if (!access_checker_->HasAccess(context, event->full_name)) { | 
|  | v8::Maybe<bool> success = object->Delete( | 
|  | context, gin::StringToSymbol(isolate, event->exposed_name)); | 
|  | CHECK(success.IsJust()); | 
|  | CHECK(success.FromJust()); | 
|  | } | 
|  | } | 
|  | for (const auto& property : root_properties_) { | 
|  | std::string full_name = base::StrCat({api_name_, ".", property}); | 
|  | if (!access_checker_->HasAccess(context, full_name)) { | 
|  | v8::Maybe<bool> success = | 
|  | object->Delete(context, gin::StringToSymbol(isolate, property)); | 
|  | CHECK(success.IsJust()); | 
|  | CHECK(success.FromJust()); | 
|  | } | 
|  | } | 
|  |  | 
|  | binding_hooks_->InitializeInstance(context, object); | 
|  |  | 
|  | return object; | 
|  | } | 
|  |  | 
|  | void APIBinding::InitializeTemplate(v8::Isolate* isolate) { | 
|  | DCHECK(object_template_.IsEmpty()); | 
|  | v8::Local<v8::ObjectTemplate> object_template = | 
|  | v8::ObjectTemplate::New(isolate); | 
|  |  | 
|  | for (const auto& key_value : methods_) { | 
|  | MethodData& method = *key_value.second; | 
|  | DCHECK(method.callback.is_null()); | 
|  | method.callback = | 
|  | base::BindRepeating(&APIBinding::HandleCall, weak_factory_.GetWeakPtr(), | 
|  | method.full_name, method.signature); | 
|  |  | 
|  | object_template->Set( | 
|  | gin::StringToSymbol(isolate, key_value.first), | 
|  | v8::FunctionTemplate::New( | 
|  | isolate, &RunAPIBindingHandlerCallback, | 
|  | v8::External::New(isolate, &method.callback, | 
|  | gin::kAPIBindingHandlerCallbackTag), | 
|  | v8::Local<v8::Signature>(), 0, v8::ConstructorBehavior::kThrow)); | 
|  | } | 
|  |  | 
|  | for (const auto& event : events_) { | 
|  | object_template->SetLazyDataProperty( | 
|  | gin::StringToSymbol(isolate, event->exposed_name), | 
|  | &APIBinding::GetEventObject, | 
|  | v8::External::New(isolate, event.get(), gin::kAPIBindingEventDataTag)); | 
|  | } | 
|  |  | 
|  | for (const auto& entry : enums_) { | 
|  | v8::Local<v8::ObjectTemplate> enum_object = | 
|  | v8::ObjectTemplate::New(isolate); | 
|  | for (const auto& enum_entry : entry.second) { | 
|  | enum_object->Set(gin::StringToSymbol(isolate, enum_entry.second), | 
|  | gin::StringToSymbol(isolate, enum_entry.first)); | 
|  | } | 
|  | object_template->Set(isolate, entry.first.c_str(), enum_object); | 
|  | } | 
|  |  | 
|  | if (property_definitions_) { | 
|  | DecorateTemplateWithProperties(isolate, object_template, | 
|  | *property_definitions_, /*is_root=*/true); | 
|  | } | 
|  |  | 
|  | // Allow custom bindings a chance to tweak the template, such as to add | 
|  | // additional properties or types. | 
|  | binding_hooks_->InitializeTemplate(isolate, object_template, *type_refs_); | 
|  |  | 
|  | object_template_.Set(isolate, object_template); | 
|  | } | 
|  |  | 
|  | void APIBinding::DecorateTemplateWithProperties( | 
|  | v8::Isolate* isolate, | 
|  | v8::Local<v8::ObjectTemplate> object_template, | 
|  | const base::Value::Dict& properties, | 
|  | bool is_root) { | 
|  | static const char kValueKey[] = "value"; | 
|  | for (auto item : properties) { | 
|  | const base::Value::Dict* dict = item.second.GetIfDict(); | 
|  | CHECK(dict); | 
|  | if (dict->FindBool("optional")) { | 
|  | // TODO(devlin): What does optional even mean here? It's only used, it | 
|  | // seems, for lastError and inIncognitoContext, which are both handled | 
|  | // with custom bindings. Investigate, and remove. | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const base::Value::List* platforms = dict->FindList("platforms"); | 
|  | // TODO(devlin): Availability should be specified in the features files, | 
|  | // not the API schema files. | 
|  | if (platforms) { | 
|  | std::string this_platform = binding::GetPlatformString(); | 
|  | auto is_this_platform = [&this_platform](const base::Value& platform) { | 
|  | return platform.is_string() && platform.GetString() == this_platform; | 
|  | }; | 
|  | if (std::ranges::none_of(*platforms, is_this_platform)) { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | v8::Local<v8::String> v8_key = gin::StringToSymbol(isolate, item.first); | 
|  | const std::string* ref = dict->FindString("$ref"); | 
|  | if (ref) { | 
|  | const base::Value::List* property_values = dict->FindList("value"); | 
|  | CHECK(property_values); | 
|  | auto property_data = std::make_unique<CustomPropertyData>( | 
|  | *ref, item.first, property_values, create_custom_type_); | 
|  | object_template->SetLazyDataProperty( | 
|  | v8_key, &APIBinding::GetCustomPropertyObject, | 
|  | v8::External::New(isolate, property_data.get(), | 
|  | gin::kAPIBindingCustomPropertyDataTag)); | 
|  | custom_properties_.push_back(std::move(property_data)); | 
|  | if (is_root) | 
|  | root_properties_.insert(item.first); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const std::string* type = dict->FindString("type"); | 
|  | CHECK(type); | 
|  | if (*type != "object" && !dict->Find(kValueKey)) { | 
|  | // TODO(devlin): What does a fundamental property not having a value mean? | 
|  | // It doesn't seem useful, and looks like it's only used by runtime.id, | 
|  | // which is set by custom bindings. Investigate, and remove. | 
|  | continue; | 
|  | } | 
|  | if (*type == "integer") { | 
|  | std::optional<int> val = dict->FindInt(kValueKey); | 
|  | CHECK(val); | 
|  | object_template->Set(v8_key, v8::Integer::New(isolate, *val)); | 
|  | } else if (*type == "boolean") { | 
|  | std::optional<bool> val = dict->FindBool(kValueKey); | 
|  | CHECK(val); | 
|  | object_template->Set(v8_key, v8::Boolean::New(isolate, *val)); | 
|  | } else if (*type == "string") { | 
|  | const std::string* val = dict->FindString(kValueKey); | 
|  | CHECK(val) << item.first; | 
|  | object_template->Set(v8_key, gin::StringToSymbol(isolate, *val)); | 
|  | } else if (*type == "object" || !ref->empty()) { | 
|  | v8::Local<v8::ObjectTemplate> property_template = | 
|  | v8::ObjectTemplate::New(isolate); | 
|  | const base::Value::Dict* property_dict = dict->FindDict("properties"); | 
|  | CHECK(property_dict); | 
|  | DecorateTemplateWithProperties(isolate, property_template, *property_dict, | 
|  | /*is_root=*/false); | 
|  | object_template->Set(v8_key, property_template); | 
|  | } | 
|  | if (is_root) | 
|  | root_properties_.insert(item.first); | 
|  | } | 
|  | } | 
|  |  | 
|  | // static | 
|  | void APIBinding::GetEventObject( | 
|  | v8::Local<v8::Name> property, | 
|  | const v8::PropertyCallbackInfo<v8::Value>& info) { | 
|  | v8::Isolate* isolate = info.GetIsolate(); | 
|  | v8::HandleScope handle_scope(isolate); | 
|  | v8::Local<v8::Context> context; | 
|  | if (!info.HolderV2()->GetCreationContext(isolate).ToLocal(&context) || | 
|  | !binding::IsContextValidOrThrowError(context)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CHECK(info.Data()->IsExternal()); | 
|  | auto* event_data = static_cast<EventData*>( | 
|  | info.Data().As<v8::External>()->Value(gin::kAPIBindingEventDataTag)); | 
|  | v8::Local<v8::Value> retval; | 
|  | if (event_data->binding->binding_hooks_->CreateCustomEvent( | 
|  | context, event_data->full_name, &retval)) { | 
|  | // A custom event was created; our work is done. | 
|  | } else if (event_data->supports_rules) { | 
|  | auto* event = cppgc::MakeGarbageCollected<DeclarativeEvent>( | 
|  | isolate->GetCppHeap()->GetAllocationHandle(), event_data->full_name, | 
|  | event_data->binding->type_refs_, event_data->binding->request_handler_, | 
|  | event_data->actions, event_data->conditions, 0); | 
|  | retval = event->GetWrapper(isolate).ToLocalChecked(); | 
|  | } else { | 
|  | retval = event_data->binding->event_handler_->CreateEventInstance( | 
|  | event_data->full_name, event_data->supports_filters, | 
|  | event_data->supports_lazy_listeners, event_data->max_listeners, | 
|  | event_data->notify_on_change, context); | 
|  | } | 
|  | info.GetReturnValue().Set(retval); | 
|  | } | 
|  |  | 
|  | void APIBinding::GetCustomPropertyObject( | 
|  | v8::Local<v8::Name> property_name, | 
|  | const v8::PropertyCallbackInfo<v8::Value>& info) { | 
|  | v8::Isolate* isolate = info.GetIsolate(); | 
|  | v8::HandleScope handle_scope(isolate); | 
|  | v8::Local<v8::Context> context; | 
|  | if (!info.HolderV2()->GetCreationContext(isolate).ToLocal(&context) || | 
|  | !binding::IsContextValid(context)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | v8::Context::Scope context_scope(context); | 
|  | CHECK(info.Data()->IsExternal()); | 
|  | auto* property_data = | 
|  | static_cast<CustomPropertyData*>(info.Data().As<v8::External>()->Value( | 
|  | gin::kAPIBindingCustomPropertyDataTag)); | 
|  |  | 
|  | v8::Local<v8::Object> property = property_data->create_custom_type.Run( | 
|  | isolate, property_data->type_name, property_data->property_name, | 
|  | property_data->property_values.get()); | 
|  | if (property.IsEmpty()) | 
|  | return; | 
|  |  | 
|  | info.GetReturnValue().Set(property); | 
|  | } | 
|  |  | 
|  | void APIBinding::HandleCall(const std::string& name, | 
|  | const APISignature* signature, | 
|  | gin::Arguments* arguments) { | 
|  | std::string error; | 
|  | v8::Isolate* isolate = arguments->isolate(); | 
|  | v8::HandleScope handle_scope(isolate); | 
|  |  | 
|  | // Since this is called synchronously from the JS entry point, | 
|  | // GetCurrentContext() should always be correct. | 
|  | v8::Local<v8::Context> context = isolate->GetCurrentContext(); | 
|  |  | 
|  | if (!access_checker_->HasAccessOrThrowError(context, name)) { | 
|  | // TODO(devlin): Do we need handle this for events as well? I'm not sure the | 
|  | // currrent system does (though perhaps it should). Investigate. | 
|  | return; | 
|  | } | 
|  |  | 
|  | v8::LocalVector<v8::Value> argument_list = arguments->GetAll(); | 
|  |  | 
|  | bool invalid_invocation = false; | 
|  | v8::Local<v8::Function> custom_callback; | 
|  | binding::ResultModifierFunction result_modifier; | 
|  | bool updated_args = false; | 
|  | int old_request_id = request_handler_->last_sent_request_id(); | 
|  | { | 
|  | v8::TryCatch try_catch(isolate); | 
|  | APIBindingHooks::RequestResult hooks_result = binding_hooks_->RunHooks( | 
|  | name, context, signature, &argument_list, *type_refs_); | 
|  |  | 
|  | switch (hooks_result.code) { | 
|  | case APIBindingHooks::RequestResult::INVALID_INVOCATION: | 
|  | invalid_invocation = true; | 
|  | error = std::move(hooks_result.error); | 
|  | // Throw a type error below so that it's not caught by our try-catch. | 
|  | break; | 
|  | case APIBindingHooks::RequestResult::THROWN: | 
|  | DCHECK(try_catch.HasCaught()); | 
|  | try_catch.ReThrow(); | 
|  | return; | 
|  | case APIBindingHooks::RequestResult::CONTEXT_INVALIDATED: | 
|  | DCHECK(!binding::IsContextValid(context)); | 
|  | // The context was invalidated during the course of running the custom | 
|  | // hooks. Bail. | 
|  | return; | 
|  | case APIBindingHooks::RequestResult::HANDLED: | 
|  | if (!hooks_result.return_value.IsEmpty()) | 
|  | arguments->Return(hooks_result.return_value); | 
|  |  | 
|  | // TODO(devlin): This is a pretty simplistic implementation of this, | 
|  | // but it's similar to the current JS logic. If we wanted to be more | 
|  | // correct, we could create a RequestScope object that watches outgoing | 
|  | // requests. | 
|  | if (old_request_id == request_handler_->last_sent_request_id()) | 
|  | on_silent_request_.Run(context, name, argument_list); | 
|  |  | 
|  | return;  // Our work here is done. | 
|  | case APIBindingHooks::RequestResult::ARGUMENTS_UPDATED: | 
|  | updated_args = true; | 
|  | [[fallthrough]]; | 
|  | case APIBindingHooks::RequestResult::NOT_HANDLED: | 
|  | break;  // Handle in the default manner. | 
|  | } | 
|  | custom_callback = hooks_result.custom_callback; | 
|  | result_modifier = std::move(hooks_result.result_modifier); | 
|  | } | 
|  |  | 
|  | if (invalid_invocation) { | 
|  | arguments->ThrowTypeError(api_errors::InvocationError( | 
|  | name, signature->GetExpectedSignature(), error)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | APISignature::JSONParseResult parse_result; | 
|  | { | 
|  | v8::TryCatch try_catch(isolate); | 
|  |  | 
|  | // If custom hooks updated the arguments post-validation, we just trust the | 
|  | // values the hooks provide and convert them directly. This is because some | 
|  | // APIs have one set of values they use for validation, and a second they | 
|  | // use in the implementation of the function (see, e.g. | 
|  | // fileSystem.getDisplayPath). | 
|  | // TODO(devlin): That's unfortunate. Not only does it require special casing | 
|  | // here, but it also means we can't auto-generate the params for the | 
|  | // function on the browser side. | 
|  | if (updated_args) { | 
|  | parse_result = | 
|  | signature->ConvertArgumentsIgnoringSchema(context, argument_list); | 
|  | // Converted arguments passed to us by our bindings should never fail. | 
|  | DCHECK(parse_result.succeeded()); | 
|  | } else { | 
|  | parse_result = | 
|  | signature->ParseArgumentsToJSON(context, argument_list, *type_refs_); | 
|  | } | 
|  |  | 
|  | if (try_catch.HasCaught()) { | 
|  | DCHECK(!parse_result.succeeded()); | 
|  | try_catch.ReThrow(); | 
|  | return; | 
|  | } | 
|  | } | 
|  | if (!parse_result.succeeded()) { | 
|  | arguments->ThrowTypeError(api_errors::InvocationError( | 
|  | name, signature->GetExpectedSignature(), *parse_result.error)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | v8::Local<v8::Promise> promise = request_handler_->StartRequest( | 
|  | context, name, std::move(*parse_result.arguments_list), | 
|  | parse_result.async_type, parse_result.callback, custom_callback, | 
|  | std::move(result_modifier)); | 
|  | if (!promise.IsEmpty()) | 
|  | arguments->Return(promise); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |