| 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" |
| 8 #include "base/strings/stringprintf.h" |
| 9 #include "base/supports_user_data.h" |
| 10 #include "gin/arguments.h" |
| 11 #include "gin/handle.h" |
| 12 #include "gin/object_template_builder.h" |
| 13 #include "gin/per_context_data.h" |
| 14 #include "gin/wrappable.h" |
| 15 |
| 7 namespace extensions { | 16 namespace extensions { |
| 8 | 17 |
| 9 APIBindingHooks::APIBindingHooks() {} | 18 namespace { |
| 19 |
| 20 // An interface to allow for registration of custom hooks from JavaScript. |
| 21 // Contains registered hooks for a single API. |
| 22 class JSHookInterface final : public gin::Wrappable<JSHookInterface> { |
| 23 public: |
| 24 using JSHooks = std::map<std::string, v8::Global<v8::Function>>; |
| 25 |
| 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 } |
| 37 |
| 38 JSHooks* js_hooks() { return &js_hooks_; } |
| 39 |
| 40 private: |
| 41 // Adds a custom hook. |
| 42 void SetHandleRequest(v8::Isolate* isolate, |
| 43 const std::string& method_name, |
| 44 v8::Local<v8::Function> handler) { |
| 45 std::string qualified_method_name = |
| 46 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str()); |
| 47 v8::Global<v8::Function>& entry = js_hooks_[qualified_method_name]; |
| 48 if (!entry.IsEmpty()) { |
| 49 NOTREACHED() << "Hooks can only be set once."; |
| 50 return; |
| 51 } |
| 52 entry.Reset(isolate, handler); |
| 53 } |
| 54 |
| 55 std::string api_name_; |
| 56 |
| 57 JSHooks js_hooks_; |
| 58 |
| 59 DISALLOW_COPY_AND_ASSIGN(JSHookInterface); |
| 60 }; |
| 61 |
| 62 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks"; |
| 63 |
| 64 struct APIHooksPerContextData : public base::SupportsUserData::Data { |
| 65 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {} |
| 66 ~APIHooksPerContextData() override { |
| 67 v8::HandleScope scope(isolate); |
| 68 for (const auto& pair : hook_interfaces) { |
| 69 // We explicitly clear the hook data map here to remove all references to |
| 70 // v8 objects in order to avoid cycles. |
| 71 JSHookInterface* hooks = nullptr; |
| 72 gin::Converter<JSHookInterface*>::FromV8( |
| 73 isolate, pair.second.Get(isolate), &hooks); |
| 74 CHECK(hooks); |
| 75 hooks->js_hooks()->clear(); |
| 76 } |
| 77 } |
| 78 |
| 79 v8::Isolate* isolate; |
| 80 |
| 81 std::map<std::string, v8::Global<v8::Object>> hook_interfaces; |
| 82 }; |
| 83 |
| 84 gin::WrapperInfo JSHookInterface::kWrapperInfo = |
| 85 {gin::kEmbedderNativeGin}; |
| 86 |
| 87 // Creates and returns JS object for the hook interface to allow for |
| 88 // registering custom hooks from JS. |
| 89 v8::Local<v8::Object> CreateJSHookInterface(const std::string& api_name, |
| 90 v8::Local<v8::Context> context) { |
| 91 gin::PerContextData* per_context_data = gin::PerContextData::From(context); |
| 92 DCHECK(per_context_data); |
| 93 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>( |
| 94 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey)); |
| 95 if (!data) { |
| 96 auto api_data = |
| 97 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate()); |
| 98 data = api_data.get(); |
| 99 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey, |
| 100 api_data.release()); |
| 101 } |
| 102 |
| 103 DCHECK(data->hook_interfaces.find(api_name) == data->hook_interfaces.end()); |
| 104 |
| 105 gin::Handle<JSHookInterface> hooks = |
| 106 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name)); |
| 107 CHECK(!hooks.IsEmpty()); |
| 108 v8::Local<v8::Object> hooks_object = hooks.ToV8().As<v8::Object>(); |
| 109 data->hook_interfaces[api_name].Reset(context->GetIsolate(), hooks_object); |
| 110 |
| 111 return hooks_object; |
| 112 } |
| 113 |
| 114 } // namespace |
| 115 |
| 116 APIBindingHooks::APIBindingHooks(const binding::RunJSFunction& run_js) |
| 117 : run_js_(run_js) {} |
| 10 APIBindingHooks::~APIBindingHooks() {} | 118 APIBindingHooks::~APIBindingHooks() {} |
| 11 | 119 |
| 12 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name, | 120 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name, |
| 13 const HandleRequestHook& hook) { | 121 const HandleRequestHook& hook) { |
| 14 DCHECK(!hooks_used_) << "Hooks must be registered before the first use!"; | 122 DCHECK(!hooks_used_) << "Hooks must be registered before the first use!"; |
| 15 request_hooks_[method_name] = hook; | 123 request_hooks_[method_name] = hook; |
| 16 } | 124 } |
| 17 | 125 |
| 18 APIBindingHooks::HandleRequestHook APIBindingHooks::GetHandleRequest( | 126 void APIBindingHooks::RegisterJsSource(v8::Global<v8::String> source, |
| 19 const std::string& method_name) { | 127 v8::Global<v8::String> resource_name) { |
| 20 hooks_used_ = true; | 128 js_hooks_source_ = std::move(source); |
| 21 auto iter = request_hooks_.find(method_name); | 129 js_resource_name_ = std::move(resource_name); |
| 22 if (iter != request_hooks_.end()) | 130 } |
| 23 return iter->second; | |
| 24 | 131 |
| 25 return HandleRequestHook(); | 132 bool APIBindingHooks::HandleRequest( |
| 133 const std::string& api_name, |
| 134 const std::string& method_name, |
| 135 v8::Local<v8::Context> context, |
| 136 const binding::APISignature* signature, |
| 137 gin::Arguments* arguments) { |
| 138 // Easy case: a native custom hook. |
| 139 auto request_hooks_iter = request_hooks_.find(method_name); |
| 140 if (request_hooks_iter != request_hooks_.end()) { |
| 141 request_hooks_iter->second.Run(signature, arguments); |
| 142 return true; |
| 143 } |
| 144 |
| 145 // Harder case: looking up a custom hook registered on the context (since |
| 146 // these are JS, each context has a separate instance). |
| 147 gin::PerContextData* per_context_data = gin::PerContextData::From(context); |
| 148 DCHECK(per_context_data); |
| 149 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>( |
| 150 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey)); |
| 151 if (!data) |
| 152 return false; |
| 153 |
| 154 auto hook_interface_iter = data->hook_interfaces.find(api_name); |
| 155 if (hook_interface_iter == data->hook_interfaces.end()) |
| 156 return false; |
| 157 |
| 158 JSHookInterface* hook_interface = nullptr; |
| 159 gin::Converter<JSHookInterface*>::FromV8( |
| 160 context->GetIsolate(), |
| 161 hook_interface_iter->second.Get(context->GetIsolate()), &hook_interface); |
| 162 CHECK(hook_interface); |
| 163 |
| 164 auto js_hook_iter = hook_interface->js_hooks()->find(method_name); |
| 165 if (js_hook_iter == hook_interface->js_hooks()->end()) |
| 166 return false; |
| 167 |
| 168 // Found a JS handler. |
| 169 std::vector<v8::Local<v8::Value>> v8_args; |
| 170 // TODO(devlin): Right now, this doesn't support exceptions or return values, |
| 171 // which we will need to at some point. |
| 172 if (arguments->GetRemaining(&v8_args)) { |
| 173 v8::Local<v8::Function> handler = |
| 174 js_hook_iter->second.Get(context->GetIsolate()); |
| 175 run_js_.Run(handler, context, v8_args.size(), v8_args.data()); |
| 176 } |
| 177 |
| 178 return true; |
| 179 } |
| 180 |
| 181 void APIBindingHooks::InitializeInContext( |
| 182 v8::Local<v8::Context> context, |
| 183 const std::string& api_name) { |
| 184 if (js_hooks_source_.IsEmpty()) |
| 185 return; |
| 186 |
| 187 v8::Local<v8::String> source = js_hooks_source_.Get(context->GetIsolate()); |
| 188 v8::Local<v8::String> resource_name = |
| 189 js_resource_name_.Get(context->GetIsolate()); |
| 190 v8::Local<v8::Script> script; |
| 191 v8::ScriptOrigin origin(resource_name); |
| 192 if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) |
| 193 return; |
| 194 v8::Local<v8::Value> func_as_value = script->Run(); |
| 195 v8::Local<v8::Function> function; |
| 196 if (!gin::ConvertFromV8(context->GetIsolate(), func_as_value, &function)) |
| 197 return; |
| 198 v8::Local<v8::Object> api_hooks = CreateJSHookInterface(api_name, context); |
| 199 v8::Local<v8::Value> args[] = {api_hooks}; |
| 200 run_js_.Run(function, context, arraysize(args), args); |
| 26 } | 201 } |
| 27 | 202 |
| 28 } // namespace extensions | 203 } // namespace extensions |
| OLD | NEW |