Chromium Code Reviews| 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(), |
|
lazyboy
2017/01/05 20:10:35
run_js_ RunJSFunctionSync now, but we don't care a
Devlin
2017/01/05 21:03:51
I think using the sync version is fine here, since
| |
| 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 |