| Index: extensions/renderer/js_extension_bindings_system.cc
|
| diff --git a/extensions/renderer/js_extension_bindings_system.cc b/extensions/renderer/js_extension_bindings_system.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f9d38b6575786bf76f2053c9c848098e7fbb9d1d
|
| --- /dev/null
|
| +++ b/extensions/renderer/js_extension_bindings_system.cc
|
| @@ -0,0 +1,311 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "extensions/renderer/js_extension_bindings_system.h"
|
| +
|
| +#include "base/command_line.h"
|
| +#include "base/strings/string_split.h"
|
| +#include "content/public/child/v8_value_converter.h"
|
| +#include "content/public/common/content_switches.h"
|
| +#include "extensions/common/extension.h"
|
| +#include "extensions/common/extension_urls.h"
|
| +#include "extensions/common/extensions_client.h"
|
| +#include "extensions/common/features/feature.h"
|
| +#include "extensions/common/features/feature_provider.h"
|
| +#include "extensions/common/manifest_constants.h"
|
| +#include "extensions/common/manifest_handlers/externally_connectable.h"
|
| +#include "extensions/renderer/binding_generating_native_handler.h"
|
| +#include "extensions/renderer/renderer_extension_registry.h"
|
| +#include "extensions/renderer/resource_bundle_source_map.h"
|
| +#include "extensions/renderer/script_context.h"
|
| +#include "gin/converter.h"
|
| +#include "v8/include/v8.h"
|
| +
|
| +namespace extensions {
|
| +
|
| +namespace {
|
| +
|
| +static const char kEventDispatchFunction[] = "dispatchEvent";
|
| +
|
| +// Gets |field| from |object| or creates it as an empty object if it doesn't
|
| +// exist.
|
| +v8::Local<v8::Object> GetOrCreateObject(const v8::Local<v8::Object>& object,
|
| + const std::string& field,
|
| + v8::Isolate* isolate) {
|
| + v8::Local<v8::String> key = v8::String::NewFromUtf8(isolate, field.c_str());
|
| + // If the object has a callback property, it is assumed it is an unavailable
|
| + // API, so it is safe to delete. This is checked before GetOrCreateObject is
|
| + // called.
|
| + if (object->HasRealNamedCallbackProperty(key)) {
|
| + object->Delete(key);
|
| + } else if (object->HasRealNamedProperty(key)) {
|
| + v8::Local<v8::Value> value = object->Get(key);
|
| + CHECK(value->IsObject());
|
| + return v8::Local<v8::Object>::Cast(value);
|
| + }
|
| +
|
| + v8::Local<v8::Object> new_object = v8::Object::New(isolate);
|
| + object->Set(key, new_object);
|
| + return new_object;
|
| +}
|
| +
|
| +// Returns the global value for "chrome" from |context|. If one doesn't exist
|
| +// creates a new object for it. If a chrome property exists on the window
|
| +// already (as in the case when a script did `window.chrome = true`), returns
|
| +// an empty object.
|
| +v8::Local<v8::Object> GetOrCreateChrome(ScriptContext* context) {
|
| + v8::Local<v8::String> chrome_string(
|
| + v8::String::NewFromUtf8(context->isolate(), "chrome"));
|
| + v8::Local<v8::Object> global(context->v8_context()->Global());
|
| + v8::Local<v8::Value> chrome(global->Get(chrome_string));
|
| + if (chrome->IsUndefined()) {
|
| + chrome = v8::Object::New(context->isolate());
|
| + global->Set(chrome_string, chrome);
|
| + }
|
| + return chrome->IsObject() ? chrome.As<v8::Object>() : v8::Local<v8::Object>();
|
| +}
|
| +
|
| +v8::Local<v8::Object> GetOrCreateBindObjectIfAvailable(
|
| + const std::string& api_name,
|
| + std::string* bind_name,
|
| + ScriptContext* context) {
|
| + std::vector<std::string> split = base::SplitString(
|
| + api_name, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
| +
|
| + v8::Local<v8::Object> bind_object;
|
| +
|
| + // Check if this API has an ancestor. If the API's ancestor is available and
|
| + // the API is not available, don't install the bindings for this API. If
|
| + // the API is available and its ancestor is not, delete the ancestor and
|
| + // install the bindings for the API. This is to prevent loading the ancestor
|
| + // API schema if it will not be needed.
|
| + //
|
| + // For example:
|
| + // If app is available and app.window is not, just install app.
|
| + // If app.window is available and app is not, delete app and install
|
| + // app.window on a new object so app does not have to be loaded.
|
| + const FeatureProvider* api_feature_provider =
|
| + FeatureProvider::GetAPIFeatures();
|
| + std::string ancestor_name;
|
| + bool only_ancestor_available = false;
|
| +
|
| + for (size_t i = 0; i < split.size() - 1; ++i) {
|
| + ancestor_name += (i ? "." : "") + split[i];
|
| + if (api_feature_provider->GetFeature(ancestor_name) &&
|
| + context->GetAvailability(ancestor_name).is_available() &&
|
| + !context->GetAvailability(api_name).is_available()) {
|
| + only_ancestor_available = true;
|
| + break;
|
| + }
|
| +
|
| + if (bind_object.IsEmpty()) {
|
| + bind_object = GetOrCreateChrome(context);
|
| + if (bind_object.IsEmpty())
|
| + return v8::Local<v8::Object>();
|
| + }
|
| + bind_object = GetOrCreateObject(bind_object, split[i], context->isolate());
|
| + }
|
| +
|
| + if (only_ancestor_available)
|
| + return v8::Local<v8::Object>();
|
| +
|
| + DCHECK(bind_name);
|
| + *bind_name = split.back();
|
| +
|
| + return bind_object.IsEmpty() ? GetOrCreateChrome(context) : bind_object;
|
| +}
|
| +
|
| +// Determines if a ScriptContext can connect to any externally_connectable-
|
| +// enabled extension.
|
| +bool IsRuntimeAvailableToContext(ScriptContext* context) {
|
| + for (const auto& extension :
|
| + *RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
|
| + ExternallyConnectableInfo* info = static_cast<ExternallyConnectableInfo*>(
|
| + extension->GetManifestData(manifest_keys::kExternallyConnectable));
|
| + if (info && info->matches.MatchesURL(context->url()))
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +// Creates the event bindings if necessary for the given |context|.
|
| +void MaybeCreateEventBindings(ScriptContext* context) {
|
| + // chrome.Event is part of the public API (although undocumented). Make it
|
| + // lazily evalulate to Event from event_bindings.js. For extensions only
|
| + // though, not all webpages!
|
| + if (!context->extension())
|
| + return;
|
| + v8::Local<v8::Object> chrome = GetOrCreateChrome(context);
|
| + if (chrome.IsEmpty())
|
| + return;
|
| + context->module_system()->SetLazyField(chrome, "Event", kEventBindings,
|
| + "Event");
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +JsExtensionBindingsSystem::JsExtensionBindingsSystem(
|
| + ResourceBundleSourceMap* source_map,
|
| + std::unique_ptr<RequestSender> request_sender)
|
| + : source_map_(source_map), request_sender_(std::move(request_sender)) {}
|
| +
|
| +JsExtensionBindingsSystem::~JsExtensionBindingsSystem() {}
|
| +
|
| +void JsExtensionBindingsSystem::DidCreateScriptContext(ScriptContext* context) {
|
| + MaybeCreateEventBindings(context);
|
| +}
|
| +
|
| +void JsExtensionBindingsSystem::WillReleaseScriptContext(
|
| + ScriptContext* context) {
|
| + // TODO(kalman): Make |request_sender| use |context->AddInvalidationObserver|.
|
| + // In fact |request_sender_| should really be owned by ScriptContext.
|
| + request_sender_->InvalidateSource(context);
|
| +}
|
| +
|
| +void JsExtensionBindingsSystem::UpdateBindingsForContext(
|
| + ScriptContext* context) {
|
| + v8::HandleScope handle_scope(context->isolate());
|
| + v8::Context::Scope context_scope(context->v8_context());
|
| +
|
| + // TODO(kalman): Make the bindings registration have zero overhead then run
|
| + // the same code regardless of context type.
|
| + switch (context->context_type()) {
|
| + case Feature::UNSPECIFIED_CONTEXT:
|
| + case Feature::WEB_PAGE_CONTEXT:
|
| + case Feature::BLESSED_WEB_PAGE_CONTEXT:
|
| + // Hard-code registration of any APIs that are exposed to webpage-like
|
| + // contexts, because it's too expensive to run the full bindings code.
|
| + // All of the same permission checks will still apply.
|
| + if (context->GetAvailability("app").is_available())
|
| + RegisterBinding("app", context);
|
| + if (context->GetAvailability("webstore").is_available())
|
| + RegisterBinding("webstore", context);
|
| + if (context->GetAvailability("dashboardPrivate").is_available())
|
| + RegisterBinding("dashboardPrivate", context);
|
| + if (IsRuntimeAvailableToContext(context))
|
| + RegisterBinding("runtime", context);
|
| + break;
|
| +
|
| + case Feature::SERVICE_WORKER_CONTEXT:
|
| + DCHECK(ExtensionsClient::Get()
|
| + ->ExtensionAPIEnabledInExtensionServiceWorkers());
|
| + // Intentional fallthrough.
|
| + case Feature::BLESSED_EXTENSION_CONTEXT:
|
| + case Feature::UNBLESSED_EXTENSION_CONTEXT:
|
| + case Feature::CONTENT_SCRIPT_CONTEXT:
|
| + case Feature::WEBUI_CONTEXT: {
|
| + // Extension context; iterate through all the APIs and bind the available
|
| + // ones.
|
| + const FeatureProvider* api_feature_provider =
|
| + FeatureProvider::GetAPIFeatures();
|
| + for (const auto& map_entry : api_feature_provider->GetAllFeatures()) {
|
| + // Internal APIs are included via require(api_name) from internal code
|
| + // rather than chrome[api_name].
|
| + if (map_entry.second->IsInternal())
|
| + continue;
|
| +
|
| + // If this API has a parent feature (and isn't marked 'noparent'),
|
| + // then this must be a function or event, so we should not register.
|
| + if (api_feature_provider->GetParent(map_entry.second.get()) != nullptr)
|
| + continue;
|
| +
|
| + // Skip chrome.test if this isn't a test.
|
| + if (map_entry.first == "test" &&
|
| + !base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + ::switches::kTestType)) {
|
| + continue;
|
| + }
|
| +
|
| + if (context->IsAnyFeatureAvailableToContext(*map_entry.second)) {
|
| + // TODO(lazyboy): RegisterBinding() uses |source_map_|, any thread
|
| + // safety issue?
|
| + RegisterBinding(map_entry.first, context);
|
| + }
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void JsExtensionBindingsSystem::HandleResponse(int request_id,
|
| + bool success,
|
| + const base::ListValue& response,
|
| + const std::string& error) {
|
| + request_sender_->HandleResponse(request_id, success, response, error);
|
| +}
|
| +
|
| +RequestSender* JsExtensionBindingsSystem::GetRequestSender() {
|
| + return request_sender_.get();
|
| +}
|
| +
|
| +void JsExtensionBindingsSystem::DispatchEventInContext(
|
| + const std::string& event_name,
|
| + const base::ListValue* event_args,
|
| + const base::DictionaryValue* filtering_info,
|
| + ScriptContext* context) {
|
| + v8::HandleScope handle_scope(context->isolate());
|
| + v8::Context::Scope context_scope(context->v8_context());
|
| +
|
| + std::vector<v8::Local<v8::Value>> arguments;
|
| + arguments.push_back(gin::StringToSymbol(context->isolate(), event_name));
|
| +
|
| + {
|
| + std::unique_ptr<content::V8ValueConverter> converter(
|
| + content::V8ValueConverter::create());
|
| + arguments.push_back(
|
| + converter->ToV8Value(event_args, context->v8_context()));
|
| + if (!filtering_info->empty()) {
|
| + arguments.push_back(
|
| + converter->ToV8Value(filtering_info, context->v8_context()));
|
| + }
|
| + }
|
| +
|
| + context->module_system()->CallModuleMethodSafe(
|
| + kEventBindings, kEventDispatchFunction, arguments.size(),
|
| + arguments.data());
|
| +}
|
| +
|
| +void JsExtensionBindingsSystem::RegisterBinding(const std::string& api_name,
|
| + ScriptContext* context) {
|
| + std::string bind_name;
|
| + v8::Local<v8::Object> bind_object =
|
| + GetOrCreateBindObjectIfAvailable(api_name, &bind_name, context);
|
| +
|
| + // Empty if the bind object failed to be created, probably because the
|
| + // extension overrode chrome with a non-object, e.g. window.chrome = true.
|
| + if (bind_object.IsEmpty())
|
| + return;
|
| +
|
| + v8::Local<v8::String> v8_bind_name =
|
| + v8::String::NewFromUtf8(context->isolate(), bind_name.c_str());
|
| + if (bind_object->HasRealNamedProperty(v8_bind_name)) {
|
| + // The bind object may already have the property if the API has been
|
| + // registered before (or if the extension has put something there already,
|
| + // but, whatevs).
|
| + //
|
| + // In the former case, we need to re-register the bindings for the APIs
|
| + // which the extension now has permissions for (if any), but not touch any
|
| + // others so that we don't destroy state such as event listeners.
|
| + //
|
| + // TODO(kalman): Only register available APIs to make this all moot.
|
| + if (bind_object->HasRealNamedCallbackProperty(v8_bind_name))
|
| + return; // lazy binding still there, nothing to do
|
| + if (bind_object->Get(v8_bind_name)->IsObject())
|
| + return; // binding has already been fully installed
|
| + }
|
| +
|
| + ModuleSystem* module_system = context->module_system();
|
| + if (!source_map_->Contains(api_name)) {
|
| + module_system->RegisterNativeHandler(
|
| + api_name,
|
| + std::unique_ptr<NativeHandler>(
|
| + new BindingGeneratingNativeHandler(context, api_name, "binding")));
|
| + module_system->SetNativeLazyField(bind_object, bind_name, api_name,
|
| + "binding");
|
| + } else {
|
| + module_system->SetLazyField(bind_object, bind_name, api_name, "binding");
|
| + }
|
| +}
|
| +
|
| +} // namespace extensions
|
|
|