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

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

Issue 2598123002: [Extensions Bindings] Add support for updateArgumentsPreValidate (Closed)
Patch Set: rebase Created 3 years, 11 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
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" 7 #include "base/memory/ptr_util.h"
8 #include "base/strings/stringprintf.h" 8 #include "base/strings/stringprintf.h"
9 #include "base/supports_user_data.h" 9 #include "base/supports_user_data.h"
10 #include "extensions/renderer/api_signature.h" 10 #include "extensions/renderer/api_signature.h"
11 #include "gin/arguments.h" 11 #include "gin/arguments.h"
12 #include "gin/handle.h" 12 #include "gin/handle.h"
13 #include "gin/object_template_builder.h" 13 #include "gin/object_template_builder.h"
14 #include "gin/per_context_data.h" 14 #include "gin/per_context_data.h"
15 #include "gin/wrappable.h" 15 #include "gin/wrappable.h"
16 16
17 namespace extensions { 17 namespace extensions {
18 18
19 namespace { 19 namespace {
20 20
21 // An interface to allow for registration of custom hooks from JavaScript. 21 // An interface to allow for registration of custom hooks from JavaScript.
22 // Contains registered hooks for a single API. 22 // Contains registered hooks for a single API.
23 class JSHookInterface final : public gin::Wrappable<JSHookInterface> { 23 class JSHookInterface final : public gin::Wrappable<JSHookInterface> {
24 public: 24 public:
25 using JSHooks = std::map<std::string, v8::Global<v8::Function>>;
26
27 explicit JSHookInterface(const std::string& api_name) 25 explicit JSHookInterface(const std::string& api_name)
28 : api_name_(api_name) {} 26 : api_name_(api_name) {}
29 27
30 static gin::WrapperInfo kWrapperInfo; 28 static gin::WrapperInfo kWrapperInfo;
31 29
32 // gin::Wrappable: 30 // gin::Wrappable:
33 gin::ObjectTemplateBuilder GetObjectTemplateBuilder( 31 gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
34 v8::Isolate* isolate) override { 32 v8::Isolate* isolate) override {
35 return Wrappable<JSHookInterface>::GetObjectTemplateBuilder(isolate) 33 return Wrappable<JSHookInterface>::GetObjectTemplateBuilder(isolate)
36 .SetMethod("setHandleRequest", &JSHookInterface::SetHandleRequest); 34 .SetMethod("setHandleRequest", &JSHookInterface::SetHandleRequest)
35 .SetMethod("setUpdateArgumentsPreValidate",
36 &JSHookInterface::SetUpdateArgumentsPreValidate);
37 } 37 }
38 38
39 JSHooks* js_hooks() { return &js_hooks_; } 39 void ClearHooks() {
40 handle_request_hooks_.clear();
41 pre_validation_hooks_.clear();
42 }
43
44 v8::Local<v8::Function> GetHandleRequestHook(const std::string& method_name,
jbroman 2017/01/02 19:46:38 super-nit: can these and GetHookFromMap be const?
Devlin 2017/01/04 17:57:02 Yep, done.
45 v8::Isolate* isolate) {
46 return GetHookFromMap(&handle_request_hooks_, method_name, isolate);
47 }
48
49 v8::Local<v8::Function> GetPreValidationHook(const std::string& method_name,
50 v8::Isolate* isolate) {
51 return GetHookFromMap(&pre_validation_hooks_, method_name, isolate);
52 }
40 53
41 private: 54 private:
42 // Adds a custom hook. 55 using JSHooks = std::map<std::string, v8::Global<v8::Function>>;
43 void SetHandleRequest(v8::Isolate* isolate, 56
44 const std::string& method_name, 57 v8::Local<v8::Function> GetHookFromMap(JSHooks* map,
45 v8::Local<v8::Function> handler) { 58 const std::string& method_name,
59 v8::Isolate* isolate) {
60 auto iter = map->find(method_name);
61 if (iter == map->end())
62 return v8::Local<v8::Function>();
63 return iter->second.Get(isolate);
64 }
65
66 void AddHookToMap(JSHooks* map,
67 v8::Isolate* isolate,
68 const std::string& method_name,
69 v8::Local<v8::Function> hook) {
46 std::string qualified_method_name = 70 std::string qualified_method_name =
47 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str()); 71 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str());
48 v8::Global<v8::Function>& entry = js_hooks_[qualified_method_name]; 72 v8::Global<v8::Function>& entry =
73 (*map)[qualified_method_name];
jbroman 2017/01/02 19:46:38 nit: This should still fit on one line.
Devlin 2017/01/04 17:57:02 huh - wonder why git cl format didn't do this. Do
49 if (!entry.IsEmpty()) { 74 if (!entry.IsEmpty()) {
50 NOTREACHED() << "Hooks can only be set once."; 75 NOTREACHED() << "Hooks can only be set once.";
51 return; 76 return;
52 } 77 }
53 entry.Reset(isolate, handler); 78 entry.Reset(isolate, hook);
79 }
80
81 // Adds a hook to handle the implementation of the API method.
82 void SetHandleRequest(v8::Isolate* isolate,
83 const std::string& method_name,
84 v8::Local<v8::Function> hook) {
85 AddHookToMap(&handle_request_hooks_, isolate, method_name, hook);
86 }
87
88 // Adds a hook to update the arguments passed to the API method before we do
89 // any kind of validation.
90 void SetUpdateArgumentsPreValidate(v8::Isolate* isolate,
91 const std::string& method_name,
92 v8::Local<v8::Function> hook) {
93 AddHookToMap(&pre_validation_hooks_, isolate, method_name, hook);
54 } 94 }
55 95
56 std::string api_name_; 96 std::string api_name_;
57 97
58 JSHooks js_hooks_; 98 JSHooks handle_request_hooks_;
99 JSHooks pre_validation_hooks_;
59 100
60 DISALLOW_COPY_AND_ASSIGN(JSHookInterface); 101 DISALLOW_COPY_AND_ASSIGN(JSHookInterface);
61 }; 102 };
62 103
63 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks"; 104 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks";
64 105
65 struct APIHooksPerContextData : public base::SupportsUserData::Data { 106 struct APIHooksPerContextData : public base::SupportsUserData::Data {
66 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {} 107 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {}
67 ~APIHooksPerContextData() override { 108 ~APIHooksPerContextData() override {
68 v8::HandleScope scope(isolate); 109 v8::HandleScope scope(isolate);
69 for (const auto& pair : hook_interfaces) { 110 for (const auto& pair : hook_interfaces) {
70 // We explicitly clear the hook data map here to remove all references to 111 // We explicitly clear the hook data map here to remove all references to
71 // v8 objects in order to avoid cycles. 112 // v8 objects in order to avoid cycles.
72 JSHookInterface* hooks = nullptr; 113 JSHookInterface* hooks = nullptr;
73 gin::Converter<JSHookInterface*>::FromV8( 114 gin::Converter<JSHookInterface*>::FromV8(
74 isolate, pair.second.Get(isolate), &hooks); 115 isolate, pair.second.Get(isolate), &hooks);
75 CHECK(hooks); 116 CHECK(hooks);
76 hooks->js_hooks()->clear(); 117 hooks->ClearHooks();
77 } 118 }
78 } 119 }
79 120
80 v8::Isolate* isolate; 121 v8::Isolate* isolate;
81 122
82 std::map<std::string, v8::Global<v8::Object>> hook_interfaces; 123 std::map<std::string, v8::Global<v8::Object>> hook_interfaces;
83 }; 124 };
84 125
85 gin::WrapperInfo JSHookInterface::kWrapperInfo = 126 gin::WrapperInfo JSHookInterface::kWrapperInfo =
86 {gin::kEmbedderNativeGin}; 127 {gin::kEmbedderNativeGin};
87 128
129 // Gets the v8::Object of the JSHookInterface, optionally creating it if it
130 // doesn't exist.
131 v8::Local<v8::Object> GetJSHookInterfaceObject(
132 const std::string& api_name,
133 v8::Local<v8::Context> context,
134 bool should_create) {
135 gin::PerContextData* per_context_data = gin::PerContextData::From(context);
136 DCHECK(per_context_data);
137 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>(
138 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
139 if (!data) {
140 if (!should_create)
141 return v8::Local<v8::Object>();
142
143 auto api_data =
144 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate());
145 data = api_data.get();
146 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey,
147 api_data.release());
148 }
149
150 auto iter = data->hook_interfaces.find(api_name);
151 if (iter != data->hook_interfaces.end())
152 return iter->second.Get(context->GetIsolate());
153
154 if (!should_create)
155 return v8::Local<v8::Object>();
156
157 gin::Handle<JSHookInterface> hooks =
158 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name));
159 CHECK(!hooks.IsEmpty());
160 v8::Local<v8::Object> hooks_object = hooks.ToV8().As<v8::Object>();
161 data->hook_interfaces[api_name].Reset(context->GetIsolate(), hooks_object);
162
163 return hooks_object;
164 }
165
88 } // namespace 166 } // namespace
89 167
90 APIBindingHooks::APIBindingHooks(const binding::RunJSFunction& run_js) 168 APIBindingHooks::APIBindingHooks(const binding::RunJSFunctionSync& run_js)
91 : run_js_(run_js) {} 169 : run_js_(run_js) {}
92 APIBindingHooks::~APIBindingHooks() {} 170 APIBindingHooks::~APIBindingHooks() {}
93 171
94 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name, 172 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name,
95 const HandleRequestHook& hook) { 173 const HandleRequestHook& hook) {
96 DCHECK(!hooks_used_) << "Hooks must be registered before the first use!"; 174 DCHECK(!hooks_used_) << "Hooks must be registered before the first use!";
97 request_hooks_[method_name] = hook; 175 request_hooks_[method_name] = hook;
98 } 176 }
99 177
100 void APIBindingHooks::RegisterJsSource(v8::Global<v8::String> source, 178 void APIBindingHooks::RegisterJsSource(v8::Global<v8::String> source,
101 v8::Global<v8::String> resource_name) { 179 v8::Global<v8::String> resource_name) {
102 js_hooks_source_ = std::move(source); 180 js_hooks_source_ = std::move(source);
103 js_resource_name_ = std::move(resource_name); 181 js_resource_name_ = std::move(resource_name);
104 } 182 }
105 183
106 APIBindingHooks::RequestResult APIBindingHooks::HandleRequest( 184 APIBindingHooks::RequestResult APIBindingHooks::HandleRequest(
107 const std::string& api_name, 185 const std::string& api_name,
108 const std::string& method_name, 186 const std::string& method_name,
109 v8::Local<v8::Context> context, 187 v8::Local<v8::Context> context,
110 const APISignature* signature, 188 const APISignature* signature,
111 gin::Arguments* arguments, 189 std::vector<v8::Local<v8::Value>>* arguments,
112 const ArgumentSpec::RefMap& type_refs) { 190 const ArgumentSpec::RefMap& type_refs) {
113 // Easy case: a native custom hook. 191 // Easy case: a native custom hook.
114 auto request_hooks_iter = request_hooks_.find(method_name); 192 auto request_hooks_iter = request_hooks_.find(method_name);
115 if (request_hooks_iter != request_hooks_.end()) { 193 if (request_hooks_iter != request_hooks_.end()) {
116 RequestResult result = 194 RequestResult result =
117 request_hooks_iter->second.Run(signature, arguments, type_refs); 195 request_hooks_iter->second.Run(
196 signature, context, arguments, type_refs);
118 // Right now, it doesn't make sense to register a request handler that 197 // Right now, it doesn't make sense to register a request handler that
119 // doesn't handle the request. 198 // doesn't handle the request.
120 DCHECK_NE(RequestResult::NOT_HANDLED, result); 199 DCHECK_NE(RequestResult::NOT_HANDLED, result);
121 return result; 200 return result;
122 } 201 }
123 202
124 // Harder case: looking up a custom hook registered on the context (since 203 // Harder case: looking up a custom hook registered on the context (since
125 // these are JS, each context has a separate instance). 204 // these are JS, each context has a separate instance).
126 gin::PerContextData* per_context_data = gin::PerContextData::From(context); 205 v8::Local<v8::Object> hook_interface_object =
127 DCHECK(per_context_data); 206 GetJSHookInterfaceObject(api_name, context, false);
128 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>( 207 if (hook_interface_object.IsEmpty())
129 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey));
130 if (!data)
131 return RequestResult::NOT_HANDLED; 208 return RequestResult::NOT_HANDLED;
132 209
133 auto hook_interface_iter = data->hook_interfaces.find(api_name); 210 v8::Isolate* isolate = context->GetIsolate();
134 if (hook_interface_iter == data->hook_interfaces.end())
135 return RequestResult::NOT_HANDLED;
136 211
137 JSHookInterface* hook_interface = nullptr; 212 JSHookInterface* hook_interface = nullptr;
138 gin::Converter<JSHookInterface*>::FromV8( 213 gin::Converter<JSHookInterface*>::FromV8(
139 context->GetIsolate(), 214 isolate,
140 hook_interface_iter->second.Get(context->GetIsolate()), &hook_interface); 215 hook_interface_object, &hook_interface);
141 CHECK(hook_interface); 216 CHECK(hook_interface);
142 217
143 auto js_hook_iter = hook_interface->js_hooks()->find(method_name); 218 v8::Local<v8::Function> pre_validate_hook =
144 if (js_hook_iter == hook_interface->js_hooks()->end()) 219 hook_interface->GetPreValidationHook(method_name, isolate);
145 return RequestResult::NOT_HANDLED; 220 if (!pre_validate_hook.IsEmpty()) {
221 v8::TryCatch try_catch(isolate);
222 // TODO(devlin): What to do with the result of this function call? Can it
223 // only fail in the case we've already thrown?
224 UpdateArguments(pre_validate_hook, context, arguments);
225 if (try_catch.HasCaught()) {
226 try_catch.ReThrow();
227 return RequestResult::THROWN;
228 }
229 }
146 230
147 // Found a JS handler. 231 v8::Local<v8::Function> handle_request =
148 std::vector<v8::Local<v8::Value>> v8_args; 232 hook_interface->GetHandleRequestHook(method_name, isolate);
149 std::string error;
150 if (!signature->ParseArgumentsToV8(arguments, type_refs, &v8_args, &error))
151 return RequestResult::INVALID_INVOCATION;
152 233
jbroman 2017/01/02 19:46:38 super-nit: for consistency with the PreValidationH
Devlin 2017/01/04 17:57:02 Done.
153 // TODO(devlin): Right now, this doesn't support exceptions or return values, 234 if (!handle_request.IsEmpty()) {
154 // which we will need to at some point. 235 v8::TryCatch try_catch(isolate);
155 v8::Local<v8::Function> handler = 236 std::vector<v8::Local<v8::Value>> parsed_v8_args;
156 js_hook_iter->second.Get(context->GetIsolate()); 237 std::string error;
157 run_js_.Run(handler, context, v8_args.size(), v8_args.data()); 238 bool success = signature->ParseArgumentsToV8(context, *arguments, type_refs,
239 &parsed_v8_args, &error);
240 if (try_catch.HasCaught()) {
241 try_catch.ReThrow();
242 return RequestResult::THROWN;
243 }
244 if (!success)
245 return RequestResult::INVALID_INVOCATION;
158 246
159 return RequestResult::HANDLED; 247 run_js_.Run(handle_request, context, parsed_v8_args.size(),
248 parsed_v8_args.data());
249 return RequestResult::HANDLED;
250 }
251
252 return RequestResult::NOT_HANDLED;
160 } 253 }
161 254
162 void APIBindingHooks::InitializeInContext( 255 void APIBindingHooks::InitializeInContext(
163 v8::Local<v8::Context> context, 256 v8::Local<v8::Context> context,
164 const std::string& api_name) { 257 const std::string& api_name) {
165 if (js_hooks_source_.IsEmpty()) 258 if (js_hooks_source_.IsEmpty())
166 return; 259 return;
167 260
168 v8::Local<v8::String> source = js_hooks_source_.Get(context->GetIsolate()); 261 v8::Local<v8::String> source = js_hooks_source_.Get(context->GetIsolate());
169 v8::Local<v8::String> resource_name = 262 v8::Local<v8::String> resource_name =
170 js_resource_name_.Get(context->GetIsolate()); 263 js_resource_name_.Get(context->GetIsolate());
171 v8::Local<v8::Script> script; 264 v8::Local<v8::Script> script;
172 v8::ScriptOrigin origin(resource_name); 265 v8::ScriptOrigin origin(resource_name);
173 if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) 266 if (!v8::Script::Compile(context, source, &origin).ToLocal(&script))
174 return; 267 return;
175 v8::Local<v8::Value> func_as_value = script->Run(); 268 v8::Local<v8::Value> func_as_value = script->Run();
176 v8::Local<v8::Function> function; 269 v8::Local<v8::Function> function;
177 if (!gin::ConvertFromV8(context->GetIsolate(), func_as_value, &function)) 270 if (!gin::ConvertFromV8(context->GetIsolate(), func_as_value, &function))
178 return; 271 return;
179 v8::Local<v8::Value> api_hooks = GetJSHookInterface(api_name, context); 272 v8::Local<v8::Value> api_hooks = GetJSHookInterface(api_name, context);
180 v8::Local<v8::Value> args[] = {api_hooks}; 273 v8::Local<v8::Value> args[] = {api_hooks};
181 run_js_.Run(function, context, arraysize(args), args); 274 run_js_.Run(function, context, arraysize(args), args);
182 } 275 }
183 276
184 v8::Local<v8::Object> APIBindingHooks::GetJSHookInterface( 277 v8::Local<v8::Object> APIBindingHooks::GetJSHookInterface(
185 const std::string& api_name, 278 const std::string& api_name,
186 v8::Local<v8::Context> context) { 279 v8::Local<v8::Context> context) {
187 gin::PerContextData* per_context_data = gin::PerContextData::From(context); 280 return GetJSHookInterfaceObject(api_name, context, true);
188 DCHECK(per_context_data); 281 }
189 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>( 282
190 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey)); 283 bool APIBindingHooks::UpdateArguments(
191 if (!data) { 284 v8::Local<v8::Function> function,
192 auto api_data = 285 v8::Local<v8::Context> context,
193 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate()); 286 std::vector<v8::Local<v8::Value>>* arguments) {
194 data = api_data.get(); 287 v8::Global<v8::Value> global_result =
195 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey, 288 run_js_.Run(function, context,
196 api_data.release()); 289 arguments->size(), arguments->data());
290 DCHECK(!global_result.IsEmpty());
291 v8::Local<v8::Value> result = global_result.Get(context->GetIsolate());
292 std::vector<v8::Local<v8::Value>> new_args;
293 if (result.IsEmpty() ||
294 !gin::Converter<std::vector<v8::Local<v8::Value>>>::FromV8(
295 context->GetIsolate(), result, &new_args)) {
296 return false;
197 } 297 }
198 298 arguments->swap(new_args);
199 auto iter = data->hook_interfaces.find(api_name); 299 return true;
200 if (iter != data->hook_interfaces.end())
201 return iter->second.Get(context->GetIsolate());
202
203 gin::Handle<JSHookInterface> hooks =
204 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name));
205 CHECK(!hooks.IsEmpty());
206 v8::Local<v8::Object> hooks_object = hooks.ToV8().As<v8::Object>();
207 data->hook_interfaces[api_name].Reset(context->GetIsolate(), hooks_object);
208
209 return hooks_object;
210 } 300 }
211 301
212 } // namespace extensions 302 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698