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