OLD | NEW |
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 |
OLD | NEW |