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