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