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