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

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

Issue 2512233002: [Extensions Bindings] Add ExtensionBindingsSystem interface; hook it up (Closed)
Patch Set: . Created 4 years, 1 month 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
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "extensions/renderer/js_extension_bindings_system.h"
6
7 #include "base/command_line.h"
8 #include "base/strings/string_split.h"
9 #include "content/public/child/v8_value_converter.h"
10 #include "content/public/common/content_switches.h"
11 #include "extensions/common/extension.h"
12 #include "extensions/common/extension_urls.h"
13 #include "extensions/common/extensions_client.h"
14 #include "extensions/common/features/feature.h"
15 #include "extensions/common/features/feature_provider.h"
16 #include "extensions/common/manifest_constants.h"
17 #include "extensions/common/manifest_handlers/externally_connectable.h"
18 #include "extensions/renderer/binding_generating_native_handler.h"
19 #include "extensions/renderer/renderer_extension_registry.h"
20 #include "extensions/renderer/resource_bundle_source_map.h"
21 #include "extensions/renderer/script_context.h"
22 #include "gin/converter.h"
23 #include "v8/include/v8.h"
24
25 namespace extensions {
26
27 namespace {
28
29 static const char kEventDispatchFunction[] = "dispatchEvent";
30
31 // Gets |field| from |object| or creates it as an empty object if it doesn't
32 // exist.
33 v8::Local<v8::Object> GetOrCreateObject(const v8::Local<v8::Object>& object,
34 const std::string& field,
35 v8::Isolate* isolate) {
36 v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, field.c_str());
37 // If the object has a callback property, it is assumed it is an unavailable
38 // API, so it is safe to delete. This is checked before GetOrCreateObject is
39 // called.
40 if (object->HasRealNamedCallbackProperty(key)) {
41 object->Delete(key);
42 } else if (object->HasRealNamedProperty(key)) {
43 v8::Local<v8::Value> value = object->Get(key);
44 CHECK(value->IsObject());
45 return v8::Local<v8::Object>::Cast(value);
46 }
47
48 v8::Local<v8::Object> new_object = v8::Object::New(isolate);
49 object->Set(key, new_object);
50 return new_object;
51 }
52
53 // Returns the global value for "chrome" from |context|. If one doesn't exist
54 // creates a new object for it. If a chrome property exists on the window
55 // already (as in the case when a script did `window.chrome = true`), returns
56 // an empty object.
57 v8::Local<v8::Object> GetOrCreateChrome(ScriptContext* context) {
58 v8::Local<v8::String> chrome_string(
59 v8::String::NewFromUtf8(context->isolate(), "chrome"));
60 v8::Local<v8::Object> global(context->v8_context()->Global());
61 v8::Local<v8::Value> chrome(global->Get(chrome_string));
62 if (chrome->IsUndefined()) {
63 chrome = v8::Object::New(context->isolate());
64 global->Set(chrome_string, chrome);
65 }
66 return chrome->IsObject() ? chrome.As<v8::Object>() : v8::Local<v8::Object>();
67 }
68
69 v8::Local<v8::Object> GetOrCreateBindObjectIfAvailable(
70 const std::string& api_name,
71 std::string* bind_name,
72 ScriptContext* context) {
73 std::vector<std::string> split = base::SplitString(
74 api_name, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
75
76 v8::Local<v8::Object> bind_object;
77
78 // Check if this API has an ancestor. If the API's ancestor is available and
79 // the API is not available, don't install the bindings for this API. If
80 // the API is available and its ancestor is not, delete the ancestor and
81 // install the bindings for the API. This is to prevent loading the ancestor
82 // API schema if it will not be needed.
83 //
84 // For example:
85 // If app is available and app.window is not, just install app.
86 // If app.window is available and app is not, delete app and install
87 // app.window on a new object so app does not have to be loaded.
88 const FeatureProvider* api_feature_provider =
89 FeatureProvider::GetAPIFeatures();
90 std::string ancestor_name;
91 bool only_ancestor_available = false;
92
93 for (size_t i = 0; i < split.size() - 1; ++i) {
94 ancestor_name += (i ? "." : "") + split[i];
95 if (api_feature_provider->GetFeature(ancestor_name) &&
96 context->GetAvailability(ancestor_name).is_available() &&
97 !context->GetAvailability(api_name).is_available()) {
98 only_ancestor_available = true;
99 break;
100 }
101
102 if (bind_object.IsEmpty()) {
103 bind_object = GetOrCreateChrome(context);
104 if (bind_object.IsEmpty())
105 return v8::Local<v8::Object>();
106 }
107 bind_object = GetOrCreateObject(bind_object, split[i], context->isolate());
108 }
109
110 if (only_ancestor_available)
111 return v8::Local<v8::Object>();
112
113 if (bind_name)
lazyboy 2016/11/19 03:23:01 Is |bind_name| ever null?
Devlin 2016/11/21 19:29:41 Nope, removed.
114 *bind_name = split.back();
115
116 return bind_object.IsEmpty() ? GetOrCreateChrome(context) : bind_object;
117 }
118
119 // Determines if a ScriptContext can connect to any externally_connectable-
120 // enabled extension.
121 bool IsRuntimeAvailableToContext(ScriptContext* context) {
122 for (const auto& extension :
123 *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
124 ExternallyConnectableInfo* info = static_cast<ExternallyConnectableInfo*>(
125 extension->GetManifestData(manifest_keys::kExternallyConnectable));
126 if (info && info->matches.MatchesURL(context->url()))
127 return true;
128 }
129 return false;
130 }
131
132 // Creates the event bindings if necessary for the given |context|.
133 void MaybeCreateEventBindings(ScriptContext* context) {
134 // chrome.Event is part of the public API (although undocumented). Make it
135 // lazily evalulate to Event from event_bindings.js. For extensions only
136 // though, not all webpages!
137 if (!context->extension())
138 return;
139 v8::Local<v8::Object> chrome = GetOrCreateChrome(context);
140 if (chrome.IsEmpty())
141 return;
142 context->module_system()->SetLazyField(chrome, "Event", kEventBindings,
143 "Event");
144 }
145
146 } // namespace
147
148 JsExtensionBindingsSystem::JsExtensionBindingsSystem(
149 ResourceBundleSourceMap* source_map,
150 std::unique_ptr<RequestSender> request_sender)
151 : source_map_(source_map), request_sender_(std::move(request_sender)) {}
152
153 JsExtensionBindingsSystem::~JsExtensionBindingsSystem() {}
154
155 void JsExtensionBindingsSystem::DidCreateScriptContext(ScriptContext* context) {
156 MaybeCreateEventBindings(context);
157 }
158
159 void JsExtensionBindingsSystem::WillReleaseScriptContext(
160 ScriptContext* context) {
161 // TODO(kalman): Make |request_sender| use |context->AddInvalidationObserver|.
162 // In fact |request_sender_| should really be owned by ScriptContext.
163 request_sender_->InvalidateSource(context);
164 }
165
166 void JsExtensionBindingsSystem::UpdateBindingsForContext(
167 ScriptContext* context) {
168 v8::HandleScope handle_scope(context->isolate());
169 v8::Context::Scope context_scope(context->v8_context());
170
171 // TODO(kalman): Make the bindings registration have zero overhead then run
172 // the same code regardless of context type.
173 switch (context->context_type()) {
174 case Feature::UNSPECIFIED_CONTEXT:
175 case Feature::WEB_PAGE_CONTEXT:
176 case Feature::BLESSED_WEB_PAGE_CONTEXT:
177 // Hard-code registration of any APIs that are exposed to webpage-like
178 // contexts, because it's too expensive to run the full bindings code.
179 // All of the same permission checks will still apply.
180 if (context->GetAvailability("app").is_available())
181 RegisterBinding("app", context);
182 if (context->GetAvailability("webstore").is_available())
183 RegisterBinding("webstore", context);
184 if (context->GetAvailability("dashboardPrivate").is_available())
185 RegisterBinding("dashboardPrivate", context);
186 if (IsRuntimeAvailableToContext(context))
187 RegisterBinding("runtime", context);
188 break;
189
190 case Feature::SERVICE_WORKER_CONTEXT:
191 DCHECK(ExtensionsClient::Get()
192 ->ExtensionAPIEnabledInExtensionServiceWorkers());
193 // Intentional fallthrough.
194 case Feature::BLESSED_EXTENSION_CONTEXT:
195 case Feature::UNBLESSED_EXTENSION_CONTEXT:
196 case Feature::CONTENT_SCRIPT_CONTEXT:
197 case Feature::WEBUI_CONTEXT: {
198 // Extension context; iterate through all the APIs and bind the available
199 // ones.
200 const FeatureProvider* api_feature_provider =
201 FeatureProvider::GetAPIFeatures();
202 for (const auto& map_entry : api_feature_provider->GetAllFeatures()) {
203 // Internal APIs are included via require(api_name) from internal code
204 // rather than chrome[api_name].
205 if (map_entry.second->IsInternal())
206 continue;
207
208 // If this API has a parent feature (and isn't marked 'noparent'),
209 // then this must be a function or event, so we should not register.
210 if (api_feature_provider->GetParent(map_entry.second.get()) != nullptr)
211 continue;
212
213 // Skip chrome.test if this isn't a test.
214 if (map_entry.first == "test" &&
215 !base::CommandLine::ForCurrentProcess()->HasSwitch(
216 ::switches::kTestType)) {
217 continue;
218 }
219
220 if (context->IsAnyFeatureAvailableToContext(*map_entry.second)) {
221 // TODO(lazyboy): RegisterBinding() uses |source_map_|, any thread
222 // safety issue?
223 RegisterBinding(map_entry.first, context);
224 }
225 }
226 break;
227 }
228 }
229 }
230
231 void JsExtensionBindingsSystem::HandleResponse(int request_id,
232 bool success,
233 const base::ListValue& response,
234 const std::string& error) {
235 request_sender_->HandleResponse(request_id, success, response, error);
236 }
237
238 RequestSender* JsExtensionBindingsSystem::GetRequestSender() {
239 return request_sender_.get();
240 }
241
242 void JsExtensionBindingsSystem::DispatchEventInContext(
243 const std::string& event_name,
244 const base::ListValue* event_args,
245 const base::DictionaryValue* filtering_info,
246 ScriptContext* context) {
247 v8::HandleScope handle_scope(context->isolate());
248 v8::Context::Scope context_scope(context->v8_context());
249
250 std::vector<v8::Local<v8::Value>> arguments;
251 arguments.push_back(gin::StringToSymbol(context->isolate(), event_name));
252
253 {
254 std::unique_ptr<content::V8ValueConverter> converter(
255 content::V8ValueConverter::create());
256 arguments.push_back(
257 converter->ToV8Value(event_args, context->v8_context()));
258 if (!filtering_info->empty()) {
259 arguments.push_back(
260 converter->ToV8Value(filtering_info, context->v8_context()));
261 }
262 }
263
264 context->module_system()->CallModuleMethodSafe(
265 kEventBindings, kEventDispatchFunction, arguments.size(),
266 arguments.data());
267 }
268
269 void JsExtensionBindingsSystem::RegisterBinding(const std::string& api_name,
270 ScriptContext* context) {
271 std::string bind_name;
272 v8::Local<v8::Object> bind_object =
273 GetOrCreateBindObjectIfAvailable(api_name, &bind_name, context);
274
275 // Empty if the bind object failed to be created, probably because the
276 // extension overrode chrome with a non-object, e.g. window.chrome = true.
277 if (bind_object.IsEmpty())
278 return;
279
280 v8::Local<v8::String> v8_bind_name =
281 v8::String::NewFromUtf8(context->isolate(), bind_name.c_str());
282 if (bind_object->HasRealNamedProperty(v8_bind_name)) {
283 // The bind object may already have the property if the API has been
284 // registered before (or if the extension has put something there already,
285 // but, whatevs).
286 //
287 // In the former case, we need to re-register the bindings for the APIs
288 // which the extension now has permissions for (if any), but not touch any
289 // others so that we don't destroy state such as event listeners.
290 //
291 // TODO(kalman): Only register available APIs to make this all moot.
292 if (bind_object->HasRealNamedCallbackProperty(v8_bind_name))
293 return; // lazy binding still there, nothing to do
294 if (bind_object->Get(v8_bind_name)->IsObject())
295 return; // binding has already been fully installed
296 }
297
298 ModuleSystem* module_system = context->module_system();
299 if (!source_map_->Contains(api_name)) {
300 module_system->RegisterNativeHandler(
301 api_name,
302 std::unique_ptr<NativeHandler>(
303 new BindingGeneratingNativeHandler(context, api_name, "binding")));
304 module_system->SetNativeLazyField(bind_object, bind_name, api_name,
305 "binding");
306 } else {
307 module_system->SetLazyField(bind_object, bind_name, api_name, "binding");
308 }
309 }
310
311 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698