[Extensions Bindings] Separate out js util and bindings hooks

Custom bindings need to be able to both set custom hooks and callbacks
on an API object and also to be able to access common js util in order
to send requests, modify the last error, etc.

In the case of custom bindings associated with a particular API (e.g.
runtime_custom_bindings.js is associated with the chrome.runtime API),
we have an associated API binding object (the chrome.runtime binding).
However, there are also shared custom binding files, like messaging.js,
which don't have a single API object, but still require the common JS
util.

Separate out the js util from the APIBindingBridge in order to be able
to expose these utils to the shared files that won't have an associated
bridge (since they don't have an associated API).

BUG=653596

Review-Url: https://codereview.chromium.org/2731413002
Cr-Commit-Position: refs/heads/master@{#455843}
diff --git a/chrome/renderer/resources/extensions/browser_action_custom_bindings.js b/chrome/renderer/resources/extensions/browser_action_custom_bindings.js
index 9975d31..9d18ebbd 100644
--- a/chrome/renderer/resources/extensions/browser_action_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/browser_action_custom_bindings.js
@@ -8,8 +8,9 @@
 
 var setIcon = require('setIcon').setIcon;
 var getExtensionViews = requireNative('runtime').GetExtensionViews;
-var sendRequest = apiBridge ?
-    apiBridge.sendRequest.bind(apiBridge) : require('sendRequest').sendRequest;
+var sendRequest = bindingUtil ?
+    bindingUtil.sendRequest.bind(bindingUtil) :
+    require('sendRequest').sendRequest;
 var lastError = require('lastError');
 
 binding.registerCustomHook(function(bindingsAPI) {
diff --git a/extensions/renderer/BUILD.gn b/extensions/renderer/BUILD.gn
index 95af304a..9bbcb5eb 100644
--- a/extensions/renderer/BUILD.gn
+++ b/extensions/renderer/BUILD.gn
@@ -23,6 +23,8 @@
     "api_binding_bridge.h",
     "api_binding_hooks.cc",
     "api_binding_hooks.h",
+    "api_binding_js_util.cc",
+    "api_binding_js_util.h",
     "api_binding_types.h",
     "api_bindings_system.cc",
     "api_bindings_system.h",
diff --git a/extensions/renderer/api_binding_bridge.cc b/extensions/renderer/api_binding_bridge.cc
index 6d8b2dc6..49ccd29 100644
--- a/extensions/renderer/api_binding_bridge.cc
+++ b/extensions/renderer/api_binding_bridge.cc
@@ -6,10 +6,6 @@
 
 #include "base/values.h"
 #include "extensions/renderer/api_binding_hooks.h"
-#include "extensions/renderer/api_event_handler.h"
-#include "extensions/renderer/api_request_handler.h"
-#include "extensions/renderer/api_signature.h"
-#include "extensions/renderer/api_type_reference_map.h"
 #include "gin/converter.h"
 #include "gin/object_template_builder.h"
 
@@ -29,20 +25,13 @@
 
 gin::WrapperInfo APIBindingBridge::kWrapperInfo = {gin::kEmbedderNativeGin};
 
-APIBindingBridge::APIBindingBridge(const APITypeReferenceMap* type_refs,
-                                   APIRequestHandler* request_handler,
-                                   APIEventHandler* event_handler,
-                                   APIBindingHooks* hooks,
+APIBindingBridge::APIBindingBridge(APIBindingHooks* hooks,
                                    v8::Local<v8::Context> context,
                                    v8::Local<v8::Value> api_object,
                                    const std::string& extension_id,
                                    const std::string& context_type,
                                    const binding::RunJSFunction& run_js)
-    : type_refs_(type_refs),
-      request_handler_(request_handler),
-      event_handler_(event_handler),
-      hooks_(hooks),
-      extension_id_(extension_id),
+    : extension_id_(extension_id),
       context_type_(context_type),
       run_js_(run_js) {
   v8::Isolate* isolate = context->GetIsolate();
@@ -53,7 +42,7 @@
     NOTREACHED();
     return;
   }
-  v8::Local<v8::Object> js_hook_interface = hooks_->GetJSHookInterface(context);
+  v8::Local<v8::Object> js_hook_interface = hooks->GetJSHookInterface(context);
   result = wrapper->SetPrivate(context,
                                GetPrivatePropertyName(isolate,
                                                       kHookInterfaceKey),
@@ -66,10 +55,7 @@
 gin::ObjectTemplateBuilder APIBindingBridge::GetObjectTemplateBuilder(
     v8::Isolate* isolate) {
   return Wrappable<APIBindingBridge>::GetObjectTemplateBuilder(isolate)
-      .SetMethod("registerCustomHook", &APIBindingBridge::RegisterCustomHook)
-      .SetMethod("sendRequest", &APIBindingBridge::SendRequest)
-      .SetMethod("registerEventArgumentMassager",
-                 &APIBindingBridge::RegisterEventArgumentMassager);
+      .SetMethod("registerCustomHook", &APIBindingBridge::RegisterCustomHook);
 }
 
 void APIBindingBridge::RegisterCustomHook(v8::Isolate* isolate,
@@ -113,42 +99,4 @@
   run_js_.Run(function, context, arraysize(args), args);
 }
 
-void APIBindingBridge::SendRequest(
-    gin::Arguments* arguments,
-    const std::string& name,
-    const std::vector<v8::Local<v8::Value>>& request_args) {
-  v8::Isolate* isolate = arguments->isolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Local<v8::Object> holder;
-  CHECK(arguments->GetHolder(&holder));
-  v8::Local<v8::Context> context = holder->CreationContext();
-
-  const APISignature* signature = type_refs_->GetAPIMethodSignature(name);
-  DCHECK(signature);
-
-  std::unique_ptr<base::ListValue> converted_arguments;
-  v8::Local<v8::Function> callback;
-  std::string error;
-  CHECK(signature->ParseArgumentsToJSON(context, request_args, *type_refs_,
-                                        &converted_arguments, &callback,
-                                        &error));
-
-  request_handler_->StartRequest(context, name, std::move(converted_arguments),
-                                 callback,
-                                 hooks_->GetCustomJSCallback(name, context));
-}
-
-void APIBindingBridge::RegisterEventArgumentMassager(
-    gin::Arguments* arguments,
-    const std::string& event_name,
-    v8::Local<v8::Function> massager) {
-  v8::Isolate* isolate = arguments->isolate();
-  v8::HandleScope handle_scope(isolate);
-  v8::Local<v8::Object> holder;
-  CHECK(arguments->GetHolder(&holder));
-  v8::Local<v8::Context> context = holder->CreationContext();
-
-  event_handler_->RegisterArgumentMassager(context, event_name, massager);
-}
-
 }  // namespace extensions
diff --git a/extensions/renderer/api_binding_bridge.h b/extensions/renderer/api_binding_bridge.h
index 30dd71b..32f1238 100644
--- a/extensions/renderer/api_binding_bridge.h
+++ b/extensions/renderer/api_binding_bridge.h
@@ -12,25 +12,15 @@
 #include "gin/wrappable.h"
 #include "v8/include/v8.h"
 
-namespace gin {
-class Arguments;
-}
-
 namespace extensions {
 class APIBindingHooks;
-class APIEventHandler;
-class APIRequestHandler;
-class APITypeReferenceMap;
 
 // An object that serves as a bridge between the current JS-centric bindings and
 // the new native bindings system. This basically needs to conform to the public
 // methods of the Binding prototype in binding.js.
 class APIBindingBridge final : public gin::Wrappable<APIBindingBridge> {
  public:
-  APIBindingBridge(const APITypeReferenceMap* type_refs,
-                   APIRequestHandler* request_handler,
-                   APIEventHandler* event_handler,
-                   APIBindingHooks* hooks,
+  APIBindingBridge(APIBindingHooks* hooks,
                    v8::Local<v8::Context> context,
                    v8::Local<v8::Value> api_object,
                    const std::string& extension_id,
@@ -56,30 +46,6 @@
   void RegisterCustomHook(v8::Isolate* isolate,
                           v8::Local<v8::Function> function);
 
-  // A handler to initiate an API request through the APIRequestHandler. A
-  // replacement for custom bindings that utilize require('sendRequest').
-  void SendRequest(gin::Arguments* arguments,
-                   const std::string& name,
-                   const std::vector<v8::Local<v8::Value>>& request_args);
-
-  // A handler to register an argument massager for a specific event.
-  // Replacement for event_bindings.registerArgumentMassager.
-  void RegisterEventArgumentMassager(gin::Arguments* arguments,
-                                     const std::string& event_name,
-                                     v8::Local<v8::Function> massager);
-
-  // Type references. Guaranteed to outlive this object.
-  const APITypeReferenceMap* type_refs_;
-
-  // The request handler. Guaranteed to outlive this object.
-  APIRequestHandler* request_handler_;
-
-  // The event handler. Guaranteed to outlive this object.
-  APIEventHandler* event_handler_;
-
-  // The hooks associated with this API. Guaranteed to outlive this object.
-  APIBindingHooks* hooks_;
-
   // The id of the extension that owns the context this belongs to.
   std::string extension_id_;
 
diff --git a/extensions/renderer/api_binding_js_util.cc b/extensions/renderer/api_binding_js_util.cc
new file mode 100644
index 0000000..5de8b5b
--- /dev/null
+++ b/extensions/renderer/api_binding_js_util.cc
@@ -0,0 +1,76 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/renderer/api_binding_js_util.h"
+
+#include "base/values.h"
+#include "extensions/renderer/api_event_handler.h"
+#include "extensions/renderer/api_request_handler.h"
+#include "extensions/renderer/api_signature.h"
+#include "extensions/renderer/api_type_reference_map.h"
+#include "gin/converter.h"
+#include "gin/object_template_builder.h"
+
+namespace extensions {
+
+gin::WrapperInfo APIBindingJSUtil::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+APIBindingJSUtil::APIBindingJSUtil(const APITypeReferenceMap* type_refs,
+                                   APIRequestHandler* request_handler,
+                                   APIEventHandler* event_handler)
+    : type_refs_(type_refs),
+      request_handler_(request_handler),
+      event_handler_(event_handler) {}
+
+APIBindingJSUtil::~APIBindingJSUtil() {}
+
+gin::ObjectTemplateBuilder APIBindingJSUtil::GetObjectTemplateBuilder(
+    v8::Isolate* isolate) {
+  return Wrappable<APIBindingJSUtil>::GetObjectTemplateBuilder(isolate)
+      .SetMethod("sendRequest", &APIBindingJSUtil::SendRequest)
+      .SetMethod("registerEventArgumentMassager",
+                 &APIBindingJSUtil::RegisterEventArgumentMassager);
+}
+
+void APIBindingJSUtil::SendRequest(
+    gin::Arguments* arguments,
+    const std::string& name,
+    const std::vector<v8::Local<v8::Value>>& request_args) {
+  v8::Isolate* isolate = arguments->isolate();
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Object> holder;
+  CHECK(arguments->GetHolder(&holder));
+  v8::Local<v8::Context> context = holder->CreationContext();
+
+  const APISignature* signature = type_refs_->GetAPIMethodSignature(name);
+  DCHECK(signature);
+
+  std::unique_ptr<base::ListValue> converted_arguments;
+  v8::Local<v8::Function> callback;
+  std::string error;
+  CHECK(signature->ParseArgumentsToJSON(context, request_args, *type_refs_,
+                                        &converted_arguments, &callback,
+                                        &error));
+
+  // TODO(devlin): The JS version of this allows callers to curry in
+  // arguments, including which thread the request should be for and an
+  // optional custom callback.
+  request_handler_->StartRequest(context, name, std::move(converted_arguments),
+                                 callback, v8::Local<v8::Function>());
+}
+
+void APIBindingJSUtil::RegisterEventArgumentMassager(
+    gin::Arguments* arguments,
+    const std::string& event_name,
+    v8::Local<v8::Function> massager) {
+  v8::Isolate* isolate = arguments->isolate();
+  v8::HandleScope handle_scope(isolate);
+  v8::Local<v8::Object> holder;
+  CHECK(arguments->GetHolder(&holder));
+  v8::Local<v8::Context> context = holder->CreationContext();
+
+  event_handler_->RegisterArgumentMassager(context, event_name, massager);
+}
+
+}  // namespace extensions
diff --git a/extensions/renderer/api_binding_js_util.h b/extensions/renderer/api_binding_js_util.h
new file mode 100644
index 0000000..c7e3be36
--- /dev/null
+++ b/extensions/renderer/api_binding_js_util.h
@@ -0,0 +1,66 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_RENDERER_API_BINDING_JS_UTIL_H_
+#define EXTENSIONS_RENDERER_API_BINDING_JS_UTIL_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "gin/wrappable.h"
+#include "v8/include/v8.h"
+
+namespace gin {
+class Arguments;
+}
+
+namespace extensions {
+class APIEventHandler;
+class APIRequestHandler;
+class APITypeReferenceMap;
+
+// An object that exposes utility methods to the existing JS bindings, such as
+// sendRequest and registering event argument massagers. If/when we get rid of
+// some of our JS bindings, we can reduce or remove this class.
+class APIBindingJSUtil final : public gin::Wrappable<APIBindingJSUtil> {
+ public:
+  APIBindingJSUtil(const APITypeReferenceMap* type_refs,
+                   APIRequestHandler* request_handler,
+                   APIEventHandler* event_handler);
+  ~APIBindingJSUtil() override;
+
+  static gin::WrapperInfo kWrapperInfo;
+
+  // gin::Wrappable:
+  gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+      v8::Isolate* isolate) final;
+
+ private:
+  // A handler to initiate an API request through the APIRequestHandler. A
+  // replacement for custom bindings that utilize require('sendRequest').
+  void SendRequest(gin::Arguments* arguments,
+                   const std::string& name,
+                   const std::vector<v8::Local<v8::Value>>& request_args);
+
+  // A handler to register an argument massager for a specific event.
+  // Replacement for event_bindings.registerArgumentMassager.
+  void RegisterEventArgumentMassager(gin::Arguments* arguments,
+                                     const std::string& event_name,
+                                     v8::Local<v8::Function> massager);
+
+  // Type references. Guaranteed to outlive this object.
+  const APITypeReferenceMap* type_refs_;
+
+  // The request handler. Guaranteed to outlive this object.
+  APIRequestHandler* request_handler_;
+
+  // The event handler. Guaranteed to outlive this object.
+  APIEventHandler* event_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(APIBindingJSUtil);
+};
+
+}  // namespace extensions
+
+#endif  // EXTENSIONS_RENDERER_API_BINDING_JS_UTIL_H_
diff --git a/extensions/renderer/module_system.cc b/extensions/renderer/module_system.cc
index 85e1531..55c6d64 100644
--- a/extensions/renderer/module_system.cc
+++ b/extensions/renderer/module_system.cc
@@ -561,6 +561,11 @@
   get_internal_api_.Set(GetIsolate(), get_internal_api);
 }
 
+void ModuleSystem::SetJSBindingUtilGetter(const JSBindingUtilGetter& getter) {
+  DCHECK(js_binding_util_getter_.is_null());
+  js_binding_util_getter_ = getter;
+}
+
 v8::Local<v8::Value> ModuleSystem::RunString(v8::Local<v8::String> code,
                                              v8::Local<v8::String> name) {
   return context_->RunScript(
@@ -641,7 +646,7 @@
   v8::Local<v8::String> left = ToV8StringUnsafe(
       GetIsolate(),
       "(function(define, require, requireNative, requireAsync, exports, "
-      "console, privates, apiBridge, getInternalApi,"
+      "console, privates, apiBridge, bindingUtil, getInternalApi,"
       "$Array, $Function, $JSON, $Object, $RegExp, $String, $Error) {"
       "'use strict';");
   v8::Local<v8::String> right = ToV8StringUnsafe(GetIsolate(), "\n})");
@@ -748,6 +753,19 @@
                            .ToLocalChecked();
   }
 
+  v8::Local<v8::Value> binding_util;
+  if (!js_binding_util_getter_.is_null()) {
+    js_binding_util_getter_.Run(v8_context, &binding_util);
+    if (binding_util.IsEmpty()) {
+      // The NativeExtensionBindingsSystem was destroyed. This shouldn't happen,
+      // but JS makes the impossible possible!
+      NOTREACHED();
+      return v8::Undefined(GetIsolate());
+    }
+  } else {
+    binding_util = v8::Undefined(GetIsolate());
+  }
+
   // These must match the argument order in WrapSource.
   v8::Local<v8::Value> args[] = {
       // AMD.
@@ -765,6 +783,7 @@
       GetPropertyUnsafe(v8_context, natives, "privates",
                         v8::NewStringType::kInternalized),
       api_bridge,        // exposed as apiBridge.
+      binding_util,      // exposed as bindingUtil.
       get_internal_api,  // exposed as getInternalApi.
       // Each safe builtin. Keep in order with the arguments in WrapSource.
       context_->safe_builtins()->GetArray(),
diff --git a/extensions/renderer/module_system.h b/extensions/renderer/module_system.h
index 55ce62cc..72816a5 100644
--- a/extensions/renderer/module_system.h
+++ b/extensions/renderer/module_system.h
@@ -162,6 +162,10 @@
 
   void SetGetInternalAPIHook(v8::Local<v8::FunctionTemplate> get_internal_api);
 
+  using JSBindingUtilGetter =
+      base::Callback<void(v8::Local<v8::Context>, v8::Local<v8::Value>*)>;
+  void SetJSBindingUtilGetter(const JSBindingUtilGetter& getter);
+
  protected:
   friend class ModuleSystemTestEnvironment;
   friend class ScriptContext;
@@ -271,6 +275,8 @@
   // The template to be used for retrieving an internal API.
   v8::Eternal<v8::FunctionTemplate> get_internal_api_;
 
+  JSBindingUtilGetter js_binding_util_getter_;
+
   base::WeakPtrFactory<ModuleSystem> weak_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(ModuleSystem);
diff --git a/extensions/renderer/native_extension_bindings_system.cc b/extensions/renderer/native_extension_bindings_system.cc
index e1ccd50..0e65f47 100644
--- a/extensions/renderer/native_extension_bindings_system.cc
+++ b/extensions/renderer/native_extension_bindings_system.cc
@@ -13,6 +13,7 @@
 #include "extensions/common/features/feature_provider.h"
 #include "extensions/renderer/api_binding_bridge.h"
 #include "extensions/renderer/api_binding_hooks.h"
+#include "extensions/renderer/api_binding_js_util.h"
 #include "extensions/renderer/chrome_setting.h"
 #include "extensions/renderer/module_system.h"
 #include "extensions/renderer/script_context.h"
@@ -189,10 +190,8 @@
 
   gin::Handle<APIBindingBridge> bridge_handle = gin::CreateHandle(
       context->GetIsolate(),
-      new APIBindingBridge(bindings_system->type_reference_map(),
-                           bindings_system->request_handler(),
-                           bindings_system->event_handler(), hooks, context,
-                           binding_object, script_context->GetExtensionID(),
+      new APIBindingBridge(hooks, context, binding_object,
+                           script_context->GetExtensionID(),
                            script_context->GetContextTypeDescription(),
                            base::Bind(&CallJsFunction)));
   v8::Local<v8::Value> native_api_bridge = bridge_handle.ToV8();
@@ -369,6 +368,9 @@
   // web/guest view.
   context->module_system()->SetGetInternalAPIHook(
       get_internal_api_.Get(isolate));
+  context->module_system()->SetJSBindingUtilGetter(
+      base::Bind(&NativeExtensionBindingsSystem::GetJSBindingUtil,
+                 weak_factory_.GetWeakPtr()));
 }
 
 void NativeExtensionBindingsSystem::WillReleaseScriptContext(
@@ -637,4 +639,15 @@
       change, ScriptContextSet::GetContextByV8Context(context), event_name);
 }
 
+void NativeExtensionBindingsSystem::GetJSBindingUtil(
+    v8::Local<v8::Context> context,
+    v8::Local<v8::Value>* binding_util_out) {
+  gin::Handle<APIBindingJSUtil> handle =
+      gin::CreateHandle(context->GetIsolate(),
+                        new APIBindingJSUtil(api_system_.type_reference_map(),
+                                             api_system_.request_handler(),
+                                             api_system_.event_handler()));
+  *binding_util_out = handle.ToV8();
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/native_extension_bindings_system.h b/extensions/renderer/native_extension_bindings_system.h
index 4d5df3f2..22dc4d4 100644
--- a/extensions/renderer/native_extension_bindings_system.h
+++ b/extensions/renderer/native_extension_bindings_system.h
@@ -81,6 +81,13 @@
   // Callback to get an API binding for an internal API.
   static void GetInternalAPI(const v8::FunctionCallbackInfo<v8::Value>& info);
 
+  // Helper method to get a APIBindingJSUtil object for the current context,
+  // and populate |binding_util_out|. We use an out parameter instead of
+  // returning it in order to let us use weak ptrs, which can't be used on a
+  // method with a return value.
+  void GetJSBindingUtil(v8::Local<v8::Context> context,
+                        v8::Local<v8::Value>* binding_util_out);
+
   // Handler to send request IPCs. Abstracted out for testing purposes.
   SendRequestIPCMethod send_request_ipc_;
 
diff --git a/extensions/renderer/native_extension_bindings_system_unittest.cc b/extensions/renderer/native_extension_bindings_system_unittest.cc
index 41efc80..7e82ac2 100644
--- a/extensions/renderer/native_extension_bindings_system_unittest.cc
+++ b/extensions/renderer/native_extension_bindings_system_unittest.cc
@@ -502,7 +502,7 @@
       "apiBridge.registerCustomHook((api) => {\n"
       "  api.apiFunctions.setHandleRequest('queryState',\n"
       "                                    (time, callback) => {\n"
-      "    apiBridge.sendRequest('idle.queryState', [time, callback]);\n"
+      "    bindingUtil.sendRequest('idle.queryState', [time, callback]);\n"
       "  });\n"
       "});\n";