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

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

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

Powered by Google App Engine
This is Rietveld 408576698