| Index: extensions/renderer/messaging_bindings.cc
|
| diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc
|
| index db61703c19bd1a25280fd5dc10305ae0c216d946..6f6f938ed4352c8c5b69fc24f38c86676911ea10 100644
|
| --- a/extensions/renderer/messaging_bindings.cc
|
| +++ b/extensions/renderer/messaging_bindings.cc
|
| @@ -12,7 +12,10 @@
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| #include "base/callback.h"
|
| +#include "base/callback_helpers.h"
|
| +#include "base/lazy_instance.h"
|
| #include "base/message_loop/message_loop.h"
|
| +#include "base/metrics/histogram_macros.h"
|
| #include "base/values.h"
|
| #include "content/public/child/v8_value_converter.h"
|
| #include "content/public/common/child_process_host.h"
|
| @@ -22,6 +25,7 @@
|
| #include "extensions/common/extension_messages.h"
|
| #include "extensions/common/manifest_handlers/externally_connectable.h"
|
| #include "extensions/renderer/extension_frame_helper.h"
|
| +#include "extensions/renderer/extension_port.h"
|
| #include "extensions/renderer/gc_callback.h"
|
| #include "extensions/renderer/script_context.h"
|
| #include "extensions/renderer/script_context_set.h"
|
| @@ -34,8 +38,6 @@
|
| #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) {
|
| @@ -50,40 +52,48 @@ using v8_helpers::IsEmptyOrUndefied;
|
|
|
| namespace {
|
|
|
| -void HasMessagePort(int port_id,
|
| +// A global map between ScriptContext and MessagingBindings.
|
| +base::LazyInstance<std::map<ScriptContext*, MessagingBindings*>>
|
| + g_messaging_map = LAZY_INSTANCE_INITIALIZER;
|
| +
|
| +void HasMessagePort(int global_port_id,
|
| bool* has_port,
|
| ScriptContext* script_context) {
|
| if (*has_port)
|
| return; // Stop checking if the port was found.
|
|
|
| - v8::Isolate* isolate = script_context->isolate();
|
| - v8::HandleScope handle_scope(isolate);
|
| -
|
| - v8::Local<v8::Value> port_id_handle = v8::Integer::New(isolate, port_id);
|
| - v8::Local<v8::Value> v8_has_port =
|
| - script_context->module_system()->CallModuleMethod("messaging", "hasPort",
|
| - 1, &port_id_handle);
|
| - if (IsEmptyOrUndefied(v8_has_port))
|
| - return;
|
| - CHECK(v8_has_port->IsBoolean());
|
| - if (!v8_has_port.As<v8::Boolean>()->Value())
|
| - return;
|
| - *has_port = true;
|
| + MessagingBindings* bindings = g_messaging_map.Get()[script_context];
|
| + DCHECK(bindings);
|
| + if (bindings->GetPortWithGlobalId(global_port_id))
|
| + *has_port = true;
|
| }
|
|
|
| void DispatchOnConnectToScriptContext(
|
| - int target_port_id,
|
| + int global_target_port_id,
|
| const std::string& channel_name,
|
| const ExtensionMsg_TabConnectionInfo* source,
|
| const ExtensionMsg_ExternalConnectionInfo& info,
|
| const std::string& tls_channel_id,
|
| bool* port_created,
|
| ScriptContext* script_context) {
|
| + MessagingBindings* bindings = g_messaging_map.Get()[script_context];
|
| + DCHECK(bindings);
|
| +
|
| + int opposite_port_id = global_target_port_id ^ 1;
|
| + if (bindings->GetPortWithGlobalId(opposite_port_id))
|
| + return; // The channel was opened by this same context; ignore it.
|
| +
|
| + ExtensionPort* port =
|
| + bindings->CreateNewPortWithGlobalId(global_target_port_id);
|
| + int local_port_id = port->local_id();
|
| + // Remove the port.
|
| + base::ScopedClosureRunner remove_port(
|
| + base::Bind(&MessagingBindings::RemovePortWithLocalId,
|
| + bindings->GetWeakPtr(), local_port_id));
|
| +
|
| v8::Isolate* isolate = script_context->isolate();
|
| v8::HandleScope handle_scope(isolate);
|
|
|
| - std::unique_ptr<content::V8ValueConverter> converter(
|
| - content::V8ValueConverter::create());
|
|
|
| const std::string& source_url_spec = info.source_url.spec();
|
| std::string target_extension_id = script_context->GetExtensionID();
|
| @@ -95,8 +105,11 @@ void DispatchOnConnectToScriptContext(
|
| v8::Local<v8::Value> guest_render_frame_routing_id = v8::Undefined(isolate);
|
|
|
| if (extension) {
|
| - if (!source->tab.empty() && !extension->is_platform_app())
|
| + if (!source->tab.empty() && !extension->is_platform_app()) {
|
| + std::unique_ptr<content::V8ValueConverter> converter(
|
| + content::V8ValueConverter::create());
|
| tab = converter->ToV8Value(&source->tab, script_context->v8_context());
|
| + }
|
|
|
| ExternallyConnectableInfo* externally_connectable =
|
| ExternallyConnectableInfo::Get(extension);
|
| @@ -129,7 +142,7 @@ void DispatchOnConnectToScriptContext(
|
|
|
| v8::Local<v8::Value> arguments[] = {
|
| // portId
|
| - v8::Integer::New(isolate, target_port_id),
|
| + v8::Integer::New(isolate, local_port_id),
|
| // channelName
|
| v8_channel_name,
|
| // sourceTab
|
| @@ -156,32 +169,29 @@ void DispatchOnConnectToScriptContext(
|
|
|
| if (!IsEmptyOrUndefied(retval)) {
|
| CHECK(retval->IsBoolean());
|
| - *port_created |= retval.As<v8::Boolean>()->Value();
|
| + bool used = retval.As<v8::Boolean>()->Value();
|
| + *port_created |= used;
|
| + if (used) // Port was used; don't remove it.
|
| + remove_port.ReplaceClosure(base::Closure());
|
| } else {
|
| LOG(ERROR) << "Empty return value from dispatchOnConnect.";
|
| }
|
| }
|
|
|
| void DeliverMessageToScriptContext(const Message& message,
|
| - int target_port_id,
|
| + int global_target_port_id,
|
| ScriptContext* script_context) {
|
| + MessagingBindings* bindings = g_messaging_map.Get()[script_context];
|
| + DCHECK(bindings);
|
| + ExtensionPort* port = bindings->GetPortWithGlobalId(global_target_port_id);
|
| + if (!port)
|
| + return;
|
| +
|
| v8::Isolate* isolate = script_context->isolate();
|
| v8::HandleScope handle_scope(isolate);
|
|
|
| - // Check to see whether the context has this port before bothering to create
|
| - // the message.
|
| v8::Local<v8::Value> port_id_handle =
|
| - v8::Integer::New(isolate, target_port_id);
|
| - v8::Local<v8::Value> has_port =
|
| - script_context->module_system()->CallModuleMethod("messaging", "hasPort",
|
| - 1, &port_id_handle);
|
| - // Could be empty/undefined if an exception was thrown.
|
| - // TODO(kalman): Should this be built into CallModuleMethod?
|
| - if (IsEmptyOrUndefied(has_port))
|
| - return;
|
| - CHECK(has_port->IsBoolean());
|
| - if (!has_port.As<v8::Boolean>()->Value())
|
| - return;
|
| + v8::Integer::New(isolate, port->local_id());
|
|
|
| v8::Local<v8::String> v8_data;
|
| if (!ToV8String(isolate, message.data.c_str(), &v8_data))
|
| @@ -207,14 +217,20 @@ void DeliverMessageToScriptContext(const Message& message,
|
| "messaging", "dispatchOnMessage", &arguments);
|
| }
|
|
|
| -void DispatchOnDisconnectToScriptContext(int port_id,
|
| +void DispatchOnDisconnectToScriptContext(int global_port_id,
|
| const std::string& error_message,
|
| ScriptContext* script_context) {
|
| + MessagingBindings* bindings = g_messaging_map.Get()[script_context];
|
| + DCHECK(bindings);
|
| + ExtensionPort* port = bindings->GetPortWithGlobalId(global_port_id);
|
| + if (!port)
|
| + return;
|
| +
|
| v8::Isolate* isolate = script_context->isolate();
|
| v8::HandleScope handle_scope(isolate);
|
|
|
| std::vector<v8::Local<v8::Value>> arguments;
|
| - arguments.push_back(v8::Integer::New(isolate, port_id));
|
| + arguments.push_back(v8::Integer::New(isolate, port->local_id()));
|
| v8::Local<v8::String> v8_error_message;
|
| if (!error_message.empty())
|
| ToV8String(isolate, error_message.c_str(), &v8_error_message);
|
| @@ -232,6 +248,7 @@ void DispatchOnDisconnectToScriptContext(int port_id,
|
|
|
| MessagingBindings::MessagingBindings(ScriptContext* context)
|
| : ObjectBackedNativeHandler(context), weak_ptr_factory_(this) {
|
| + g_messaging_map.Get()[context] = this;
|
| RouteFunction("CloseChannel", base::Bind(&MessagingBindings::CloseChannel,
|
| base::Unretained(this)));
|
| RouteFunction("PostMessage", base::Bind(&MessagingBindings::PostMessage,
|
| @@ -239,29 +256,38 @@ MessagingBindings::MessagingBindings(ScriptContext* context)
|
| // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
|
| RouteFunction("BindToGC", base::Bind(&MessagingBindings::BindToGC,
|
| base::Unretained(this)));
|
| + RouteFunction("OpenChannelToExtension", "runtime.connect",
|
| + base::Bind(&MessagingBindings::OpenChannelToExtension,
|
| + base::Unretained(this)));
|
| + RouteFunction("OpenChannelToNativeApp", "runtime.connectNative",
|
| + base::Bind(&MessagingBindings::OpenChannelToNativeApp,
|
| + base::Unretained(this)));
|
| + RouteFunction(
|
| + "OpenChannelToTab",
|
| + base::Bind(&MessagingBindings::OpenChannelToTab, base::Unretained(this)));
|
| }
|
|
|
| -MessagingBindings::~MessagingBindings() {}
|
| +MessagingBindings::~MessagingBindings() {
|
| + g_messaging_map.Get().erase(context());
|
| +}
|
|
|
| // static
|
| void MessagingBindings::ValidateMessagePort(
|
| const ScriptContextSet& context_set,
|
| - int port_id,
|
| + int global_port_id,
|
| content::RenderFrame* render_frame) {
|
| int routing_id = render_frame->GetRoutingID();
|
|
|
| bool has_port = false;
|
| context_set.ForEach(render_frame,
|
| - base::Bind(&HasMessagePort, port_id, &has_port));
|
| - // Note: HasMessagePort invokes a JavaScript function. If the runtime of the
|
| - // extension bindings in JS have been compromised, then |render_frame| may be
|
| - // invalid at this point.
|
| + base::Bind(&HasMessagePort, global_port_id, &has_port));
|
|
|
| // A reply is only sent if the port is missing, because the port is assumed to
|
| // exist unless stated otherwise.
|
| if (!has_port) {
|
| content::RenderThread::Get()->Send(
|
| - new ExtensionHostMsg_CloseMessagePort(routing_id, port_id, false));
|
| + new ExtensionHostMsg_CloseMessagePort(routing_id,
|
| + global_port_id, false));
|
| }
|
| }
|
|
|
| @@ -315,19 +341,46 @@ void MessagingBindings::DispatchOnDisconnect(
|
| base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message));
|
| }
|
|
|
| +ExtensionPort* MessagingBindings::GetPortWithGlobalId(int id) {
|
| + for (const auto& key_value : ports_) {
|
| + if (key_value.second->global_id() == id)
|
| + return key_value.second.get();
|
| + }
|
| + return nullptr;
|
| +}
|
| +
|
| +ExtensionPort* MessagingBindings::CreateNewPortWithGlobalId(int global_id) {
|
| + int local_id = GetNextLocalId();
|
| + ExtensionPort* port =
|
| + ports_
|
| + .insert(std::make_pair(
|
| + local_id, base::MakeUnique<ExtensionPort>(context(), local_id)))
|
| + .first->second.get();
|
| + port->SetGlobalId(global_id);
|
| + return port;
|
| +}
|
| +
|
| +void MessagingBindings::RemovePortWithLocalId(int local_id) {
|
| + ports_.erase(local_id);
|
| +}
|
| +
|
| +base::WeakPtr<MessagingBindings> MessagingBindings::GetWeakPtr() {
|
| + return weak_ptr_factory_.GetWeakPtr();
|
| +}
|
| +
|
| void MessagingBindings::PostMessage(
|
| const v8::FunctionCallbackInfo<v8::Value>& args) {
|
| // Arguments are (int32_t port_id, string message).
|
| - CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString());
|
| + CHECK(args.Length() == 2);
|
| + CHECK(args[0]->IsInt32());
|
| + CHECK(args[1]->IsString());
|
|
|
| int port_id = args[0].As<v8::Int32>()->Value();
|
| -
|
| - content::RenderFrame* render_frame = context()->GetRenderFrame();
|
| - if (render_frame) {
|
| - render_frame->Send(new ExtensionHostMsg_PostMessage(
|
| - render_frame->GetRoutingID(), port_id,
|
| - Message(*v8::String::Utf8Value(args[1]),
|
| - blink::WebUserGestureIndicator::isProcessingUserGesture())));
|
| + auto iter = ports_.find(port_id);
|
| + if (iter != ports_.end()) {
|
| + iter->second->PostExtensionMessage(base::MakeUnique<Message>(
|
| + *v8::String::Utf8Value(args[1]),
|
| + blink::WebUserGestureIndicator::isProcessingUserGesture()));
|
| }
|
| }
|
|
|
| @@ -345,18 +398,20 @@ void MessagingBindings::CloseChannel(
|
|
|
| void MessagingBindings::BindToGC(
|
| const v8::FunctionCallbackInfo<v8::Value>& args) {
|
| - CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() &&
|
| - args[2]->IsInt32());
|
| - int port_id = args[2].As<v8::Int32>()->Value();
|
| + CHECK(args.Length() == 3);
|
| + CHECK(args[0]->IsObject());
|
| + CHECK(args[1]->IsFunction());
|
| + CHECK(args[2]->IsInt32());
|
| + int local_port_id = args[2].As<v8::Int32>()->Value();
|
| base::Closure fallback = base::Bind(&base::DoNothing);
|
| - if (port_id >= 0) {
|
| + if (local_port_id >= 0) {
|
| // TODO(robwu): Falling back to closing the port shouldn't be needed. If
|
| // the script context is destroyed, then the frame has navigated. But that
|
| // is already detected by the browser, so this logic is redundant. Remove
|
| // this fallback (and move BindToGC out of messaging because it is also
|
| // used in other places that have nothing to do with messaging...).
|
| fallback = base::Bind(&MessagingBindings::ClosePort,
|
| - weak_ptr_factory_.GetWeakPtr(), port_id,
|
| + weak_ptr_factory_.GetWeakPtr(), local_port_id,
|
| false /* force_close */);
|
| }
|
| // Destroys itself when the object is GC'd or context is invalidated.
|
| @@ -364,14 +419,156 @@ void MessagingBindings::BindToGC(
|
| args[1].As<v8::Function>(), fallback);
|
| }
|
|
|
| -void MessagingBindings::ClosePort(int port_id, bool force_close) {
|
| +void MessagingBindings::OpenChannelToExtension(
|
| + const v8::FunctionCallbackInfo<v8::Value>& args) {
|
| + content::RenderFrame* render_frame = context()->GetRenderFrame();
|
| + if (!render_frame)
|
| + return;
|
| +
|
| + // The Javascript code should validate/fill the arguments.
|
| + CHECK_EQ(args.Length(), 3);
|
| + CHECK(args[0]->IsString());
|
| + CHECK(args[1]->IsString());
|
| + CHECK(args[2]->IsBoolean());
|
| +
|
| + int local_id = GetNextLocalId();
|
| + ports_[local_id] = base::MakeUnique<ExtensionPort>(context(), local_id);
|
| +
|
| + ExtensionMsg_ExternalConnectionInfo info;
|
| + // For messaging APIs, hosted apps should be considered a web page so hide
|
| + // its extension ID.
|
| + const Extension* extension = context()->extension();
|
| + if (extension && !extension->is_hosted_app())
|
| + info.source_id = extension->id();
|
| +
|
| + info.target_id = *v8::String::Utf8Value(args[0]);
|
| + info.source_url = context()->url();
|
| + std::string channel_name = *v8::String::Utf8Value(args[1]);
|
| + // TODO(devlin): Why is this not part of info?
|
| + bool include_tls_channel_id =
|
| + args.Length() > 2 ? args[2]->BooleanValue() : false;
|
| +
|
| + ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
|
| + DCHECK(frame_helper);
|
| + frame_helper->RequestPortId(
|
| + info, channel_name, include_tls_channel_id,
|
| + base::Bind(&MessagingBindings::SetGlobalPortId,
|
| + weak_ptr_factory_.GetWeakPtr(), local_id));
|
| +
|
| + args.GetReturnValue().Set(static_cast<int32_t>(local_id));
|
| +}
|
| +
|
| +void MessagingBindings::OpenChannelToNativeApp(
|
| + const v8::FunctionCallbackInfo<v8::Value>& args) {
|
| + // The Javascript code should validate/fill the arguments.
|
| + CHECK_EQ(args.Length(), 1);
|
| + CHECK(args[0]->IsString());
|
| + // This should be checked by our function routing code.
|
| + CHECK(context()->GetAvailability("runtime.connectNative").is_available());
|
| +
|
| + content::RenderFrame* render_frame = context()->GetRenderFrame();
|
| + if (!render_frame)
|
| + return;
|
| +
|
| + std::string native_app_name = *v8::String::Utf8Value(args[0]);
|
| +
|
| + int local_id = GetNextLocalId();
|
| + ExtensionPort* port =
|
| + ports_
|
| + .insert(std::make_pair(
|
| + local_id, base::MakeUnique<ExtensionPort>(context(), local_id)))
|
| + .first->second.get();
|
| +
|
| + int global_id = -1;
|
| + {
|
| + SCOPED_UMA_HISTOGRAM_TIMER(
|
| + "Extensions.Messaging.GetPortIdSyncTime.NativeApp");
|
| + // TODO(devlin): Make this async. crbug.com/642380
|
| + render_frame->Send(new ExtensionHostMsg_OpenChannelToNativeApp(
|
| + render_frame->GetRoutingID(), native_app_name, &global_id));
|
| + }
|
| +
|
| + port->SetGlobalId(global_id);
|
| + args.GetReturnValue().Set(static_cast<int32_t>(local_id));
|
| +}
|
| +
|
| +void MessagingBindings::OpenChannelToTab(
|
| + const v8::FunctionCallbackInfo<v8::Value>& args) {
|
| + content::RenderFrame* render_frame = context()->GetRenderFrame();
|
| + if (!render_frame)
|
| + return;
|
| +
|
| + // tabs_custom_bindings.js unwraps arguments to tabs.connect/sendMessage and
|
| + // passes them to OpenChannelToTab, in the following order:
|
| + // - |tab_id| - Positive number that specifies the destination of the channel.
|
| + // - |frame_id| - Target frame(s) in the tab where onConnect is dispatched:
|
| + // -1 for all frames, 0 for the main frame, >0 for a child frame.
|
| + // - |extension_id| - ID of the initiating extension.
|
| + // - |channel_name| - A user-defined channel name.
|
| + CHECK(args.Length() == 4);
|
| + CHECK(args[0]->IsInt32());
|
| + CHECK(args[1]->IsInt32());
|
| + CHECK(args[2]->IsString());
|
| + CHECK(args[3]->IsString());
|
| +
|
| + int local_id = GetNextLocalId();
|
| + ExtensionPort* port =
|
| + ports_
|
| + .insert(std::make_pair(
|
| + local_id, base::MakeUnique<ExtensionPort>(context(), local_id)))
|
| + .first->second.get();
|
| +
|
| + ExtensionMsg_TabTargetConnectionInfo info;
|
| + info.tab_id = args[0]->Int32Value();
|
| + info.frame_id = args[1]->Int32Value();
|
| + std::string extension_id = *v8::String::Utf8Value(args[2]);
|
| + std::string channel_name = *v8::String::Utf8Value(args[3]);
|
| + int global_id = -1;
|
| + {
|
| + SCOPED_UMA_HISTOGRAM_TIMER("Extensions.Messaging.GetPortIdSyncTime.Tab");
|
| + // TODO(devlin): Make this async. crbug.com/642380
|
| + render_frame->Send(new ExtensionHostMsg_OpenChannelToTab(
|
| + render_frame->GetRoutingID(), info, extension_id, channel_name,
|
| + &global_id));
|
| + }
|
| + port->SetGlobalId(global_id);
|
| + args.GetReturnValue().Set(static_cast<int32_t>(local_id));
|
| +}
|
| +
|
| +void MessagingBindings::ClosePort(int local_port_id, bool force_close) {
|
| // TODO(robwu): Merge this logic with CloseChannel once the TODO in BindToGC
|
| // has been addressed.
|
| - content::RenderFrame* render_frame = context()->GetRenderFrame();
|
| - if (render_frame) {
|
| - render_frame->Send(new ExtensionHostMsg_CloseMessagePort(
|
| - render_frame->GetRoutingID(), port_id, force_close));
|
| + auto iter = ports_.find(local_port_id);
|
| + if (iter != ports_.end()) {
|
| + std::unique_ptr<ExtensionPort> port = std::move(iter->second);
|
| + ports_.erase(iter);
|
| + port->Close(force_close);
|
| + // If the port hasn't been initialized, we can't delete it just yet, because
|
| + // we need to wait for it to send any pending messages. This is important
|
| + // to support the flow:
|
| + // var port = chrome.runtime.connect();
|
| + // port.postMessage({message});
|
| + // port.disconnect();
|
| + if (!port->initialized())
|
| + disconnected_ports_[port->local_id()] = std::move(port);
|
| }
|
| }
|
|
|
| +void MessagingBindings::SetGlobalPortId(int local_id, int global_id) {
|
| + auto iter = ports_.find(local_id);
|
| + if (iter != ports_.end()) {
|
| + iter->second->SetGlobalId(global_id);
|
| + return;
|
| + }
|
| +
|
| + iter = disconnected_ports_.find(local_id);
|
| + DCHECK(iter != disconnected_ports_.end());
|
| + iter->second->SetGlobalId(global_id);
|
| + // Setting the global id dispatches pending messages, so we can delete the
|
| + // port now.
|
| + disconnected_ports_.erase(iter);
|
| +}
|
| +
|
| +int MessagingBindings::GetNextLocalId() { return next_local_id_++; }
|
| +
|
| } // namespace extensions
|
|
|