[Extensions Bindings] Introduce a GinPort for migrating messaging bindings
Extension messaging is one of the few APIs that's exposed to content
scripts, web pages, and extension contexts alike. It also has a bunch
of custom JS bindings and has led to various bugs and leaks. This makes
it a great target for converting to native bindings.
Introduce a GinPort object to represent the JS-exposed interface of a
runtime.Port object (i.e., the two-way communication extensions use to
message themselves and each other).
Add unittests for the same.
Bug: 653596
Change-Id: I9e4c3c5465a63307ca8c13d26742800f866bf74c
Reviewed-on: https://chromium-review.googlesource.com/657919
Commit-Queue: Devlin <[email protected]>
Reviewed-by: Istiaque Ahmed <[email protected]>
Reviewed-by: Jeremy Roman <[email protected]>
Cr-Commit-Position: refs/heads/master@{#502482}
diff --git a/extensions/renderer/gin_port.cc b/extensions/renderer/gin_port.cc
new file mode 100644
index 0000000..2ee5183
--- /dev/null
+++ b/extensions/renderer/gin_port.cc
@@ -0,0 +1,239 @@
+// Copyright 2017 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/gin_port.h"
+
+#include <cstring>
+#include <vector>
+
+#include "extensions/common/api/messaging/message.h"
+#include "extensions/renderer/bindings/api_event_handler.h"
+#include "extensions/renderer/bindings/event_emitter.h"
+#include "gin/arguments.h"
+#include "gin/converter.h"
+#include "gin/object_template_builder.h"
+#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
+
+namespace extensions {
+
+namespace {
+
+constexpr char kSenderKey[] = "sender";
+constexpr char kOnMessageEvent[] = "onMessage";
+constexpr char kOnDisconnectEvent[] = "onDisconnect";
+
+} // namespace
+
+GinPort::GinPort(const PortId& port_id,
+ const std::string& name,
+ APIEventHandler* event_handler,
+ Delegate* delegate)
+ : port_id_(port_id),
+ name_(name),
+ event_handler_(event_handler),
+ delegate_(delegate) {}
+
+GinPort::~GinPort() {}
+
+gin::WrapperInfo GinPort::kWrapperInfo = {gin::kEmbedderNativeGin};
+
+gin::ObjectTemplateBuilder GinPort::GetObjectTemplateBuilder(
+ v8::Isolate* isolate) {
+ return Wrappable<GinPort>::GetObjectTemplateBuilder(isolate)
+ .SetMethod("disconnect", &GinPort::DisconnectHandler)
+ .SetMethod("postMessage", &GinPort::PostMessageHandler)
+ .SetProperty("name", &GinPort::GetName)
+ .SetProperty("onDisconnect", &GinPort::GetOnDisconnectEvent)
+ .SetProperty("onMessage", &GinPort::GetOnMessageEvent)
+ .SetProperty("sender", &GinPort::GetSender);
+}
+
+void GinPort::DispatchOnMessage(v8::Local<v8::Context> context,
+ const Message& message) {
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ v8::Local<v8::String> v8_message_string =
+ gin::StringToV8(isolate, message.data);
+ v8::Local<v8::Value> parsed_message;
+ {
+ v8::TryCatch try_catch(isolate);
+ if (!v8::JSON::Parse(context, v8_message_string).ToLocal(&parsed_message)) {
+ NOTREACHED();
+ return;
+ }
+ }
+ v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
+ std::vector<v8::Local<v8::Value>> args = {parsed_message, self};
+ DispatchEvent(context, &args, kOnMessageEvent);
+}
+
+void GinPort::Disconnect(v8::Local<v8::Context> context) {
+ DCHECK(!is_closed_);
+
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Context::Scope context_scope(context);
+
+ v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
+ std::vector<v8::Local<v8::Value>> args = {self};
+ DispatchEvent(context, &args, kOnDisconnectEvent);
+
+ Invalidate(context);
+}
+
+void GinPort::SetSender(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> sender) {
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+
+ v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
+ v8::Local<v8::Private> key =
+ v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
+ v8::Maybe<bool> set_result = wrapper->SetPrivate(context, key, sender);
+ DCHECK(set_result.IsJust() && set_result.FromJust());
+}
+
+void GinPort::DisconnectHandler(gin::Arguments* arguments) {
+ if (is_closed_)
+ return;
+
+ v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
+ Invalidate(context);
+}
+
+void GinPort::PostMessageHandler(gin::Arguments* arguments,
+ v8::Local<v8::Value> v8_message) {
+ v8::Isolate* isolate = arguments->isolate();
+ if (is_closed_) {
+ ThrowError(isolate, "Attempting to use a disconnected port object");
+ return;
+ }
+
+ // TODO(devlin): For some reason, we don't use the signature for
+ // Port.postMessage when evaluating the parameters. We probably should, but
+ // we don't know how many extensions that may break. It would be good to
+ // investigate, and, ideally, use the signature.
+
+ if (v8_message->IsUndefined()) {
+ // JSON.stringify won't serialized undefined (it returns undefined), but it
+ // will serialized null. We've always converted undefined to null in JS
+ // bindings, so preserve this behavior for now.
+ v8_message = v8::Null(isolate);
+ }
+
+ bool success = false;
+ v8::Local<v8::String> stringified;
+ {
+ v8::TryCatch try_catch(isolate);
+ success =
+ v8::JSON::Stringify(arguments->GetHolderCreationContext(), v8_message)
+ .ToLocal(&stringified);
+ }
+
+ std::string message;
+ if (success) {
+ message = gin::V8ToString(stringified);
+ // JSON.stringify can either fail (with unserializable objects) or can
+ // return undefined. If it returns undefined, the v8 API then coerces it to
+ // the string value "undefined". Throw an error if we were passed
+ // unserializable objects.
+ success = message != "undefined";
+ }
+
+ if (!success) {
+ ThrowError(isolate, "Illegal argument to Port.postMessage");
+ return;
+ }
+
+ delegate_->PostMessageToPort(
+ port_id_,
+ std::make_unique<Message>(
+ message, blink::WebUserGestureIndicator::IsProcessingUserGesture()));
+}
+
+std::string GinPort::GetName() {
+ return name_;
+}
+
+v8::Local<v8::Value> GinPort::GetOnDisconnectEvent(gin::Arguments* arguments) {
+ return GetEvent(arguments->GetHolderCreationContext(), kOnDisconnectEvent);
+}
+
+v8::Local<v8::Value> GinPort::GetOnMessageEvent(gin::Arguments* arguments) {
+ return GetEvent(arguments->GetHolderCreationContext(), kOnMessageEvent);
+}
+
+v8::Local<v8::Value> GinPort::GetSender(gin::Arguments* arguments) {
+ v8::Isolate* isolate = arguments->isolate();
+ v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
+ v8::Local<v8::Private> key =
+ v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
+ v8::Local<v8::Value> sender;
+ if (!wrapper->GetPrivate(arguments->GetHolderCreationContext(), key)
+ .ToLocal(&sender)) {
+ NOTREACHED();
+ return v8::Undefined(isolate);
+ }
+
+ return sender;
+}
+
+v8::Local<v8::Object> GinPort::GetEvent(v8::Local<v8::Context> context,
+ base::StringPiece event_name) {
+ DCHECK(event_name == kOnMessageEvent || event_name == kOnDisconnectEvent);
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
+ v8::Local<v8::Private> key =
+ v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, event_name));
+ v8::Local<v8::Value> event_val;
+ if (!wrapper->GetPrivate(context, key).ToLocal(&event_val)) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+
+ DCHECK(!event_val.IsEmpty());
+ v8::Local<v8::Object> event_object;
+ if (event_val->IsUndefined()) {
+ event_object = event_handler_->CreateAnonymousEventInstance(context);
+ v8::Maybe<bool> set_result =
+ wrapper->SetPrivate(context, key, event_object);
+ if (!set_result.IsJust() || !set_result.FromJust()) {
+ NOTREACHED();
+ return v8::Local<v8::Object>();
+ }
+ } else {
+ event_object = event_val.As<v8::Object>();
+ }
+ return event_object;
+}
+
+void GinPort::DispatchEvent(v8::Local<v8::Context> context,
+ std::vector<v8::Local<v8::Value>>* args,
+ base::StringPiece event_name) {
+ v8::Isolate* isolate = context->GetIsolate();
+ v8::Local<v8::Value> on_message = GetEvent(context, event_name);
+ EventEmitter* emitter = nullptr;
+ gin::Converter<EventEmitter*>::FromV8(isolate, on_message, &emitter);
+ CHECK(emitter);
+
+ emitter->Fire(context, args, nullptr);
+}
+
+void GinPort::Invalidate(v8::Local<v8::Context> context) {
+ is_closed_ = true;
+ event_handler_->InvalidateCustomEvent(context,
+ GetEvent(context, kOnMessageEvent));
+ event_handler_->InvalidateCustomEvent(context,
+ GetEvent(context, kOnDisconnectEvent));
+ delegate_->ClosePort(port_id_);
+}
+
+void GinPort::ThrowError(v8::Isolate* isolate, base::StringPiece error) {
+ isolate->ThrowException(
+ v8::Exception::Error(gin::StringToV8(isolate, error)));
+}
+
+} // namespace extensions