| 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 DCHECK(bind_name); |
| 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 |