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 {} | |
| 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(gin::Arguments* arguments) { | |
|
jbroman
2016/12/13 22:12:40
There's nothing unusual going on here, so you don'
Devlin
2016/12/14 02:02:13
Nifty, done.
| |
| 44 std::string method_name; | |
| 45 if (!arguments->GetNext(&method_name)) | |
| 46 return; | |
| 47 v8::Local<v8::Function> handler; | |
| 48 if (!arguments->GetNext(&handler)) | |
| 49 return; | |
| 50 | |
| 51 std::string qualified_method_name = | |
| 52 base::StringPrintf("%s.%s", api_name_.c_str(), method_name.c_str()); | |
| 53 v8::Global<v8::Function>& entry = js_hooks_[qualified_method_name]; | |
| 54 if (!entry.IsEmpty()) { | |
| 55 NOTREACHED() << "Hooks can only be set once."; | |
| 56 return; | |
| 57 } | |
| 58 js_hooks_[qualified_method_name] = | |
|
jbroman
2016/12/13 22:12:41
You already have a reference to this object a few
Devlin
2016/12/14 02:02:14
I was definitely going to do that, and apparently
| |
| 59 v8::Global<v8::Function>(arguments->isolate(), handler); | |
| 60 } | |
| 61 | |
| 62 std::string api_name_; | |
| 63 | |
| 64 JSHooks js_hooks_; | |
| 65 | |
| 66 DISALLOW_COPY_AND_ASSIGN(JSHookInterface); | |
| 67 }; | |
| 68 | |
| 69 const char kExtensionAPIHooksPerContextKey[] = "extension_api_hooks"; | |
| 70 | |
| 71 struct APIHooksPerContextData : public base::SupportsUserData::Data { | |
| 72 APIHooksPerContextData(v8::Isolate* isolate) : isolate(isolate) {} | |
| 73 ~APIHooksPerContextData() override { | |
| 74 v8::HandleScope scope(isolate); | |
| 75 for (const auto& pair : hook_interfaces) { | |
| 76 // We explicitly clear the hook data map here to remove all references to | |
| 77 // v8 objects in order to avoid cycles. | |
| 78 JSHookInterface* hooks = nullptr; | |
| 79 gin::Converter<JSHookInterface*>::FromV8( | |
| 80 isolate, pair.second.Get(isolate), &hooks); | |
| 81 CHECK(hooks); | |
| 82 hooks->js_hooks()->clear(); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 v8::Isolate* isolate = nullptr; | |
| 87 | |
| 88 std::map<std::string, v8::Global<v8::Object>> hook_interfaces; | |
| 89 }; | |
| 90 | |
| 91 gin::WrapperInfo JSHookInterface::kWrapperInfo = | |
| 92 {gin::kEmbedderNativeGin}; | |
| 93 | |
| 94 // Creates and returns JS object for the hook interface to allow for | |
| 95 // registering custom hooks from JS. | |
| 96 v8::Local<v8::Object> CreateJSHookInterface(const std::string& api_name, | |
| 97 v8::Local<v8::Context> context) { | |
| 98 gin::PerContextData* per_context_data = gin::PerContextData::From(context); | |
| 99 DCHECK(per_context_data); | |
| 100 APIHooksPerContextData* data = static_cast<APIHooksPerContextData*>( | |
| 101 per_context_data->GetUserData(kExtensionAPIHooksPerContextKey)); | |
| 102 if (!data) { | |
| 103 auto api_data = | |
| 104 base::MakeUnique<APIHooksPerContextData>(context->GetIsolate()); | |
| 105 data = api_data.get(); | |
| 106 per_context_data->SetUserData(kExtensionAPIHooksPerContextKey, | |
| 107 api_data.release()); | |
| 108 } | |
| 109 | |
| 110 DCHECK(data->hook_interfaces.find(api_name) == data->hook_interfaces.end()); | |
| 111 | |
| 112 gin::Handle<JSHookInterface> hooks = | |
| 113 gin::CreateHandle(context->GetIsolate(), new JSHookInterface(api_name)); | |
| 114 CHECK(!hooks.IsEmpty()); | |
| 115 v8::Local<v8::Value> hooks_value = hooks.ToV8(); | |
| 116 CHECK(hooks_value->IsObject()); | |
|
jbroman
2016/12/13 22:12:40
It'd be pretty bad for this to not be a v8::Object
Devlin
2016/12/14 02:02:13
Removed.
| |
| 117 v8::Local<v8::Object> hooks_object = | |
| 118 v8::Local<v8::Object>::Cast(hooks_value); | |
| 119 data->hook_interfaces[api_name] = | |
|
jbroman
2016/12/13 22:12:40
Up to your preference, but you can use Reset() her
Devlin
2016/12/14 02:02:14
Works for me, done.
| |
| 120 v8::Global<v8::Object>(context->GetIsolate(), hooks_object); | |
| 121 | |
| 122 return hooks_object; | |
| 123 } | |
| 124 | |
| 125 // A wrapper to run the provided function with the passed-in arguments. | |
| 126 void RunJsFunction(const binding::RunJSFunction& run_js, | |
| 127 v8::Local<v8::Function> function, | |
| 128 v8::Local<v8::Context> context, | |
| 129 const binding::APISignature* signature, | |
| 130 gin::Arguments* arguments) { | |
| 131 std::vector<v8::Local<v8::Value>> v8_args; | |
| 132 if (!arguments->GetRemaining(&v8_args)) | |
| 133 return; | |
| 134 run_js.Run(function, context, v8_args.size(), v8_args.data()); | |
|
jbroman
2016/12/13 22:12:40
Do these functions ever need the ability to throw
Devlin
2016/12/14 02:02:14
Yes, it doesn't currently. It will need to in the
| |
| 135 } | |
| 136 | |
| 137 } // namespace | |
| 138 | |
| 139 APIBindingHooks::APIBindingHooks(const binding::RunJSFunction& run_js) | |
| 140 : run_js_(run_js) {} | |
| 10 APIBindingHooks::~APIBindingHooks() {} | 141 APIBindingHooks::~APIBindingHooks() {} |
| 11 | 142 |
| 12 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name, | 143 void APIBindingHooks::RegisterHandleRequest(const std::string& method_name, |
| 13 const HandleRequestHook& hook) { | 144 const HandleRequestHook& hook) { |
| 14 request_hooks_[method_name] = hook; | 145 request_hooks_[method_name] = hook; |
| 15 } | 146 } |
| 16 | 147 |
| 148 void APIBindingHooks::RegisterJsSource(v8::Global<v8::String> source) { | |
| 149 js_hooks_source_ = std::move(source); | |
| 150 } | |
| 151 | |
| 17 APIBindingHooks::HandleRequestHook APIBindingHooks::GetHandleRequest( | 152 APIBindingHooks::HandleRequestHook APIBindingHooks::GetHandleRequest( |
| 18 const std::string& method_name) { | 153 const std::string& api_name, |
| 19 auto iter = request_hooks_.find(method_name); | 154 const std::string& method_name, |
| 20 if (iter != request_hooks_.end()) | 155 v8::Local<v8::Context> context) { |
| 21 return iter->second; | 156 // Easy case: a native custom hook. |
| 157 auto request_hooks_iter = request_hooks_.find(method_name); | |
| 158 if (request_hooks_iter != request_hooks_.end()) | |
| 159 return request_hooks_iter->second; | |
| 22 | 160 |
| 23 return HandleRequestHook(); | 161 // Harder case: looking up a custom hook registered on the context (since |
| 162 // these are JS, each context has a separate instance). | |
| 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 return HandleRequestHook(); | |
| 169 | |
| 170 auto hook_interface_iter = data->hook_interfaces.find(api_name); | |
| 171 if (hook_interface_iter == data->hook_interfaces.end()) | |
| 172 return HandleRequestHook(); | |
| 173 | |
| 174 JSHookInterface* hook_interface = nullptr; | |
| 175 gin::Converter<JSHookInterface*>::FromV8( | |
| 176 context->GetIsolate(), | |
| 177 hook_interface_iter->second.Get(context->GetIsolate()), &hook_interface); | |
| 178 CHECK(hook_interface); | |
| 179 | |
| 180 auto js_hook_iter = hook_interface->js_hooks()->find(method_name); | |
| 181 if (js_hook_iter == hook_interface->js_hooks()->end()) | |
| 182 return HandleRequestHook(); | |
| 183 | |
| 184 return base::Bind(&RunJsFunction, run_js_, | |
|
jbroman
2016/12/13 22:12:41
I don't see whether I commented on this previously
Devlin
2016/12/14 02:02:13
Much cleaner now; good idea!
| |
| 185 js_hook_iter->second.Get(context->GetIsolate()), | |
| 186 context); | |
| 187 } | |
| 188 | |
| 189 void APIBindingHooks::InitializeInContext( | |
| 190 v8::Local<v8::Context> context, | |
| 191 const std::string& api_name) { | |
| 192 if (js_hooks_source_.IsEmpty()) | |
| 193 return; | |
| 194 | |
| 195 v8::Local<v8::String> source = js_hooks_source_.Get(context->GetIsolate()); | |
| 196 v8::Local<v8::Script> script; | |
| 197 if (!v8::Script::Compile(context, source).ToLocal(&script)) | |
|
jbroman
2016/12/13 22:12:40
nit: Do you want to also pass in a ScriptOrigin wi
Devlin
2016/12/14 02:02:13
sg; done.
| |
| 198 return; | |
| 199 v8::Local<v8::Value> func_as_value = script->Run(); | |
| 200 v8::Local<v8::Function> function; | |
| 201 if (!gin::ConvertFromV8(context->GetIsolate(), func_as_value, &function)) | |
| 202 return; | |
| 203 v8::Local<v8::Object> api_hooks = GetJSHookInterface(api_name, context); | |
|
jbroman
2016/12/13 22:12:40
I can't find the definition of this function. Shou
Devlin
2016/12/14 02:02:13
Errm, yes. Done.
| |
| 204 v8::Local<v8::Value> args[] = {api_hooks}; | |
| 205 run_js_.Run(function, context, 1, args); | |
|
jbroman
2016/12/13 22:12:40
nit: I'd prefer arraysize(args) to 1, in case argu
Devlin
2016/12/14 02:02:14
Done.
| |
| 24 } | 206 } |
| 25 | 207 |
| 26 } // namespace extensions | 208 } // namespace extensions |
| OLD | NEW |