1 // 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
10 #include "base/bind.h"
11 #include "extensions/common/api/messaging/message.h"
12 #include "extensions/renderer/bindings/api_binding_util.h"
13 #include "extensions/renderer/bindings/api_event_handler.h"
14 #include "extensions/renderer/bindings/event_emitter.h"
15 #include "extensions/renderer/messaging_util.h"
16 #include "gin/arguments.h"
17 #include "gin/converter.h"
18 #include "gin/object_template_builder.h"
19
20 namespace extensions {
21
22 namespace {
23
24 constexpr char kSenderKey[] = "sender";
25 constexpr char kOnMessageEvent[] = "onMessage";
26 constexpr char kOnDisconnectEvent[] = "onDisconnect";
27 constexpr char kContextInvalidatedError[] = "Extension context invalidated.";
28
29 } // namespace
30
GinPort(v8::Local<v8::Context> context,const PortId & port_id,int routing_id,const std::string & name,APIEventHandler * event_handler,Delegate * delegate)31 GinPort::GinPort(v8::Local<v8::Context> context,
32 const PortId& port_id,
33 int routing_id,
34 const std::string& name,
35 APIEventHandler* event_handler,
36 Delegate* delegate)
37 : port_id_(port_id),
38 routing_id_(routing_id),
39 name_(name),
40 event_handler_(event_handler),
41 delegate_(delegate),
42 accessed_sender_(false) {
43 context_invalidation_listener_.emplace(
44 context, base::BindOnce(&GinPort::OnContextInvalidated,
45 weak_factory_.GetWeakPtr()));
46 }
47
~GinPort()48 GinPort::~GinPort() {}
49
50 gin::WrapperInfo GinPort::kWrapperInfo = {gin::kEmbedderNativeGin};
51
GetObjectTemplateBuilder(v8::Isolate * isolate)52 gin::ObjectTemplateBuilder GinPort::GetObjectTemplateBuilder(
53 v8::Isolate* isolate) {
54 return Wrappable<GinPort>::GetObjectTemplateBuilder(isolate)
55 .SetMethod("disconnect", &GinPort::DisconnectHandler)
56 .SetMethod("postMessage", &GinPort::PostMessageHandler)
57 .SetLazyDataProperty("name", &GinPort::GetName)
58 .SetLazyDataProperty("onDisconnect", &GinPort::GetOnDisconnectEvent)
59 .SetLazyDataProperty("onMessage", &GinPort::GetOnMessageEvent)
60 .SetLazyDataProperty("sender", &GinPort::GetSender);
61 }
62
GetTypeName()63 const char* GinPort::GetTypeName() {
64 return "Port";
65 }
66
DispatchOnMessage(v8::Local<v8::Context> context,const Message & message)67 void GinPort::DispatchOnMessage(v8::Local<v8::Context> context,
68 const Message& message) {
69 DCHECK_EQ(kActive, state_);
70
71 v8::Isolate* isolate = context->GetIsolate();
72 v8::HandleScope handle_scope(isolate);
73 v8::Context::Scope context_scope(context);
74
75 v8::Local<v8::Value> parsed_message =
76 messaging_util::MessageToV8(context, message);
77 if (parsed_message.IsEmpty()) {
78 NOTREACHED();
79 return;
80 }
81
82 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
DispatchOnDisconnect(v8::Local<v8::Context> context)87 void GinPort::DispatchOnDisconnect(v8::Local<v8::Context> context) {
88 DCHECK_EQ(kActive, state_);
89
90 // 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
95 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
103 InvalidateEvents(context);
104
105 DCHECK_NE(state_, kActive);
106 }
107
SetSender(v8::Local<v8::Context> context,v8::Local<v8::Value> sender)108 void GinPort::SetSender(v8::Local<v8::Context> context,
109 v8::Local<v8::Value> sender) {
110 DCHECK_EQ(kActive, state_);
111 DCHECK(!accessed_sender_)
112 << "|sender| can only be set before its first access.";
113
114 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
DisconnectHandler(gin::Arguments * arguments)124 void GinPort::DisconnectHandler(gin::Arguments* arguments) {
125 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)
133 return;
134
135 v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
136 InvalidateEvents(context);
137 delegate_->ClosePort(context, port_id_, routing_id_);
138 state_ = kDisconnected;
139 }
140
PostMessageHandler(gin::Arguments * arguments,v8::Local<v8::Value> v8_message)141 void GinPort::PostMessageHandler(gin::Arguments* arguments,
142 v8::Local<v8::Value> v8_message) {
143 v8::Isolate* isolate = arguments->isolate();
144 v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
145
146 if (state_ == kInvalidated) {
147 ThrowError(isolate, kContextInvalidatedError);
148 return;
149 }
150
151 if (state_ == kDisconnected) {
152 ThrowError(isolate, "Attempting to use a disconnected port object");
153 return;
154 }
155
156 std::string error;
157 std::unique_ptr<Message> message =
158 messaging_util::MessageFromV8(context, v8_message, &error);
159 // 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.
162 if (!message) {
163 ThrowError(isolate, error);
164 return;
165 }
166
167 delegate_->PostMessageToPort(context, port_id_, routing_id_,
168 std::move(message));
169 }
170
GetName()171 std::string GinPort::GetName() {
172 return name_;
173 }
174
GetOnDisconnectEvent(gin::Arguments * arguments)175 v8::Local<v8::Value> GinPort::GetOnDisconnectEvent(gin::Arguments* arguments) {
176 return GetEvent(arguments->GetHolderCreationContext(), kOnDisconnectEvent);
177 }
178
GetOnMessageEvent(gin::Arguments * arguments)179 v8::Local<v8::Value> GinPort::GetOnMessageEvent(gin::Arguments* arguments) {
180 return GetEvent(arguments->GetHolderCreationContext(), kOnMessageEvent);
181 }
182
GetSender(gin::Arguments * arguments)183 v8::Local<v8::Value> GinPort::GetSender(gin::Arguments* arguments) {
184 accessed_sender_ = true;
185 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
GetEvent(v8::Local<v8::Context> context,base::StringPiece event_name)199 v8::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();
203
204 if (state_ == kInvalidated) {
205 ThrowError(isolate, kContextInvalidatedError);
206 return v8::Local<v8::Object>();
207 }
208
209 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
DispatchEvent(v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * args,base::StringPiece event_name)234 void 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
243 emitter->Fire(context, args, nullptr, JSRunner::ResultCallback());
244 }
245
OnContextInvalidated()246 void 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
InvalidateEvents(v8::Local<v8::Context> context)253 void GinPort::InvalidateEvents(v8::Local<v8::Context> context) {
254 // 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
260 // 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.
263 event_handler_->InvalidateCustomEvent(context,
264 GetEvent(context, kOnMessageEvent));
265 event_handler_->InvalidateCustomEvent(context,
266 GetEvent(context, kOnDisconnectEvent));
267 }
268
ThrowError(v8::Isolate * isolate,base::StringPiece error)269 void GinPort::ThrowError(v8::Isolate* isolate, base::StringPiece error) {
270 isolate->ThrowException(
271 v8::Exception::Error(gin::StringToV8(isolate, error)));
272 }
273
274 } // namespace extensions
275