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

Unified 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 side-by-side diff with in-line comments
Download patch
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..081605aa0a6f2e009e3e17ac8ea98c74d02b891d
--- /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>();
+
+ if (bind_name)
lazyboy 2016/11/19 03:23:01 Is |bind_name| ever null?
Devlin 2016/11/21 19:29:41 Nope, removed.
+ *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

Powered by Google App Engine
This is Rietveld 408576698