Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 | |
| OLD | NEW |