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