diff options
author | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-01 17:29:13 +0000 |
---|---|---|
committer | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-01 17:29:13 +0000 |
commit | e6893672ec403d9dbf7fc5aab64ba6109ffd625a (patch) | |
tree | 25d46598e07af97d4f284663a3e3d53412e4eb2b /extensions/renderer/messaging_bindings.cc | |
parent | 3dfb202d7b2cbdb92b1fe9d6274211d823efebe8 (diff) | |
download | chromium_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.cc | 441 |
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 |