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

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

Issue 2947463002: [Extensions Bindings] Add a bindings/ subdirectory under renderer (Closed)
Patch Set: . Created 3 years, 5 months 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
OLDNEW
(Empty)
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/api_binding_hooks.h"
6
7 #include "base/memory/ptr_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "base/supports_user_data.h"
10 #include "extensions/renderer/api_binding_hooks_delegate.h"
11 #include "extensions/renderer/api_signature.h"
12 #include "gin/arguments.h"
13 #include "gin/handle.h"
14 #include "gin/object_template_builder.h"
15 #include "gin/per_context_data.h"
16 #include "gin/wrappable.h"
17
18 namespace extensions {
19
20 namespace {
21
22 // An interface to allow for registration of custom hooks from JavaScript.
23 // Contains registered hooks for a single API.
24 class JSHookInterface final : public gin::Wrappable<JSHookInterface> {
25 public:
26 explicit JSHookInterface(const std::string& api_name)
27 : api_name_(api_name) {}
28
29 static gin::WrapperInfo kWrapperInfo;
30
31 // gin::Wrappable:
32 gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
33 v8::Isolate* isolate) override {
34 return Wrappable<JSHookInterface>::GetObjectTemplateBuilder(isolate)
35 .SetMethod("setHandleRequest", &JSHookInterface::SetHandleRequest)
36 .SetMethod("setUpdateArgumentsPreValidate",
37 &JSHookInterface::SetUpdateArgumentsPreValidate)
38 .SetMethod("setUpdateArgumentsPostValidate",
39 &JSHookInterface::SetUpdateArgumentsPostValidate)
40 .SetMethod("setCustomCallback", &JSHookInterface::SetCustomCallback);
41 }
42
43 void ClearHooks() {
44 handle_request_hooks_.clear();
45 pre_validation_hooks_.clear();
46 post_validation_hooks_.clear();
47 }
48
49 v8::Local<v8::Function> GetHandleRequestHook(const std::string& method_name,
50 v8::Isolate* isolate) const {
51 return GetHookFromMap(handle_request_hooks_, method_name, isolate);
52 }
53
54 v8::Local<v8::Function> GetPreValidationHook(const std::string& method_name,
55 v8::Isolate* isolate) const {
56 return GetHookFromMap(pre_validation_hooks_, method_name, isolate);
57 }
58
59 v8::Local<v8::Function> GetPostValidationHook(const std::string& method_name,
60 v8::Isolate* isolate) const {
61 return GetHookFromMap(post_validation_hooks_, method_name, isolate);
62 }
63
64 v8::Local<v8::Function> GetCustomCallback(const std::string& method_name,
65 v8::Isolate* isolate) const {
66 return GetHookFromMap(custom_callback_hooks_, method_name, isolate);
67 }
68
69 private:
70 using JSHooks = std::map<std::string, v8::Global<v8::Function>>;
71
72 v8::Local<v8::Function> GetHookFromMap(const JSHooks& map,
73 const std::string& method_name,
74 v8::Isolate* isolate) const {
75 auto iter = map.find(method_name);
76 if (iter == map.end())
77 return v8::Local<v8::Function>();
78 return iter->second.Get(isolate);
79 }
80
81 void AddHookToMap(JSHooks* map,
82 v8::Isolate* isolate,
83 const std::string& method_name,
84 v8::Local<v8::Function> hook) {
85 std::string qualified_method_name =
86 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str());
87 v8::Global<v8::Function>& entry = (*map)[qualified_method_name];
88 if (!entry.IsEmpty()) {
89 NOTREACHED() << "Hooks can only be set once.";
90 return;
91 }
92 entry.Reset(isolate, hook);
93 }
94
95 // Adds a hook to handle the implementation of the API method.
96 void SetHandleRequest(v8::Isolate* isolate,
97 const std::string& method_name,
98 v8::Local<v8::Function> hook) {
99 AddHookToMap(&handle_request_hooks_, isolate, method_name, hook);
100 }
101
102 // Adds a hook to update the arguments passed to the API method before we do
103 // any kind of validation.
104 void SetUpdateArgumentsPreValidate(v8::Isolate* isolate,
105 const std::string& method_name,
106 v8::Local<v8::Function> hook) {
107 AddHookToMap(&pre_validation_hooks_, isolate, method_name, hook);
108 }
109
110 void SetUpdateArgumentsPostValidate(v8::Isolate* isolate,
111 const std::string& method_name,
112 v8::Local<v8::Function> hook) {
113 AddHookToMap(&post_validation_hooks_, isolate, method_name, hook);
114 }
115
116 void SetCustomCallback(v8::Isolate* isolate,
117 const std::string& method_name,
118 v8::Local<v8::Function> hook) {
119 AddHookToMap(&custom_callback_hooks_, isolate, method_name, hook);
120 }
121
122 std::string api_name_;
123
124 JSHooks handle_request_hooks_;
125 JSHooks pre_validation_hooks_;
126 JSHooks post_validation_hooks_;
127 JSHooks custom_callback_hooks_;
128
129 DISALLOW_COPY_AND_ASSIGN(JSHookInterface);
130 };
131
132 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks";
133
134 struct APIHooksPerContextData : public base::SupportsUserData::Data {
135 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {}
136 ~APIHooksPerContextData() override {
137 v8::HandleScope scope(isolate);
138 for (const auto& pair : hook_interfaces) {
139 // We explicitly clear the hook data map here to remove all references to
140 // v8 objects in order to avoid cycles.
141 JSHookInterface* hooks = nullptr;
142 gin::Converter<JSHookInterface*>::FromV8(
143 isolate, pair.second.Get(isolate), &hooks);
144 CHECK(hooks);
145 hooks->ClearHooks();
146 }
147 }
148
149 v8::Isolate* isolate;
150
151 std::map<std::string, v8::Global<v8::Object>> hook_interfaces;
152 };
153
154 gin::WrapperInfo JSHookInterface::kWrapperInfo =
155 {gin::kEmbedderNativeGin};
156
157 // Gets the v8::Object of the JSHookInterface, optionally creating it if it
158 // doesn't exist.
159 v8::Local<v8::Object> GetJSHookInterfaceObject(
160 const std::string& api_name,
161 v8::Local<v8::Context> context,
162 bool should_create) {
163 gin::PerContextData* per_context_data = gin::PerContextData::From(context);
164 DCHECK(per_context_data);
165 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>(
166 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
167 if (!data) {
168 if (!should_create)
169 return v8::Local<v8::Object>();
170
171 auto api_data =
172 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate());
173 data = api_data.get();
174 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey,
175 std::move(api_data));
176 }
177
178 auto iter = data->hook_interfaces.find(api_name);
179 if (iter != data->hook_interfaces.end())
180 return iter->second.Get(context->GetIsolate());
181
182 if (!should_create)
183 return v8::Local<v8::Object>();
184
185 gin::Handle<JSHookInterface> hooks =
186 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name));
187 CHECK(!hooks.IsEmpty());
188 v8::Local<v8::Object> hooks_object = hooks.ToV8().As<v8::Object>();
189 data->hook_interfaces[api_name].Reset(context->GetIsolate(), hooks_object);
190
191 return hooks_object;
192 }
193
194 } // namespace
195
196 APIBindingHooks::RequestResult::RequestResult(ResultCode code) : code(code) {}
197 APIBindingHooks::RequestResult::RequestResult(
198 ResultCode code,
199 v8::Local<v8::Function> custom_callback)
200 : code(code), custom_callback(custom_callback) {}
201 APIBindingHooks::RequestResult::RequestResult(std::string invocation_error)
202 : code(INVALID_INVOCATION), error(std::move(invocation_error)) {}
203 APIBindingHooks::RequestResult::~RequestResult() {}
204 APIBindingHooks::RequestResult::RequestResult(const RequestResult& other) =
205 default;
206
207 APIBindingHooks::APIBindingHooks(const std::string& api_name,
208 const binding::RunJSFunctionSync& run_js)
209 : api_name_(api_name), run_js_(run_js) {}
210 APIBindingHooks::~APIBindingHooks() {}
211
212 APIBindingHooks::RequestResult APIBindingHooks::RunHooks(
213 const std::string& method_name,
214 v8::Local<v8::Context> context,
215 const APISignature* signature,
216 std::vector<v8::Local<v8::Value>>* arguments,
217 const APITypeReferenceMap& type_refs) {
218 // Easy case: a native custom hook.
219 if (delegate_) {
220 RequestResult result = delegate_->HandleRequest(
221 method_name, signature, context, arguments, type_refs);
222 if (result.code != RequestResult::NOT_HANDLED)
223 return result;
224 }
225
226 // Harder case: looking up a custom hook registered on the context (since
227 // these are JS, each context has a separate instance).
228 v8::Local<v8::Object> hook_interface_object =
229 GetJSHookInterfaceObject(api_name_, context, false);
230 if (hook_interface_object.IsEmpty())
231 return RequestResult(RequestResult::NOT_HANDLED);
232
233 v8::Isolate* isolate = context->GetIsolate();
234
235 JSHookInterface* hook_interface = nullptr;
236 gin::Converter<JSHookInterface*>::FromV8(
237 isolate,
238 hook_interface_object, &hook_interface);
239 CHECK(hook_interface);
240
241 v8::Local<v8::Function> pre_validate_hook =
242 hook_interface->GetPreValidationHook(method_name, isolate);
243 v8::TryCatch try_catch(isolate);
244 if (!pre_validate_hook.IsEmpty()) {
245 // TODO(devlin): What to do with the result of this function call? Can it
246 // only fail in the case we've already thrown?
247 UpdateArguments(pre_validate_hook, context, arguments);
248 if (try_catch.HasCaught()) {
249 try_catch.ReThrow();
250 return RequestResult(RequestResult::THROWN);
251 }
252 }
253
254 v8::Local<v8::Function> post_validate_hook =
255 hook_interface->GetPostValidationHook(method_name, isolate);
256 v8::Local<v8::Function> handle_request =
257 hook_interface->GetHandleRequestHook(method_name, isolate);
258 v8::Local<v8::Function> custom_callback =
259 hook_interface->GetCustomCallback(method_name, isolate);
260
261 // If both the post validation hook and the handle request hook are empty,
262 // we're done...
263 if (post_validate_hook.IsEmpty() && handle_request.IsEmpty())
264 return RequestResult(RequestResult::NOT_HANDLED, custom_callback);
265
266 {
267 // ... otherwise, we have to validate the arguments.
268 std::vector<v8::Local<v8::Value>> parsed_v8_args;
269 std::string error;
270 bool success = signature->ParseArgumentsToV8(context, *arguments, type_refs,
271 &parsed_v8_args, &error);
272 if (try_catch.HasCaught()) {
273 try_catch.ReThrow();
274 return RequestResult(RequestResult::THROWN);
275 }
276 if (!success)
277 return RequestResult(std::move(error));
278 arguments->swap(parsed_v8_args);
279 }
280
281 bool updated_args = false;
282 if (!post_validate_hook.IsEmpty()) {
283 updated_args = true;
284 UpdateArguments(post_validate_hook, context, arguments);
285 if (try_catch.HasCaught()) {
286 try_catch.ReThrow();
287 return RequestResult(RequestResult::THROWN);
288 }
289 }
290
291 if (handle_request.IsEmpty()) {
292 RequestResult::ResultCode result = updated_args
293 ? RequestResult::ARGUMENTS_UPDATED
294 : RequestResult::NOT_HANDLED;
295 return RequestResult(result, custom_callback);
296 }
297
298 v8::Global<v8::Value> global_result =
299 run_js_.Run(handle_request, context, arguments->size(),
300 arguments->data());
301 if (try_catch.HasCaught()) {
302 try_catch.ReThrow();
303 return RequestResult(RequestResult::THROWN);
304 }
305 RequestResult result(RequestResult::HANDLED, custom_callback);
306 if (!global_result.IsEmpty())
307 result.return_value = global_result.Get(isolate);
308 return result;
309 }
310
311 v8::Local<v8::Object> APIBindingHooks::GetJSHookInterface(
312 v8::Local<v8::Context> context) {
313 return GetJSHookInterfaceObject(api_name_, context, true);
314 }
315
316 v8::Local<v8::Function> APIBindingHooks::GetCustomJSCallback(
317 const std::string& name,
318 v8::Local<v8::Context> context) {
319 v8::Local<v8::Object> hooks =
320 GetJSHookInterfaceObject(api_name_, context, false);
321 if (hooks.IsEmpty())
322 return v8::Local<v8::Function>();
323 JSHookInterface* hook_interface = nullptr;
324 gin::Converter<JSHookInterface*>::FromV8(context->GetIsolate(), hooks,
325 &hook_interface);
326 CHECK(hook_interface);
327
328 return hook_interface->GetCustomCallback(name, context->GetIsolate());
329 }
330
331 bool APIBindingHooks::CreateCustomEvent(v8::Local<v8::Context> context,
332 const std::string& event_name,
333 v8::Local<v8::Value>* event_out) {
334 return delegate_ &&
335 delegate_->CreateCustomEvent(context, run_js_, event_name, event_out);
336 }
337
338 void APIBindingHooks::InitializeTemplate(
339 v8::Isolate* isolate,
340 v8::Local<v8::ObjectTemplate> object_template,
341 const APITypeReferenceMap& type_refs) {
342 if (delegate_)
343 delegate_->InitializeTemplate(isolate, object_template, type_refs);
344 }
345
346 void APIBindingHooks::SetDelegate(
347 std::unique_ptr<APIBindingHooksDelegate> delegate) {
348 delegate_ = std::move(delegate);
349 }
350
351 bool APIBindingHooks::UpdateArguments(
352 v8::Local<v8::Function> function,
353 v8::Local<v8::Context> context,
354 std::vector<v8::Local<v8::Value>>* arguments) {
355 v8::Global<v8::Value> global_result;
356 {
357 v8::TryCatch try_catch(context->GetIsolate());
358 global_result = run_js_.Run(function, context,
359 arguments->size(), arguments->data());
360 if (try_catch.HasCaught()) {
361 try_catch.ReThrow();
362 return false;
363 }
364 }
365 DCHECK(!global_result.IsEmpty());
366 v8::Local<v8::Value> result = global_result.Get(context->GetIsolate());
367 std::vector<v8::Local<v8::Value>> new_args;
368 if (result.IsEmpty() ||
369 !gin::Converter<std::vector<v8::Local<v8::Value>>>::FromV8(
370 context->GetIsolate(), result, &new_args)) {
371 return false;
372 }
373 arguments->swap(new_args);
374 return true;
375 }
376
377 } // namespace extensions
OLDNEW
« no previous file with comments | « extensions/renderer/api_binding_hooks.h ('k') | extensions/renderer/api_binding_hooks_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698