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

Unified Diff: extensions/renderer/messaging_bindings.cc

Issue 2300453002: [Extensions] Begin making Extension port initialization asynchronous (Closed)
Patch Set: Nasko's Created 4 years, 3 months 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
« no previous file with comments | « extensions/renderer/messaging_bindings.h ('k') | extensions/renderer/resources/messaging.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « extensions/renderer/messaging_bindings.h ('k') | extensions/renderer/resources/messaging.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698