summaryrefslogtreecommitdiffstats
path: root/extensions/renderer/messaging_bindings.cc
diff options
context:
space:
mode:
authorrockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-01 17:29:13 +0000
committerrockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-05-01 17:29:13 +0000
commite6893672ec403d9dbf7fc5aab64ba6109ffd625a (patch)
tree25d46598e07af97d4f284663a3e3d53412e4eb2b /extensions/renderer/messaging_bindings.cc
parent3dfb202d7b2cbdb92b1fe9d6274211d823efebe8 (diff)
downloadchromium_src-e6893672ec403d9dbf7fc5aab64ba6109ffd625a.zip
chromium_src-e6893672ec403d9dbf7fc5aab64ba6109ffd625a.tar.gz
chromium_src-e6893672ec403d9dbf7fc5aab64ba6109ffd625a.tar.bz2
Pull chrome stuff out of (and move) extensions::Dispatcher.
This moves Dispatcher into //extensions along with several of its remaining dependencies. This also introduces a DispatcherDelegate interface along with implementations for chrome and app_shell. BUG=359836 TBR=sky for CCRC and ChromeRenderViewTest Review URL: https://codereview.chromium.org/260083006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@267564 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions/renderer/messaging_bindings.cc')
-rw-r--r--extensions/renderer/messaging_bindings.cc441
1 files changed, 441 insertions, 0 deletions
diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc
new file mode 100644
index 0000000..281f276
--- /dev/null
+++ b/extensions/renderer/messaging_bindings.cc
@@ -0,0 +1,441 @@
+// Copyright 2014 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/messaging_bindings.h"
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/values.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/public/renderer/render_view.h"
+#include "content/public/renderer/v8_value_converter.h"
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/common/extension_messages.h"
+#include "extensions/common/manifest_handlers/externally_connectable.h"
+#include "extensions/renderer/dispatcher.h"
+#include "extensions/renderer/event_bindings.h"
+#include "extensions/renderer/object_backed_native_handler.h"
+#include "extensions/renderer/scoped_persistent.h"
+#include "extensions/renderer/script_context.h"
+#include "extensions/renderer/script_context_set.h"
+#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
+#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
+#include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+#include "third_party/WebKit/public/web/WebUserGestureToken.h"
+#include "v8/include/v8.h"
+
+// Message passing API example (in a content script):
+// var extension =
+// new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
+// var port = runtime.connect();
+// port.postMessage('Can you hear me now?');
+// port.onmessage.addListener(function(msg, port) {
+// alert('response=' + msg);
+// port.postMessage('I got your reponse');
+// });
+
+using content::RenderThread;
+using content::V8ValueConverter;
+
+namespace extensions {
+
+namespace {
+
+struct ExtensionData {
+ struct PortData {
+ int ref_count; // how many contexts have a handle to this port
+ PortData() : ref_count(0) {}
+ };
+ std::map<int, PortData> ports; // port ID -> data
+};
+
+base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER;
+
+bool HasPortData(int port_id) {
+ return g_extension_data.Get().ports.find(port_id) !=
+ g_extension_data.Get().ports.end();
+}
+
+ExtensionData::PortData& GetPortData(int port_id) {
+ return g_extension_data.Get().ports[port_id];
+}
+
+void ClearPortData(int port_id) {
+ g_extension_data.Get().ports.erase(port_id);
+}
+
+const char kPortClosedError[] = "Attempting to use a disconnected port object";
+const char kReceivingEndDoesntExistError[] =
+ "Could not establish connection. Receiving end does not exist.";
+
+class ExtensionImpl : public ObjectBackedNativeHandler {
+ public:
+ ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context)
+ : ObjectBackedNativeHandler(context), dispatcher_(dispatcher) {
+ RouteFunction(
+ "CloseChannel",
+ base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
+ RouteFunction(
+ "PortAddRef",
+ base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
+ RouteFunction(
+ "PortRelease",
+ base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
+ RouteFunction(
+ "PostMessage",
+ base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
+ // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
+ RouteFunction("BindToGC",
+ base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
+ }
+
+ virtual ~ExtensionImpl() {}
+
+ private:
+ void ClearPortDataAndNotifyDispatcher(int port_id) {
+ ClearPortData(port_id);
+ dispatcher_->ClearPortData(port_id);
+ }
+
+ bool ShouldForwardUserGesture() {
+ return blink::WebUserGestureIndicator::isProcessingUserGesture() &&
+ !blink::WebUserGestureIndicator::currentUserGestureToken()
+ .wasForwarded();
+ }
+
+ // Sends a message along the given channel.
+ void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ content::RenderView* renderview = context()->GetRenderView();
+ if (!renderview)
+ return;
+
+ // Arguments are (int32 port_id, string message).
+ CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString());
+
+ int port_id = args[0]->Int32Value();
+ if (!HasPortData(port_id)) {
+ args.GetIsolate()->ThrowException(v8::Exception::Error(
+ v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError)));
+ return;
+ }
+
+ renderview->Send(new ExtensionHostMsg_PostMessage(
+ renderview->GetRoutingID(),
+ port_id,
+ Message(*v8::String::Utf8Value(args[1]), ShouldForwardUserGesture())));
+ }
+
+ // Forcefully disconnects a port.
+ void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32 port_id, boolean notify_browser).
+ CHECK_EQ(2, args.Length());
+ CHECK(args[0]->IsInt32());
+ CHECK(args[1]->IsBoolean());
+
+ int port_id = args[0]->Int32Value();
+ if (!HasPortData(port_id))
+ return;
+
+ // Send via the RenderThread because the RenderView might be closing.
+ bool notify_browser = args[1]->BooleanValue();
+ if (notify_browser) {
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_CloseChannel(port_id, std::string()));
+ }
+
+ ClearPortDataAndNotifyDispatcher(port_id);
+ }
+
+ // A new port has been created for a context. This occurs both when script
+ // opens a connection, and when a connection is opened to this script.
+ void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32 port_id).
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ int port_id = args[0]->Int32Value();
+ ++GetPortData(port_id).ref_count;
+ }
+
+ // The frame a port lived in has been destroyed. When there are no more
+ // frames with a reference to a given port, we will disconnect it and notify
+ // the other end of the channel.
+ void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ // Arguments are (int32 port_id).
+ CHECK_EQ(1, args.Length());
+ CHECK(args[0]->IsInt32());
+
+ int port_id = args[0]->Int32Value();
+ if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) {
+ // Send via the RenderThread because the RenderView might be closing.
+ content::RenderThread::Get()->Send(
+ new ExtensionHostMsg_CloseChannel(port_id, std::string()));
+ ClearPortDataAndNotifyDispatcher(port_id);
+ }
+ }
+
+ // Holds a |callback| to run sometime after |object| is GC'ed. |callback| will
+ // not be executed re-entrantly to avoid running JS in an unexpected state.
+ class GCCallback {
+ public:
+ static void Bind(v8::Handle<v8::Object> object,
+ v8::Handle<v8::Function> callback,
+ v8::Isolate* isolate) {
+ GCCallback* cb = new GCCallback(object, callback, isolate);
+ cb->object_.SetWeak(cb, NearDeathCallback);
+ }
+
+ private:
+ static void NearDeathCallback(
+ const v8::WeakCallbackData<v8::Object, GCCallback>& data) {
+ // v8 says we need to explicitly reset weak handles from their callbacks.
+ // It's not implicit as one might expect.
+ data.GetParameter()->object_.reset();
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&GCCallback::RunCallback,
+ base::Owned(data.GetParameter())));
+ }
+
+ GCCallback(v8::Handle<v8::Object> object,
+ v8::Handle<v8::Function> callback,
+ v8::Isolate* isolate)
+ : object_(object), callback_(callback), isolate_(isolate) {}
+
+ void RunCallback() {
+ v8::HandleScope handle_scope(isolate_);
+ v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_);
+ v8::Handle<v8::Context> context = callback->CreationContext();
+ if (context.IsEmpty())
+ return;
+ v8::Context::Scope context_scope(context);
+ blink::WebScopedMicrotaskSuppression suppression;
+ callback->Call(context->Global(), 0, NULL);
+ }
+
+ ScopedPersistent<v8::Object> object_;
+ ScopedPersistent<v8::Function> callback_;
+ v8::Isolate* isolate_;
+
+ DISALLOW_COPY_AND_ASSIGN(GCCallback);
+ };
+
+ // void BindToGC(object, callback)
+ //
+ // Binds |callback| to be invoked *sometime after* |object| is garbage
+ // collected. We don't call the method re-entrantly so as to avoid executing
+ // JS in some bizarro undefined mid-GC state.
+ void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
+ CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction());
+ GCCallback::Bind(args[0].As<v8::Object>(),
+ args[1].As<v8::Function>(),
+ args.GetIsolate());
+ }
+
+ // Dispatcher handle. Not owned.
+ Dispatcher* dispatcher_;
+};
+
+} // namespace
+
+ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher,
+ ScriptContext* context) {
+ return new ExtensionImpl(dispatcher, context);
+}
+
+// static
+void MessagingBindings::DispatchOnConnect(
+ const ScriptContextSet::ContextSet& contexts,
+ int target_port_id,
+ const std::string& channel_name,
+ const base::DictionaryValue& source_tab,
+ const std::string& source_extension_id,
+ const std::string& target_extension_id,
+ const GURL& source_url,
+ const std::string& tls_channel_id,
+ content::RenderView* restrict_to_render_view) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+
+ bool port_created = false;
+ std::string source_url_spec = source_url.spec();
+
+ // TODO(kalman): pass in the full ScriptContextSet; call ForEach.
+ for (ScriptContextSet::ContextSet::const_iterator it = contexts.begin();
+ it != contexts.end();
+ ++it) {
+ if (restrict_to_render_view &&
+ restrict_to_render_view != (*it)->GetRenderView()) {
+ continue;
+ }
+
+ // TODO(kalman): remove when ContextSet::ForEach is available.
+ if ((*it)->v8_context().IsEmpty())
+ continue;
+
+ v8::Handle<v8::Value> tab = v8::Null(isolate);
+ v8::Handle<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
+ const Extension* extension = (*it)->extension();
+ if (extension) {
+ if (!source_tab.empty() && !extension->is_platform_app())
+ tab = converter->ToV8Value(&source_tab, (*it)->v8_context());
+
+ ExternallyConnectableInfo* externally_connectable =
+ ExternallyConnectableInfo::Get(extension);
+ if (externally_connectable &&
+ externally_connectable->accepts_tls_channel_id) {
+ tls_channel_id_value =
+ v8::String::NewFromUtf8(isolate,
+ tls_channel_id.c_str(),
+ v8::String::kNormalString,
+ tls_channel_id.size());
+ }
+ }
+
+ v8::Handle<v8::Value> arguments[] = {
+ // portId
+ v8::Integer::New(isolate, target_port_id),
+ // channelName
+ v8::String::NewFromUtf8(isolate,
+ channel_name.c_str(),
+ v8::String::kNormalString,
+ channel_name.size()),
+ // sourceTab
+ tab,
+ // sourceExtensionId
+ v8::String::NewFromUtf8(isolate,
+ source_extension_id.c_str(),
+ v8::String::kNormalString,
+ source_extension_id.size()),
+ // targetExtensionId
+ v8::String::NewFromUtf8(isolate,
+ target_extension_id.c_str(),
+ v8::String::kNormalString,
+ target_extension_id.size()),
+ // sourceUrl
+ v8::String::NewFromUtf8(isolate,
+ source_url_spec.c_str(),
+ v8::String::kNormalString,
+ source_url_spec.size()),
+ // tlsChannelId
+ tls_channel_id_value,
+ };
+
+ v8::Handle<v8::Value> retval = (*it)->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnConnect", arraysize(arguments), arguments);
+
+ if (retval.IsEmpty()) {
+ LOG(ERROR) << "Empty return value from dispatchOnConnect.";
+ continue;
+ }
+
+ CHECK(retval->IsBoolean());
+ port_created |= retval->BooleanValue();
+ }
+
+ // If we didn't create a port, notify the other end of the channel (treat it
+ // as a disconnect).
+ if (!port_created) {
+ content::RenderThread::Get()->Send(new ExtensionHostMsg_CloseChannel(
+ target_port_id, kReceivingEndDoesntExistError));
+ }
+}
+
+// static
+void MessagingBindings::DeliverMessage(
+ const ScriptContextSet::ContextSet& contexts,
+ int target_port_id,
+ const Message& message,
+ content::RenderView* restrict_to_render_view) {
+ scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
+ scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus;
+ if (message.user_gesture) {
+ web_user_gesture.reset(new blink::WebScopedUserGesture);
+ blink::WebUserGestureIndicator::currentUserGestureToken().setForwarded();
+ allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator);
+ }
+
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+
+ // TODO(kalman): pass in the full ScriptContextSet; call ForEach.
+ for (ScriptContextSet::ContextSet::const_iterator it = contexts.begin();
+ it != contexts.end();
+ ++it) {
+ if (restrict_to_render_view &&
+ restrict_to_render_view != (*it)->GetRenderView()) {
+ continue;
+ }
+
+ // TODO(kalman): remove when ContextSet::ForEach is available.
+ if ((*it)->v8_context().IsEmpty())
+ continue;
+
+ // Check to see whether the context has this port before bothering to create
+ // the message.
+ v8::Handle<v8::Value> port_id_handle =
+ v8::Integer::New(isolate, target_port_id);
+ v8::Handle<v8::Value> has_port = (*it)->module_system()->CallModuleMethod(
+ "messaging", "hasPort", 1, &port_id_handle);
+
+ CHECK(!has_port.IsEmpty());
+ if (!has_port->BooleanValue())
+ continue;
+
+ std::vector<v8::Handle<v8::Value> > arguments;
+ arguments.push_back(v8::String::NewFromUtf8(isolate,
+ message.data.c_str(),
+ v8::String::kNormalString,
+ message.data.size()));
+ arguments.push_back(port_id_handle);
+ (*it)->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnMessage", &arguments);
+ }
+}
+
+// static
+void MessagingBindings::DispatchOnDisconnect(
+ const ScriptContextSet::ContextSet& contexts,
+ int port_id,
+ const std::string& error_message,
+ content::RenderView* restrict_to_render_view) {
+ v8::Isolate* isolate = v8::Isolate::GetCurrent();
+ v8::HandleScope handle_scope(isolate);
+
+ // TODO(kalman): pass in the full ScriptContextSet; call ForEach.
+ for (ScriptContextSet::ContextSet::const_iterator it = contexts.begin();
+ it != contexts.end();
+ ++it) {
+ if (restrict_to_render_view &&
+ restrict_to_render_view != (*it)->GetRenderView()) {
+ continue;
+ }
+
+ // TODO(kalman): remove when ContextSet::ForEach is available.
+ if ((*it)->v8_context().IsEmpty())
+ continue;
+
+ std::vector<v8::Handle<v8::Value> > arguments;
+ arguments.push_back(v8::Integer::New(isolate, port_id));
+ if (!error_message.empty()) {
+ arguments.push_back(
+ v8::String::NewFromUtf8(isolate, error_message.c_str()));
+ } else {
+ arguments.push_back(v8::Null(isolate));
+ }
+ (*it)->module_system()->CallModuleMethod(
+ "messaging", "dispatchOnDisconnect", &arguments);
+ }
+}
+
+} // namespace extensions