blob: f658468e95ae5a237254cbff23f639656d69bf9f [file] [log] [blame]
Devlin Croninc7d8fd062017-09-16 03:36:431// Copyright 2017 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "extensions/renderer/gin_port.h"
6
7#include <cstring>
8#include <vector>
9
Devlin Croninbd5ee3c2018-03-01 07:15:4610#include "base/bind.h"
Devlin Croninc7d8fd062017-09-16 03:36:4311#include "extensions/common/api/messaging/message.h"
Devlin Croninbd5ee3c2018-03-01 07:15:4612#include "extensions/renderer/bindings/api_binding_util.h"
Devlin Croninc7d8fd062017-09-16 03:36:4313#include "extensions/renderer/bindings/api_event_handler.h"
14#include "extensions/renderer/bindings/event_emitter.h"
Devlin Cronin0b875672017-10-06 00:49:2115#include "extensions/renderer/messaging_util.h"
Devlin Croninc7d8fd062017-09-16 03:36:4316#include "gin/arguments.h"
17#include "gin/converter.h"
18#include "gin/object_template_builder.h"
Devlin Croninc7d8fd062017-09-16 03:36:4319
20namespace extensions {
21
22namespace {
23
24constexpr char kSenderKey[] = "sender";
25constexpr char kOnMessageEvent[] = "onMessage";
26constexpr char kOnDisconnectEvent[] = "onDisconnect";
Devlin Croninbd5ee3c2018-03-01 07:15:4627constexpr char kContextInvalidatedError[] = "Extension context invalidated.";
Devlin Croninc7d8fd062017-09-16 03:36:4328
29} // namespace
30
Devlin Croninbd5ee3c2018-03-01 07:15:4631GinPort::GinPort(v8::Local<v8::Context> context,
32 const PortId& port_id,
Devlin Cronin60b72962017-09-29 00:11:4133 int routing_id,
Devlin Croninc7d8fd062017-09-16 03:36:4334 const std::string& name,
35 APIEventHandler* event_handler,
36 Delegate* delegate)
37 : port_id_(port_id),
Devlin Cronin60b72962017-09-29 00:11:4138 routing_id_(routing_id),
Devlin Croninc7d8fd062017-09-16 03:36:4339 name_(name),
40 event_handler_(event_handler),
Devlin Croninbd5ee3c2018-03-01 07:15:4641 delegate_(delegate),
Jeremy Roman9fc2de62019-07-12 14:15:0342 accessed_sender_(false) {
Devlin Croninbd5ee3c2018-03-01 07:15:4643 context_invalidation_listener_.emplace(
44 context, base::BindOnce(&GinPort::OnContextInvalidated,
45 weak_factory_.GetWeakPtr()));
46}
Devlin Croninc7d8fd062017-09-16 03:36:4347
48GinPort::~GinPort() {}
49
50gin::WrapperInfo GinPort::kWrapperInfo = {gin::kEmbedderNativeGin};
51
52gin::ObjectTemplateBuilder GinPort::GetObjectTemplateBuilder(
53 v8::Isolate* isolate) {
54 return Wrappable<GinPort>::GetObjectTemplateBuilder(isolate)
55 .SetMethod("disconnect", &GinPort::DisconnectHandler)
56 .SetMethod("postMessage", &GinPort::PostMessageHandler)
Devlin Cronin53a39792019-03-07 16:29:4557 .SetLazyDataProperty("name", &GinPort::GetName)
58 .SetLazyDataProperty("onDisconnect", &GinPort::GetOnDisconnectEvent)
59 .SetLazyDataProperty("onMessage", &GinPort::GetOnMessageEvent)
60 .SetLazyDataProperty("sender", &GinPort::GetSender);
Devlin Croninc7d8fd062017-09-16 03:36:4361}
62
Devlin Cronind00988c2018-04-19 15:45:1163const char* GinPort::GetTypeName() {
64 return "Port";
65}
66
Devlin Croninc7d8fd062017-09-16 03:36:4367void GinPort::DispatchOnMessage(v8::Local<v8::Context> context,
68 const Message& message) {
Devlin Croninbd5ee3c2018-03-01 07:15:4669 DCHECK_EQ(kActive, state_);
70
Devlin Croninc7d8fd062017-09-16 03:36:4371 v8::Isolate* isolate = context->GetIsolate();
72 v8::HandleScope handle_scope(isolate);
73 v8::Context::Scope context_scope(context);
74
Devlin Cronin0b875672017-10-06 00:49:2175 v8::Local<v8::Value> parsed_message =
76 messaging_util::MessageToV8(context, message);
77 if (parsed_message.IsEmpty()) {
78 NOTREACHED();
79 return;
Devlin Croninc7d8fd062017-09-16 03:36:4380 }
Devlin Cronin0b875672017-10-06 00:49:2181
Devlin Croninc7d8fd062017-09-16 03:36:4382 v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
83 std::vector<v8::Local<v8::Value>> args = {parsed_message, self};
84 DispatchEvent(context, &args, kOnMessageEvent);
85}
86
Devlin Cronin60b72962017-09-29 00:11:4187void GinPort::DispatchOnDisconnect(v8::Local<v8::Context> context) {
Devlin Croninbd5ee3c2018-03-01 07:15:4688 DCHECK_EQ(kActive, state_);
Devlin Croninc7d8fd062017-09-16 03:36:4389
Maksim Ivanove87c3a42019-02-20 04:06:3190 // Update |state_| before dispatching the onDisconnect event, so that we are
91 // able to reject attempts to disconnect the port again or to send a message
92 // from the event handler.
93 state_ = kDisconnected;
94
Devlin Croninc7d8fd062017-09-16 03:36:4395 v8::Isolate* isolate = context->GetIsolate();
96 v8::HandleScope handle_scope(isolate);
97 v8::Context::Scope context_scope(context);
98
99 v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
100 std::vector<v8::Local<v8::Value>> args = {self};
101 DispatchEvent(context, &args, kOnDisconnectEvent);
102
Devlin Croninbd5ee3c2018-03-01 07:15:46103 InvalidateEvents(context);
Maksim Ivanove87c3a42019-02-20 04:06:31104
105 DCHECK_NE(state_, kActive);
Devlin Croninc7d8fd062017-09-16 03:36:43106}
107
108void GinPort::SetSender(v8::Local<v8::Context> context,
109 v8::Local<v8::Value> sender) {
Devlin Croninbd5ee3c2018-03-01 07:15:46110 DCHECK_EQ(kActive, state_);
Devlin Cronin53a39792019-03-07 16:29:45111 DCHECK(!accessed_sender_)
112 << "|sender| can only be set before its first access.";
Devlin Croninbd5ee3c2018-03-01 07:15:46113
Devlin Croninc7d8fd062017-09-16 03:36:43114 v8::Isolate* isolate = context->GetIsolate();
115 v8::HandleScope handle_scope(isolate);
116
117 v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
118 v8::Local<v8::Private> key =
119 v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
120 v8::Maybe<bool> set_result = wrapper->SetPrivate(context, key, sender);
121 DCHECK(set_result.IsJust() && set_result.FromJust());
122}
123
124void GinPort::DisconnectHandler(gin::Arguments* arguments) {
Devlin Croninbd5ee3c2018-03-01 07:15:46125 if (state_ == kInvalidated) {
126 ThrowError(arguments->isolate(), kContextInvalidatedError);
127 return;
128 }
129
130 // NOTE: We don't currently throw an error for calling disconnect() multiple
131 // times, but we could.
132 if (state_ == kDisconnected)
Devlin Croninc7d8fd062017-09-16 03:36:43133 return;
134
135 v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
Devlin Croninbd5ee3c2018-03-01 07:15:46136 InvalidateEvents(context);
Devlin Cronin60b72962017-09-29 00:11:41137 delegate_->ClosePort(context, port_id_, routing_id_);
Devlin Croninbd5ee3c2018-03-01 07:15:46138 state_ = kDisconnected;
Devlin Croninc7d8fd062017-09-16 03:36:43139}
140
141void GinPort::PostMessageHandler(gin::Arguments* arguments,
142 v8::Local<v8::Value> v8_message) {
143 v8::Isolate* isolate = arguments->isolate();
Devlin Cronin60b72962017-09-29 00:11:41144 v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
Devlin Croninbd5ee3c2018-03-01 07:15:46145
146 if (state_ == kInvalidated) {
147 ThrowError(isolate, kContextInvalidatedError);
148 return;
149 }
150
151 if (state_ == kDisconnected) {
Devlin Croninc7d8fd062017-09-16 03:36:43152 ThrowError(isolate, "Attempting to use a disconnected port object");
153 return;
154 }
155
Devlin Croninfe7aae62017-11-16 03:49:55156 std::string error;
Devlin Cronin0b875672017-10-06 00:49:21157 std::unique_ptr<Message> message =
Devlin Croninfe7aae62017-11-16 03:49:55158 messaging_util::MessageFromV8(context, v8_message, &error);
Devlin Croninb15f7f02018-01-31 19:37:32159 // NOTE(devlin): JS-based bindings just log to the console here and return,
160 // rather than throwing an error. But it really seems like it should be an
161 // error. Let's see how this goes.
Devlin Cronin0b875672017-10-06 00:49:21162 if (!message) {
Devlin Croninfe7aae62017-11-16 03:49:55163 ThrowError(isolate, error);
Devlin Croninc7d8fd062017-09-16 03:36:43164 return;
165 }
166
Devlin Cronin0b875672017-10-06 00:49:21167 delegate_->PostMessageToPort(context, port_id_, routing_id_,
168 std::move(message));
Devlin Croninc7d8fd062017-09-16 03:36:43169}
170
171std::string GinPort::GetName() {
172 return name_;
173}
174
175v8::Local<v8::Value> GinPort::GetOnDisconnectEvent(gin::Arguments* arguments) {
176 return GetEvent(arguments->GetHolderCreationContext(), kOnDisconnectEvent);
177}
178
179v8::Local<v8::Value> GinPort::GetOnMessageEvent(gin::Arguments* arguments) {
180 return GetEvent(arguments->GetHolderCreationContext(), kOnMessageEvent);
181}
182
183v8::Local<v8::Value> GinPort::GetSender(gin::Arguments* arguments) {
Devlin Cronin53a39792019-03-07 16:29:45184 accessed_sender_ = true;
Devlin Croninc7d8fd062017-09-16 03:36:43185 v8::Isolate* isolate = arguments->isolate();
186 v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
187 v8::Local<v8::Private> key =
188 v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
189 v8::Local<v8::Value> sender;
190 if (!wrapper->GetPrivate(arguments->GetHolderCreationContext(), key)
191 .ToLocal(&sender)) {
192 NOTREACHED();
193 return v8::Undefined(isolate);
194 }
195
196 return sender;
197}
198
199v8::Local<v8::Object> GinPort::GetEvent(v8::Local<v8::Context> context,
200 base::StringPiece event_name) {
201 DCHECK(event_name == kOnMessageEvent || event_name == kOnDisconnectEvent);
202 v8::Isolate* isolate = context->GetIsolate();
Devlin Croninbd5ee3c2018-03-01 07:15:46203
204 if (state_ == kInvalidated) {
205 ThrowError(isolate, kContextInvalidatedError);
206 return v8::Local<v8::Object>();
207 }
208
Devlin Croninc7d8fd062017-09-16 03:36:43209 v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
210 v8::Local<v8::Private> key =
211 v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, event_name));
212 v8::Local<v8::Value> event_val;
213 if (!wrapper->GetPrivate(context, key).ToLocal(&event_val)) {
214 NOTREACHED();
215 return v8::Local<v8::Object>();
216 }
217
218 DCHECK(!event_val.IsEmpty());
219 v8::Local<v8::Object> event_object;
220 if (event_val->IsUndefined()) {
221 event_object = event_handler_->CreateAnonymousEventInstance(context);
222 v8::Maybe<bool> set_result =
223 wrapper->SetPrivate(context, key, event_object);
224 if (!set_result.IsJust() || !set_result.FromJust()) {
225 NOTREACHED();
226 return v8::Local<v8::Object>();
227 }
228 } else {
229 event_object = event_val.As<v8::Object>();
230 }
231 return event_object;
232}
233
234void GinPort::DispatchEvent(v8::Local<v8::Context> context,
235 std::vector<v8::Local<v8::Value>>* args,
236 base::StringPiece event_name) {
237 v8::Isolate* isolate = context->GetIsolate();
238 v8::Local<v8::Value> on_message = GetEvent(context, event_name);
239 EventEmitter* emitter = nullptr;
240 gin::Converter<EventEmitter*>::FromV8(isolate, on_message, &emitter);
241 CHECK(emitter);
242
Devlin Cronine086c762017-12-21 15:48:43243 emitter->Fire(context, args, nullptr, JSRunner::ResultCallback());
Devlin Croninc7d8fd062017-09-16 03:36:43244}
245
Devlin Croninbd5ee3c2018-03-01 07:15:46246void GinPort::OnContextInvalidated() {
247 DCHECK_NE(state_, kInvalidated);
248 state_ = kInvalidated;
249 // Note: no need to InvalidateEvents() here, since the APIEventHandler will
250 // invalidate them when the context is disposed.
251}
252
253void GinPort::InvalidateEvents(v8::Local<v8::Context> context) {
Kevin McNee1349a912018-11-20 22:12:50254 // No need to invalidate the events if the context itself was already
255 // invalidated; the APIEventHandler will have already cleaned up the
256 // listeners.
257 if (state_ == kInvalidated)
258 return;
259
Devlin Croninbd5ee3c2018-03-01 07:15:46260 // TODO(devlin): By calling GetEvent() here, we'll end up creating an event
261 // if one didn't exist. It would be more efficient to only invalidate events
262 // that the port has already created.
Devlin Croninc7d8fd062017-09-16 03:36:43263 event_handler_->InvalidateCustomEvent(context,
264 GetEvent(context, kOnMessageEvent));
265 event_handler_->InvalidateCustomEvent(context,
266 GetEvent(context, kOnDisconnectEvent));
Devlin Croninc7d8fd062017-09-16 03:36:43267}
268
269void GinPort::ThrowError(v8::Isolate* isolate, base::StringPiece error) {
270 isolate->ThrowException(
271 v8::Exception::Error(gin::StringToV8(isolate, error)));
272}
273
274} // namespace extensions