Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Unified Diff: extensions/renderer/api_binding_hooks.cc

Issue 2563093002: [Extension Bindings] Add JS custom hook support (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « extensions/renderer/api_binding_hooks.h ('k') | extensions/renderer/api_binding_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: extensions/renderer/api_binding_hooks.cc
diff --git a/extensions/renderer/api_binding_hooks.cc b/extensions/renderer/api_binding_hooks.cc
index 69ade18a96fa5f9650b78f8068c12d6282a86990..f4ed635f3612d52b626dbc72c1ca2f6963ba3017 100644
--- a/extensions/renderer/api_binding_hooks.cc
+++ b/extensions/renderer/api_binding_hooks.cc
@@ -4,9 +4,140 @@
#include "extensions/renderer/api_binding_hooks.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/supports_user_data.h"
+#include "gin/arguments.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_context_data.h"
+#include "gin/wrappable.h"
+
namespace extensions {
-APIBindingHooks::APIBindingHooks() {}
+namespace {
+
+// An interface to allow for registration of custom hooks from JavaScript.
+// Contains registered hooks for a single API.
+class JSHookInterface final : public gin::Wrappable<JSHookInterface> {
+ public:
+ using JSHooks = std::map<std::string, v8::Global<v8::Function>>;
+
+ explicit JSHookInterface(const std::string& api_name)
+ : api_name_(api_name) {}
+ ~JSHookInterface() override {}
+
+ static gin::WrapperInfo kWrapperInfo;
+
+ // gin::Wrappable:
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) override {
+ return Wrappable<JSHookInterface>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("setHandleRequest", &JSHookInterface::SetHandleRequest);
+ }
+
+ JSHooks* js_hooks() { return &js_hooks_; }
+
+ private:
+ // Adds a custom hook.
+ void SetHandleRequest(gin::Arguments* arguments) {
jbroman 2016/12/13 22:12:40 There's nothing unusual going on here, so you don'
Devlin 2016/12/14 02:02:13 Nifty, done.
+ std::string method_name;
+ if (!arguments->GetNext(&method_name))
+ return;
+ v8::Local<v8::Function> handler;
+ if (!arguments->GetNext(&handler))
+ return;
+
+ std::string qualified_method_name =
+ base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str());
+ v8::Global<v8::Function>& entry = js_hooks_[qualified_method_name];
+ if (!entry.IsEmpty()) {
+ NOTREACHED() << "Hooks can only be set once.";
+ return;
+ }
+ js_hooks_[qualified_method_name] =
jbroman 2016/12/13 22:12:41 You already have a reference to this object a few
Devlin 2016/12/14 02:02:14 I was definitely going to do that, and apparently
+ v8::Global<v8::Function>(arguments->isolate(), handler);
+ }
+
+ std::string api_name_;
+
+ JSHooks js_hooks_;
+
+ DISALLOW_COPY_AND_ASSIGN(JSHookInterface);
+};
+
+const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks";
+
+struct APIHooksPerContextData : public base::SupportsUserData::Data {
+ APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {}
+ ~APIHooksPerContextData() override {
+ v8::HandleScope scope(isolate);
+ for (const auto& pair : hook_interfaces) {
+ // We explicitly clear the hook data map here to remove all references to
+ // v8 objects in order to avoid cycles.
+ JSHookInterface* hooks = nullptr;
+ gin::Converter<JSHookInterface*>::FromV8(
+ isolate, pair.second.Get(isolate), &hooks);
+ CHECK(hooks);
+ hooks->js_hooks()->clear();
+ }
+ }
+
+ v8::Isolate* isolate = nullptr;
+
+ std::map<std::string, v8::Global<v8::Object>> hook_interfaces;
+};
+
+gin::WrapperInfo JSHookInterface::kWrapperInfo =
+ {gin::kEmbedderNativeGin};
+
+// Creates and returns JS object for the hook interface to allow for
+// registering custom hooks from JS.
+v8::Local<v8::Object> CreateJSHookInterface(const std::string& api_name,
+ v8::Local<v8::Context> context) {
+ gin::PerContextData* per_context_data = gin::PerContextData::From(context);
+ DCHECK(per_context_data);
+ APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>(
+ per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
+ if (!data) {
+ auto api_data =
+ base::MakeUnique<APIHooksPerContextData>(context->GetIsolate());
+ data = api_data.get();
+ per_context_data->SetUserData(kExtensionAPIHooksPerContextKey,
+ api_data.release());
+ }
+
+ DCHECK(data->hook_interfaces.find(api_name) == data->hook_interfaces.end());
+
+ gin::Handle<JSHookInterface> hooks =
+ gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name));
+ CHECK(!hooks.IsEmpty());
+ v8::Local<v8::Value> hooks_value = hooks.ToV8();
+ CHECK(hooks_value->IsObject());
jbroman 2016/12/13 22:12:40 It'd be pretty bad for this to not be a v8::Object
Devlin 2016/12/14 02:02:13 Removed.
+ v8::Local<v8::Object> hooks_object =
+ v8::Local<v8::Object>::Cast(hooks_value);
+ data->hook_interfaces[api_name] =
jbroman 2016/12/13 22:12:40 Up to your preference, but you can use Reset() her
Devlin 2016/12/14 02:02:14 Works for me, done.
+ v8::Global<v8::Object>(context->GetIsolate(), hooks_object);
+
+ return hooks_object;
+}
+
+// A wrapper to run the provided function with the passed-in arguments.
+void RunJsFunction(const binding::RunJSFunction& run_js,
+ v8::Local<v8::Function> function,
+ v8::Local<v8::Context> context,
+ const binding::APISignature* signature,
+ gin::Arguments* arguments) {
+ std::vector<v8::Local<v8::Value>> v8_args;
+ if (!arguments->GetRemaining(&v8_args))
+ return;
+ run_js.Run(function, context, v8_args.size(), v8_args.data());
jbroman 2016/12/13 22:12:40 Do these functions ever need the ability to throw
Devlin 2016/12/14 02:02:14 Yes, it doesn't currently. It will need to in the
+}
+
+} // namespace
+
+APIBindingHooks::APIBindingHooks(const binding::RunJSFunction& run_js)
+ : run_js_(run_js) {}
APIBindingHooks::~APIBindingHooks() {}
void APIBindingHooks::RegisterHandleRequest(const std::string& method_name,
@@ -14,13 +145,64 @@ void APIBindingHooks::RegisterHandleRequest(const std::string& method_name,
request_hooks_[method_name] = hook;
}
+void APIBindingHooks::RegisterJsSource(v8::Global<v8::String> source) {
+ js_hooks_source_ = std::move(source);
+}
+
APIBindingHooks::HandleRequestHook APIBindingHooks::GetHandleRequest(
- const std::string& method_name) {
- auto iter = request_hooks_.find(method_name);
- if (iter != request_hooks_.end())
- return iter->second;
+ const std::string& api_name,
+ const std::string& method_name,
+ v8::Local<v8::Context> context) {
+ // Easy case: a native custom hook.
+ auto request_hooks_iter = request_hooks_.find(method_name);
+ if (request_hooks_iter != request_hooks_.end())
+ return request_hooks_iter->second;
+
+ // Harder case: looking up a custom hook registered on the context (since
+ // these are JS, each context has a separate instance).
+ gin::PerContextData* per_context_data = gin::PerContextData::From(context);
+ DCHECK(per_context_data);
+ APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>(
+ per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
+ if (!data)
+ return HandleRequestHook();
+
+ auto hook_interface_iter = data->hook_interfaces.find(api_name);
+ if (hook_interface_iter == data->hook_interfaces.end())
+ return HandleRequestHook();
+
+ JSHookInterface* hook_interface = nullptr;
+ gin::Converter<JSHookInterface*>::FromV8(
+ context->GetIsolate(),
+ hook_interface_iter->second.Get(context->GetIsolate()), &hook_interface);
+ CHECK(hook_interface);
+
+ auto js_hook_iter = hook_interface->js_hooks()->find(method_name);
+ if (js_hook_iter == hook_interface->js_hooks()->end())
+ return HandleRequestHook();
+
+ return base::Bind(&RunJsFunction, run_js_,
jbroman 2016/12/13 22:12:41 I don't see whether I commented on this previously
Devlin 2016/12/14 02:02:13 Much cleaner now; good idea!
+ js_hook_iter->second.Get(context->GetIsolate()),
+ context);
+}
+
+void APIBindingHooks::InitializeInContext(
+ v8::Local<v8::Context> context,
+ const std::string& api_name) {
+ if (js_hooks_source_.IsEmpty())
+ return;
- return HandleRequestHook();
+ v8::Local<v8::String> source = js_hooks_source_.Get(context->GetIsolate());
+ v8::Local<v8::Script> script;
+ if (!v8::Script::Compile(context, source).ToLocal(&script))
jbroman 2016/12/13 22:12:40 nit: Do you want to also pass in a ScriptOrigin wi
Devlin 2016/12/14 02:02:13 sg; done.
+ return;
+ v8::Local<v8::Value> func_as_value = script->Run();
+ v8::Local<v8::Function> function;
+ if (!gin::ConvertFromV8(context->GetIsolate(), func_as_value, &function))
+ return;
+ v8::Local<v8::Object> api_hooks = GetJSHookInterface(api_name, context);
jbroman 2016/12/13 22:12:40 I can't find the definition of this function. Shou
Devlin 2016/12/14 02:02:13 Errm, yes. Done.
+ v8::Local<v8::Value> args[] = {api_hooks};
+ run_js_.Run(function, context, 1, args);
jbroman 2016/12/13 22:12:40 nit: I'd prefer arraysize(args) to 1, in case argu
Devlin 2016/12/14 02:02:14 Done.
}
} // namespace extensions
« no previous file with comments | « extensions/renderer/api_binding_hooks.h ('k') | extensions/renderer/api_binding_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698