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

Side by Side Diff: extensions/renderer/api_binding_hooks.cc

Issue 2563093002: [Extension Bindings] Add JS custom hook support (Closed)
Patch Set: jbroman's 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 unified diff | 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 »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "extensions/renderer/api_binding_hooks.h" 5 #include "extensions/renderer/api_binding_hooks.h"
6 6
7 #include "base/memory/ptr_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "base/supports_user_data.h"
10 #include "gin/arguments.h"
11 #include "gin/handle.h"
12 #include "gin/object_template_builder.h"
13 #include "gin/per_context_data.h"
14 #include "gin/wrappable.h"
15
7 namespace extensions { 16 namespace extensions {
8 17
9 APIBindingHooks::APIBindingHooks() {} 18 namespace {
19
20 // An interface to allow for registration of custom hooks from JavaScript.
21 // Contains registered hooks for a single API.
22 class JSHookInterface final : public gin::Wrappable<JSHookInterface> {
23 public:
24 using JSHooks = std::map<std::string, v8::Global<v8::Function>>;
25
26 explicit JSHookInterface(const std::string& api_name)
27 : api_name_(api_name) {}
28 ~JSHookInterface() override {}
jbroman 2016/12/14 16:28:28 super-nit: This destructor is implied; you don't n
Devlin 2016/12/14 16:53:52 Done.
29
30 static gin::WrapperInfo kWrapperInfo;
31
32 // gin::Wrappable:
33 gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
34 v8::Isolate* isolate) override {
35 return Wrappable<JSHookInterface>::GetObjectTemplateBuilder(isolate)
36 .SetMethod("setHandleRequest", &JSHookInterface::SetHandleRequest);
37 }
38
39 JSHooks* js_hooks() { return &js_hooks_; }
40
41 private:
42 // Adds a custom hook.
43 void SetHandleRequest(v8::Isolate* isolate,
44 const std::string& method_name,
45 v8::Local<v8::Function> handler) {
46 std::string qualified_method_name =
47 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str());
48 v8::Global<v8::Function>& entry = js_hooks_[qualified_method_name];
49 if (!entry.IsEmpty()) {
50 NOTREACHED() << "Hooks can only be set once.";
51 return;
52 }
53 entry.Reset(isolate, handler);
54 }
55
56 std::string api_name_;
57
58 JSHooks js_hooks_;
59
60 DISALLOW_COPY_AND_ASSIGN(JSHookInterface);
61 };
62
63 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks";
64
65 struct APIHooksPerContextData : public base::SupportsUserData::Data {
66 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {}
67 ~APIHooksPerContextData() override {
68 v8::HandleScope scope(isolate);
69 for (const auto& pair : hook_interfaces) {
70 // We explicitly clear the hook data map here to remove all references to
71 // v8 objects in order to avoid cycles.
72 JSHookInterface* hooks = nullptr;
73 gin::Converter<JSHookInterface*>::FromV8(
74 isolate, pair.second.Get(isolate), &hooks);
75 CHECK(hooks);
76 hooks->js_hooks()->clear();
77 }
78 }
79
80 v8::Isolate* isolate = nullptr;
jbroman 2016/12/14 16:28:27 super-nit: This is explicitly initialized in the c
Devlin 2016/12/14 16:53:52 Personally, I prefer initializing all POD in the d
81
82 std::map<std::string, v8::Global<v8::Object>> hook_interfaces;
83 };
84
85 gin::WrapperInfo JSHookInterface::kWrapperInfo =
86 {gin::kEmbedderNativeGin};
87
88 // Creates and returns JS object for the hook interface to allow for
89 // registering custom hooks from JS.
90 v8::Local<v8::Object> CreateJSHookInterface(const std::string& api_name,
91 v8::Local<v8::Context> context) {
92 gin::PerContextData* per_context_data = gin::PerContextData::From(context);
93 DCHECK(per_context_data);
94 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>(
95 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
96 if (!data) {
97 auto api_data =
98 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate());
99 data = api_data.get();
100 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey,
101 api_data.release());
102 }
103
104 DCHECK(data->hook_interfaces.find(api_name) == data->hook_interfaces.end());
105
106 gin::Handle<JSHookInterface> hooks =
107 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name));
108 CHECK(!hooks.IsEmpty());
109 v8::Local<v8::Value> hooks_value = hooks.ToV8();
110 v8::Local<v8::Object> hooks_object =
111 v8::Local<v8::Object>::Cast(hooks_value);
jbroman 2016/12/14 16:28:27 super-nit: Can combine this with the previous line
Devlin 2016/12/14 16:53:52 Done.
112 data->hook_interfaces[api_name].Reset(context->GetIsolate(), hooks_object);
113
114 return hooks_object;
115 }
116
117 } // namespace
118
119 APIBindingHooks::APIBindingHooks(const binding::RunJSFunction& run_js)
120 : run_js_(run_js) {}
10 APIBindingHooks::~APIBindingHooks() {} 121 APIBindingHooks::~APIBindingHooks() {}
11 122
12 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name, 123 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name,
13 const HandleRequestHook& hook) { 124 const HandleRequestHook& hook) {
14 DCHECK(!hooks_used_) << "Hooks must be registered before the first use!"; 125 DCHECK(!hooks_used_) << "Hooks must be registered before the first use!";
15 request_hooks_[method_name] = hook; 126 request_hooks_[method_name] = hook;
16 } 127 }
17 128
18 APIBindingHooks::HandleRequestHook APIBindingHooks::GetHandleRequest( 129 void APIBindingHooks::RegisterJsSource(v8::Global<v8::String> source,
19 const std::string& method_name) { 130 v8::Global<v8::String> resource_name) {
20 hooks_used_ = true; 131 js_hooks_source_ = std::move(source);
21 auto iter = request_hooks_.find(method_name); 132 js_resource_name_ = std::move(resource_name);
22 if (iter != request_hooks_.end()) 133 }
23 return iter->second;
24 134
25 return HandleRequestHook(); 135 bool APIBindingHooks::HandleRequest(
136 const std::string& api_name,
137 const std::string& method_name,
138 v8::Local<v8::Context> context,
139 const binding::APISignature* signature,
140 gin::Arguments* arguments) {
141 // Easy case: a native custom hook.
142 auto request_hooks_iter = request_hooks_.find(method_name);
143 if (request_hooks_iter != request_hooks_.end()) {
144 request_hooks_iter->second.Run(signature, arguments);
145 return true;
146 }
147
148 // Harder case: looking up a custom hook registered on the context (since
149 // these are JS, each context has a separate instance).
150 gin::PerContextData* per_context_data = gin::PerContextData::From(context);
151 DCHECK(per_context_data);
152 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>(
153 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
154 if (!data)
155 return false;
156
157 auto hook_interface_iter = data->hook_interfaces.find(api_name);
158 if (hook_interface_iter == data->hook_interfaces.end())
159 return false;
160
161 JSHookInterface* hook_interface = nullptr;
162 gin::Converter<JSHookInterface*>::FromV8(
163 context->GetIsolate(),
164 hook_interface_iter->second.Get(context->GetIsolate()), &hook_interface);
165 CHECK(hook_interface);
166
167 auto js_hook_iter = hook_interface->js_hooks()->find(method_name);
168 if (js_hook_iter == hook_interface->js_hooks()->end())
169 return false;
170
171 // Found a JS handler.
172 std::vector<v8::Local<v8::Value>> v8_args;
173 // TODO(devlin): Right now, this doesn't support exceptions or return values,
174 // which we will need to at some point.
175 if (arguments->GetRemaining(&v8_args)) {
176 v8::Local<v8::Function> handler =
177 js_hook_iter->second.Get(context->GetIsolate());
178 run_js_.Run(handler, context, v8_args.size(), v8_args.data());
179 }
180
181 return true;
182 }
183
184 void APIBindingHooks::InitializeInContext(
185 v8::Local<v8::Context> context,
186 const std::string& api_name) {
187 if (js_hooks_source_.IsEmpty())
188 return;
189
190 v8::Local<v8::String> source = js_hooks_source_.Get(context->GetIsolate());
191 v8::Local<v8::String> resource_name =
192 js_resource_name_.Get(context->GetIsolate());
193 v8::Local<v8::Script> script;
194 v8::ScriptOrigin origin(resource_name);
195 if (!v8::Script::Compile(context, source, &origin).ToLocal(&script))
196 return;
197 v8::Local<v8::Value> func_as_value = script->Run();
198 v8::Local<v8::Function> function;
199 if (!gin::ConvertFromV8(context->GetIsolate(), func_as_value, &function))
200 return;
201 v8::Local<v8::Object> api_hooks = CreateJSHookInterface(api_name, context);
202 v8::Local<v8::Value> args[] = {api_hooks};
203 run_js_.Run(function, context, arraysize(args), args);
26 } 204 }
27 205
28 } // namespace extensions 206 } // namespace extensions
OLDNEW
« 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