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

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

Issue 2563093002: [Extension Bindings] Add JS custom hook support (Closed)
Patch Set: nits 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
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
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