blob: 2ee51832009ac5f125f904b9552efb67582aa9f1 [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
10#include "extensions/common/api/messaging/message.h"
11#include "extensions/renderer/bindings/api_event_handler.h"
12#include "extensions/renderer/bindings/event_emitter.h"
13#include "gin/arguments.h"
14#include "gin/converter.h"
15#include "gin/object_template_builder.h"
16#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
17
18namespace extensions {
19
20namespace {
21
22constexpr char kSenderKey[] = "sender";
23constexpr char kOnMessageEvent[] = "onMessage";
24constexpr char kOnDisconnectEvent[] = "onDisconnect";
25
26} // namespace
27
28GinPort::GinPort(const PortId& port_id,
29 const std::string& name,
30 APIEventHandler* event_handler,
31 Delegate* delegate)
32 : port_id_(port_id),
33 name_(name),
34 event_handler_(event_handler),
35 delegate_(delegate) {}
36
37GinPort::~GinPort() {}
38
39gin::WrapperInfo GinPort::kWrapperInfo = {gin::kEmbedderNativeGin};
40
41gin::ObjectTemplateBuilder GinPort::GetObjectTemplateBuilder(
42 v8::Isolate* isolate) {
43 return Wrappable<GinPort>::GetObjectTemplateBuilder(isolate)
44 .SetMethod("disconnect", &GinPort::DisconnectHandler)
45 .SetMethod("postMessage", &GinPort::PostMessageHandler)
46 .SetProperty("name", &GinPort::GetName)
47 .SetProperty("onDisconnect", &GinPort::GetOnDisconnectEvent)
48 .SetProperty("onMessage", &GinPort::GetOnMessageEvent)
49 .SetProperty("sender", &GinPort::GetSender);
50}
51
52void GinPort::DispatchOnMessage(v8::Local<v8::Context> context,
53 const Message& message) {
54 v8::Isolate* isolate = context->GetIsolate();
55 v8::HandleScope handle_scope(isolate);
56 v8::Context::Scope context_scope(context);
57
58 v8::Local<v8::String> v8_message_string =
59 gin::StringToV8(isolate, message.data);
60 v8::Local<v8::Value> parsed_message;
61 {
62 v8::TryCatch try_catch(isolate);
63 if (!v8::JSON::Parse(context, v8_message_string).ToLocal(&parsed_message)) {
64 NOTREACHED();
65 return;
66 }
67 }
68 v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
69 std::vector<v8::Local<v8::Value>> args = {parsed_message, self};
70 DispatchEvent(context, &args, kOnMessageEvent);
71}
72
73void GinPort::Disconnect(v8::Local<v8::Context> context) {
74 DCHECK(!is_closed_);
75
76 v8::Isolate* isolate = context->GetIsolate();
77 v8::HandleScope handle_scope(isolate);
78 v8::Context::Scope context_scope(context);
79
80 v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
81 std::vector<v8::Local<v8::Value>> args = {self};
82 DispatchEvent(context, &args, kOnDisconnectEvent);
83
84 Invalidate(context);
85}
86
87void GinPort::SetSender(v8::Local<v8::Context> context,
88 v8::Local<v8::Value> sender) {
89 v8::Isolate* isolate = context->GetIsolate();
90 v8::HandleScope handle_scope(isolate);
91
92 v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
93 v8::Local<v8::Private> key =
94 v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
95 v8::Maybe<bool> set_result = wrapper->SetPrivate(context, key, sender);
96 DCHECK(set_result.IsJust() && set_result.FromJust());
97}
98
99void GinPort::DisconnectHandler(gin::Arguments* arguments) {
100 if (is_closed_)
101 return;
102
103 v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
104 Invalidate(context);
105}
106
107void GinPort::PostMessageHandler(gin::Arguments* arguments,
108 v8::Local<v8::Value> v8_message) {
109 v8::Isolate* isolate = arguments->isolate();
110 if (is_closed_) {
111 ThrowError(isolate, "Attempting to use a disconnected port object");
112 return;
113 }
114
115 // TODO(devlin): For some reason, we don't use the signature for
116 // Port.postMessage when evaluating the parameters. We probably should, but
117 // we don't know how many extensions that may break. It would be good to
118 // investigate, and, ideally, use the signature.
119
120 if (v8_message->IsUndefined()) {
121 // JSON.stringify won't serialized undefined (it returns undefined), but it
122 // will serialized null. We've always converted undefined to null in JS
123 // bindings, so preserve this behavior for now.
124 v8_message = v8::Null(isolate);
125 }
126
127 bool success = false;
128 v8::Local<v8::String> stringified;
129 {
130 v8::TryCatch try_catch(isolate);
131 success =
132 v8::JSON::Stringify(arguments->GetHolderCreationContext(), v8_message)
133 .ToLocal(&stringified);
134 }
135
136 std::string message;
137 if (success) {
138 message = gin::V8ToString(stringified);
139 // JSON.stringify can either fail (with unserializable objects) or can
140 // return undefined. If it returns undefined, the v8 API then coerces it to
141 // the string value "undefined". Throw an error if we were passed
142 // unserializable objects.
143 success = message != "undefined";
144 }
145
146 if (!success) {
147 ThrowError(isolate, "Illegal argument to Port.postMessage");
148 return;
149 }
150
151 delegate_->PostMessageToPort(
152 port_id_,
153 std::make_unique<Message>(
154 message, blink::WebUserGestureIndicator::IsProcessingUserGesture()));
155}
156
157std::string GinPort::GetName() {
158 return name_;
159}
160
161v8::Local<v8::Value> GinPort::GetOnDisconnectEvent(gin::Arguments* arguments) {
162 return GetEvent(arguments->GetHolderCreationContext(), kOnDisconnectEvent);
163}
164
165v8::Local<v8::Value> GinPort::GetOnMessageEvent(gin::Arguments* arguments) {
166 return GetEvent(arguments->GetHolderCreationContext(), kOnMessageEvent);
167}
168
169v8::Local<v8::Value> GinPort::GetSender(gin::Arguments* arguments) {
170 v8::Isolate* isolate = arguments->isolate();
171 v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
172 v8::Local<v8::Private> key =
173 v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, kSenderKey));
174 v8::Local<v8::Value> sender;
175 if (!wrapper->GetPrivate(arguments->GetHolderCreationContext(), key)
176 .ToLocal(&sender)) {
177 NOTREACHED();
178 return v8::Undefined(isolate);
179 }
180
181 return sender;
182}
183
184v8::Local<v8::Object> GinPort::GetEvent(v8::Local<v8::Context> context,
185 base::StringPiece event_name) {
186 DCHECK(event_name == kOnMessageEvent || event_name == kOnDisconnectEvent);
187 v8::Isolate* isolate = context->GetIsolate();
188 v8::Local<v8::Object> wrapper = GetWrapper(isolate).ToLocalChecked();
189 v8::Local<v8::Private> key =
190 v8::Private::ForApi(isolate, gin::StringToSymbol(isolate, event_name));
191 v8::Local<v8::Value> event_val;
192 if (!wrapper->GetPrivate(context, key).ToLocal(&event_val)) {
193 NOTREACHED();
194 return v8::Local<v8::Object>();
195 }
196
197 DCHECK(!event_val.IsEmpty());
198 v8::Local<v8::Object> event_object;
199 if (event_val->IsUndefined()) {
200 event_object = event_handler_->CreateAnonymousEventInstance(context);
201 v8::Maybe<bool> set_result =
202 wrapper->SetPrivate(context, key, event_object);
203 if (!set_result.IsJust() || !set_result.FromJust()) {
204 NOTREACHED();
205 return v8::Local<v8::Object>();
206 }
207 } else {
208 event_object = event_val.As<v8::Object>();
209 }
210 return event_object;
211}
212
213void GinPort::DispatchEvent(v8::Local<v8::Context> context,
214 std::vector<v8::Local<v8::Value>>* args,
215 base::StringPiece event_name) {
216 v8::Isolate* isolate = context->GetIsolate();
217 v8::Local<v8::Value> on_message = GetEvent(context, event_name);
218 EventEmitter* emitter = nullptr;
219 gin::Converter<EventEmitter*>::FromV8(isolate, on_message, &emitter);
220 CHECK(emitter);
221
222 emitter->Fire(context, args, nullptr);
223}
224
225void GinPort::Invalidate(v8::Local<v8::Context> context) {
226 is_closed_ = true;
227 event_handler_->InvalidateCustomEvent(context,
228 GetEvent(context, kOnMessageEvent));
229 event_handler_->InvalidateCustomEvent(context,
230 GetEvent(context, kOnDisconnectEvent));
231 delegate_->ClosePort(port_id_);
232}
233
234void GinPort::ThrowError(v8::Isolate* isolate, base::StringPiece error) {
235 isolate->ThrowException(
236 v8::Exception::Error(gin::StringToV8(isolate, error)));
237}
238
239} // namespace extensions