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