Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Side by Side Diff: extensions/renderer/api_binding_hooks.cc

Issue 2563093002: [Extension Bindings] Add JS custom hook support (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « extensions/renderer/api_binding_hooks.h ('k') | extensions/renderer/api_binding_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « extensions/renderer/api_binding_hooks.h ('k') | extensions/renderer/api_binding_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698