| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "extensions/renderer/api_binding_hooks.h" | |
| 6 | |
| 7 #include "base/memory/ptr_util.h" | |
| 8 #include "base/strings/stringprintf.h" | |
| 9 #include "base/supports_user_data.h" | |
| 10 #include "extensions/renderer/api_binding_hooks_delegate.h" | |
| 11 #include "extensions/renderer/api_signature.h" | |
| 12 #include "gin/arguments.h" | |
| 13 #include "gin/handle.h" | |
| 14 #include "gin/object_template_builder.h" | |
| 15 #include "gin/per_context_data.h" | |
| 16 #include "gin/wrappable.h" | |
| 17 | |
| 18 namespace extensions { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 // An interface to allow for registration of custom hooks from JavaScript. | |
| 23 // Contains registered hooks for a single API. | |
| 24 class JSHookInterface final : public gin::Wrappable<JSHookInterface> { | |
| 25 public: | |
| 26 explicit JSHookInterface(const std::string& api_name) | |
| 27 : api_name_(api_name) {} | |
| 28 | |
| 29 static gin::WrapperInfo kWrapperInfo; | |
| 30 | |
| 31 // gin::Wrappable: | |
| 32 gin::ObjectTemplateBuilder GetObjectTemplateBuilder( | |
| 33 v8::Isolate* isolate) override { | |
| 34 return Wrappable<JSHookInterface>::GetObjectTemplateBuilder(isolate) | |
| 35 .SetMethod("setHandleRequest", &JSHookInterface::SetHandleRequest) | |
| 36 .SetMethod("setUpdateArgumentsPreValidate", | |
| 37 &JSHookInterface::SetUpdateArgumentsPreValidate) | |
| 38 .SetMethod("setUpdateArgumentsPostValidate", | |
| 39 &JSHookInterface::SetUpdateArgumentsPostValidate) | |
| 40 .SetMethod("setCustomCallback", &JSHookInterface::SetCustomCallback); | |
| 41 } | |
| 42 | |
| 43 void ClearHooks() { | |
| 44 handle_request_hooks_.clear(); | |
| 45 pre_validation_hooks_.clear(); | |
| 46 post_validation_hooks_.clear(); | |
| 47 } | |
| 48 | |
| 49 v8::Local<v8::Function> GetHandleRequestHook(const std::string& method_name, | |
| 50 v8::Isolate* isolate) const { | |
| 51 return GetHookFromMap(handle_request_hooks_, method_name, isolate); | |
| 52 } | |
| 53 | |
| 54 v8::Local<v8::Function> GetPreValidationHook(const std::string& method_name, | |
| 55 v8::Isolate* isolate) const { | |
| 56 return GetHookFromMap(pre_validation_hooks_, method_name, isolate); | |
| 57 } | |
| 58 | |
| 59 v8::Local<v8::Function> GetPostValidationHook(const std::string& method_name, | |
| 60 v8::Isolate* isolate) const { | |
| 61 return GetHookFromMap(post_validation_hooks_, method_name, isolate); | |
| 62 } | |
| 63 | |
| 64 v8::Local<v8::Function> GetCustomCallback(const std::string& method_name, | |
| 65 v8::Isolate* isolate) const { | |
| 66 return GetHookFromMap(custom_callback_hooks_, method_name, isolate); | |
| 67 } | |
| 68 | |
| 69 private: | |
| 70 using JSHooks = std::map<std::string, v8::Global<v8::Function>>; | |
| 71 | |
| 72 v8::Local<v8::Function> GetHookFromMap(const JSHooks& map, | |
| 73 const std::string& method_name, | |
| 74 v8::Isolate* isolate) const { | |
| 75 auto iter = map.find(method_name); | |
| 76 if (iter == map.end()) | |
| 77 return v8::Local<v8::Function>(); | |
| 78 return iter->second.Get(isolate); | |
| 79 } | |
| 80 | |
| 81 void AddHookToMap(JSHooks* map, | |
| 82 v8::Isolate* isolate, | |
| 83 const std::string& method_name, | |
| 84 v8::Local<v8::Function> hook) { | |
| 85 std::string qualified_method_name = | |
| 86 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str()); | |
| 87 v8::Global<v8::Function>& entry = (*map)[qualified_method_name]; | |
| 88 if (!entry.IsEmpty()) { | |
| 89 NOTREACHED() << "Hooks can only be set once."; | |
| 90 return; | |
| 91 } | |
| 92 entry.Reset(isolate, hook); | |
| 93 } | |
| 94 | |
| 95 // Adds a hook to handle the implementation of the API method. | |
| 96 void SetHandleRequest(v8::Isolate* isolate, | |
| 97 const std::string& method_name, | |
| 98 v8::Local<v8::Function> hook) { | |
| 99 AddHookToMap(&handle_request_hooks_, isolate, method_name, hook); | |
| 100 } | |
| 101 | |
| 102 // Adds a hook to update the arguments passed to the API method before we do | |
| 103 // any kind of validation. | |
| 104 void SetUpdateArgumentsPreValidate(v8::Isolate* isolate, | |
| 105 const std::string& method_name, | |
| 106 v8::Local<v8::Function> hook) { | |
| 107 AddHookToMap(&pre_validation_hooks_, isolate, method_name, hook); | |
| 108 } | |
| 109 | |
| 110 void SetUpdateArgumentsPostValidate(v8::Isolate* isolate, | |
| 111 const std::string& method_name, | |
| 112 v8::Local<v8::Function> hook) { | |
| 113 AddHookToMap(&post_validation_hooks_, isolate, method_name, hook); | |
| 114 } | |
| 115 | |
| 116 void SetCustomCallback(v8::Isolate* isolate, | |
| 117 const std::string& method_name, | |
| 118 v8::Local<v8::Function> hook) { | |
| 119 AddHookToMap(&custom_callback_hooks_, isolate, method_name, hook); | |
| 120 } | |
| 121 | |
| 122 std::string api_name_; | |
| 123 | |
| 124 JSHooks handle_request_hooks_; | |
| 125 JSHooks pre_validation_hooks_; | |
| 126 JSHooks post_validation_hooks_; | |
| 127 JSHooks custom_callback_hooks_; | |
| 128 | |
| 129 DISALLOW_COPY_AND_ASSIGN(JSHookInterface); | |
| 130 }; | |
| 131 | |
| 132 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks"; | |
| 133 | |
| 134 struct APIHooksPerContextData : public base::SupportsUserData::Data { | |
| 135 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {} | |
| 136 ~APIHooksPerContextData() override { | |
| 137 v8::HandleScope scope(isolate); | |
| 138 for (const auto& pair : hook_interfaces) { | |
| 139 // We explicitly clear the hook data map here to remove all references to | |
| 140 // v8 objects in order to avoid cycles. | |
| 141 JSHookInterface* hooks = nullptr; | |
| 142 gin::Converter<JSHookInterface*>::FromV8( | |
| 143 isolate, pair.second.Get(isolate), &hooks); | |
| 144 CHECK(hooks); | |
| 145 hooks->ClearHooks(); | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 v8::Isolate* isolate; | |
| 150 | |
| 151 std::map<std::string, v8::Global<v8::Object>> hook_interfaces; | |
| 152 }; | |
| 153 | |
| 154 gin::WrapperInfo JSHookInterface::kWrapperInfo = | |
| 155 {gin::kEmbedderNativeGin}; | |
| 156 | |
| 157 // Gets the v8::Object of the JSHookInterface, optionally creating it if it | |
| 158 // doesn't exist. | |
| 159 v8::Local<v8::Object> GetJSHookInterfaceObject( | |
| 160 const std::string& api_name, | |
| 161 v8::Local<v8::Context> context, | |
| 162 bool should_create) { | |
| 163 gin::PerContextData* per_context_data = gin::PerContextData::From(context); | |
| 164 DCHECK(per_context_data); | |
| 165 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>( | |
| 166 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey)); | |
| 167 if (!data) { | |
| 168 if (!should_create) | |
| 169 return v8::Local<v8::Object>(); | |
| 170 | |
| 171 auto api_data = | |
| 172 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate()); | |
| 173 data = api_data.get(); | |
| 174 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey, | |
| 175 std::move(api_data)); | |
| 176 } | |
| 177 | |
| 178 auto iter = data->hook_interfaces.find(api_name); | |
| 179 if (iter != data->hook_interfaces.end()) | |
| 180 return iter->second.Get(context->GetIsolate()); | |
| 181 | |
| 182 if (!should_create) | |
| 183 return v8::Local<v8::Object>(); | |
| 184 | |
| 185 gin::Handle<JSHookInterface> hooks = | |
| 186 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name)); | |
| 187 CHECK(!hooks.IsEmpty()); | |
| 188 v8::Local<v8::Object> hooks_object = hooks.ToV8().As<v8::Object>(); | |
| 189 data->hook_interfaces[api_name].Reset(context->GetIsolate(), hooks_object); | |
| 190 | |
| 191 return hooks_object; | |
| 192 } | |
| 193 | |
| 194 } // namespace | |
| 195 | |
| 196 APIBindingHooks::RequestResult::RequestResult(ResultCode code) : code(code) {} | |
| 197 APIBindingHooks::RequestResult::RequestResult( | |
| 198 ResultCode code, | |
| 199 v8::Local<v8::Function> custom_callback) | |
| 200 : code(code), custom_callback(custom_callback) {} | |
| 201 APIBindingHooks::RequestResult::RequestResult(std::string invocation_error) | |
| 202 : code(INVALID_INVOCATION), error(std::move(invocation_error)) {} | |
| 203 APIBindingHooks::RequestResult::~RequestResult() {} | |
| 204 APIBindingHooks::RequestResult::RequestResult(const RequestResult& other) = | |
| 205 default; | |
| 206 | |
| 207 APIBindingHooks::APIBindingHooks(const std::string& api_name, | |
| 208 const binding::RunJSFunctionSync& run_js) | |
| 209 : api_name_(api_name), run_js_(run_js) {} | |
| 210 APIBindingHooks::~APIBindingHooks() {} | |
| 211 | |
| 212 APIBindingHooks::RequestResult APIBindingHooks::RunHooks( | |
| 213 const std::string& method_name, | |
| 214 v8::Local<v8::Context> context, | |
| 215 const APISignature* signature, | |
| 216 std::vector<v8::Local<v8::Value>>* arguments, | |
| 217 const APITypeReferenceMap& type_refs) { | |
| 218 // Easy case: a native custom hook. | |
| 219 if (delegate_) { | |
| 220 RequestResult result = delegate_->HandleRequest( | |
| 221 method_name, signature, context, arguments, type_refs); | |
| 222 if (result.code != RequestResult::NOT_HANDLED) | |
| 223 return result; | |
| 224 } | |
| 225 | |
| 226 // Harder case: looking up a custom hook registered on the context (since | |
| 227 // these are JS, each context has a separate instance). | |
| 228 v8::Local<v8::Object> hook_interface_object = | |
| 229 GetJSHookInterfaceObject(api_name_, context, false); | |
| 230 if (hook_interface_object.IsEmpty()) | |
| 231 return RequestResult(RequestResult::NOT_HANDLED); | |
| 232 | |
| 233 v8::Isolate* isolate = context->GetIsolate(); | |
| 234 | |
| 235 JSHookInterface* hook_interface = nullptr; | |
| 236 gin::Converter<JSHookInterface*>::FromV8( | |
| 237 isolate, | |
| 238 hook_interface_object, &hook_interface); | |
| 239 CHECK(hook_interface); | |
| 240 | |
| 241 v8::Local<v8::Function> pre_validate_hook = | |
| 242 hook_interface->GetPreValidationHook(method_name, isolate); | |
| 243 v8::TryCatch try_catch(isolate); | |
| 244 if (!pre_validate_hook.IsEmpty()) { | |
| 245 // TODO(devlin): What to do with the result of this function call? Can it | |
| 246 // only fail in the case we've already thrown? | |
| 247 UpdateArguments(pre_validate_hook, context, arguments); | |
| 248 if (try_catch.HasCaught()) { | |
| 249 try_catch.ReThrow(); | |
| 250 return RequestResult(RequestResult::THROWN); | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 v8::Local<v8::Function> post_validate_hook = | |
| 255 hook_interface->GetPostValidationHook(method_name, isolate); | |
| 256 v8::Local<v8::Function> handle_request = | |
| 257 hook_interface->GetHandleRequestHook(method_name, isolate); | |
| 258 v8::Local<v8::Function> custom_callback = | |
| 259 hook_interface->GetCustomCallback(method_name, isolate); | |
| 260 | |
| 261 // If both the post validation hook and the handle request hook are empty, | |
| 262 // we're done... | |
| 263 if (post_validate_hook.IsEmpty() && handle_request.IsEmpty()) | |
| 264 return RequestResult(RequestResult::NOT_HANDLED, custom_callback); | |
| 265 | |
| 266 { | |
| 267 // ... otherwise, we have to validate the arguments. | |
| 268 std::vector<v8::Local<v8::Value>> parsed_v8_args; | |
| 269 std::string error; | |
| 270 bool success = signature->ParseArgumentsToV8(context, *arguments, type_refs, | |
| 271 &parsed_v8_args, &error); | |
| 272 if (try_catch.HasCaught()) { | |
| 273 try_catch.ReThrow(); | |
| 274 return RequestResult(RequestResult::THROWN); | |
| 275 } | |
| 276 if (!success) | |
| 277 return RequestResult(std::move(error)); | |
| 278 arguments->swap(parsed_v8_args); | |
| 279 } | |
| 280 | |
| 281 bool updated_args = false; | |
| 282 if (!post_validate_hook.IsEmpty()) { | |
| 283 updated_args = true; | |
| 284 UpdateArguments(post_validate_hook, context, arguments); | |
| 285 if (try_catch.HasCaught()) { | |
| 286 try_catch.ReThrow(); | |
| 287 return RequestResult(RequestResult::THROWN); | |
| 288 } | |
| 289 } | |
| 290 | |
| 291 if (handle_request.IsEmpty()) { | |
| 292 RequestResult::ResultCode result = updated_args | |
| 293 ? RequestResult::ARGUMENTS_UPDATED | |
| 294 : RequestResult::NOT_HANDLED; | |
| 295 return RequestResult(result, custom_callback); | |
| 296 } | |
| 297 | |
| 298 v8::Global<v8::Value> global_result = | |
| 299 run_js_.Run(handle_request, context, arguments->size(), | |
| 300 arguments->data()); | |
| 301 if (try_catch.HasCaught()) { | |
| 302 try_catch.ReThrow(); | |
| 303 return RequestResult(RequestResult::THROWN); | |
| 304 } | |
| 305 RequestResult result(RequestResult::HANDLED, custom_callback); | |
| 306 if (!global_result.IsEmpty()) | |
| 307 result.return_value = global_result.Get(isolate); | |
| 308 return result; | |
| 309 } | |
| 310 | |
| 311 v8::Local<v8::Object> APIBindingHooks::GetJSHookInterface( | |
| 312 v8::Local<v8::Context> context) { | |
| 313 return GetJSHookInterfaceObject(api_name_, context, true); | |
| 314 } | |
| 315 | |
| 316 v8::Local<v8::Function> APIBindingHooks::GetCustomJSCallback( | |
| 317 const std::string& name, | |
| 318 v8::Local<v8::Context> context) { | |
| 319 v8::Local<v8::Object> hooks = | |
| 320 GetJSHookInterfaceObject(api_name_, context, false); | |
| 321 if (hooks.IsEmpty()) | |
| 322 return v8::Local<v8::Function>(); | |
| 323 JSHookInterface* hook_interface = nullptr; | |
| 324 gin::Converter<JSHookInterface*>::FromV8(context->GetIsolate(), hooks, | |
| 325 &hook_interface); | |
| 326 CHECK(hook_interface); | |
| 327 | |
| 328 return hook_interface->GetCustomCallback(name, context->GetIsolate()); | |
| 329 } | |
| 330 | |
| 331 bool APIBindingHooks::CreateCustomEvent(v8::Local<v8::Context> context, | |
| 332 const std::string& event_name, | |
| 333 v8::Local<v8::Value>* event_out) { | |
| 334 return delegate_ && | |
| 335 delegate_->CreateCustomEvent(context, run_js_, event_name, event_out); | |
| 336 } | |
| 337 | |
| 338 void APIBindingHooks::InitializeTemplate( | |
| 339 v8::Isolate* isolate, | |
| 340 v8::Local<v8::ObjectTemplate> object_template, | |
| 341 const APITypeReferenceMap& type_refs) { | |
| 342 if (delegate_) | |
| 343 delegate_->InitializeTemplate(isolate, object_template, type_refs); | |
| 344 } | |
| 345 | |
| 346 void APIBindingHooks::SetDelegate( | |
| 347 std::unique_ptr<APIBindingHooksDelegate> delegate) { | |
| 348 delegate_ = std::move(delegate); | |
| 349 } | |
| 350 | |
| 351 bool APIBindingHooks::UpdateArguments( | |
| 352 v8::Local<v8::Function> function, | |
| 353 v8::Local<v8::Context> context, | |
| 354 std::vector<v8::Local<v8::Value>>* arguments) { | |
| 355 v8::Global<v8::Value> global_result; | |
| 356 { | |
| 357 v8::TryCatch try_catch(context->GetIsolate()); | |
| 358 global_result = run_js_.Run(function, context, | |
| 359 arguments->size(), arguments->data()); | |
| 360 if (try_catch.HasCaught()) { | |
| 361 try_catch.ReThrow(); | |
| 362 return false; | |
| 363 } | |
| 364 } | |
| 365 DCHECK(!global_result.IsEmpty()); | |
| 366 v8::Local<v8::Value> result = global_result.Get(context->GetIsolate()); | |
| 367 std::vector<v8::Local<v8::Value>> new_args; | |
| 368 if (result.IsEmpty() || | |
| 369 !gin::Converter<std::vector<v8::Local<v8::Value>>>::FromV8( | |
| 370 context->GetIsolate(), result, &new_args)) { | |
| 371 return false; | |
| 372 } | |
| 373 arguments->swap(new_args); | |
| 374 return true; | |
| 375 } | |
| 376 | |
| 377 } // namespace extensions | |
| OLD | NEW |