910e62b5创建于 1月15日历史提交
// 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