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