Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [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/one_time_message_handler.h" |
| 6 | |
| 7 | #include <map> |
| 8 | |
| 9 | #include "base/bind.h" |
| 10 | #include "base/callback.h" |
| 11 | #include "base/stl_util.h" |
| 12 | #include "base/supports_user_data.h" |
| 13 | #include "content/public/renderer/render_frame.h" |
| 14 | #include "extensions/common/api/messaging/message.h" |
| 15 | #include "extensions/common/api/messaging/port_id.h" |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 16 | #include "extensions/renderer/bindings/api_binding_util.h" |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 17 | #include "extensions/renderer/bindings/api_bindings_system.h" |
| 18 | #include "extensions/renderer/bindings/api_event_handler.h" |
| 19 | #include "extensions/renderer/bindings/api_request_handler.h" |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 20 | #include "extensions/renderer/bindings/get_per_context_data.h" |
Devlin Cronin | 75bd3602 | 2017-11-22 19:03:07 | [diff] [blame] | 21 | #include "extensions/renderer/gc_callback.h" |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 22 | #include "extensions/renderer/ipc_message_sender.h" |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 23 | #include "extensions/renderer/message_target.h" |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 24 | #include "extensions/renderer/messaging_util.h" |
| 25 | #include "extensions/renderer/native_extension_bindings_system.h" |
| 26 | #include "extensions/renderer/script_context.h" |
| 27 | #include "gin/arguments.h" |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 28 | #include "gin/dictionary.h" |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 29 | #include "gin/handle.h" |
| 30 | #include "gin/per_context_data.h" |
| 31 | #include "ipc/ipc_message.h" |
| 32 | |
| 33 | namespace extensions { |
| 34 | |
| 35 | namespace { |
| 36 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 37 | // An opener port in the context; i.e., the caller of runtime.sendMessage. |
| 38 | struct OneTimeOpener { |
| 39 | int request_id = -1; |
| 40 | int routing_id = MSG_ROUTING_NONE; |
| 41 | }; |
| 42 | |
| 43 | // A receiver port in the context; i.e., a listener to runtime.onMessage. |
| 44 | struct OneTimeReceiver { |
| 45 | int routing_id = MSG_ROUTING_NONE; |
Devlin Cronin | 182b089 | 2017-11-10 22:22:16 | [diff] [blame] | 46 | std::string event_name; |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 47 | v8::Global<v8::Object> sender; |
| 48 | }; |
| 49 | |
| 50 | using OneTimeMessageCallback = |
| 51 | base::OnceCallback<void(gin::Arguments* arguments)>; |
| 52 | struct OneTimeMessageContextData : public base::SupportsUserData::Data { |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 53 | static constexpr char kPerContextDataKey[] = |
| 54 | "extension_one_time_message_context_data"; |
| 55 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 56 | std::map<PortId, OneTimeOpener> openers; |
| 57 | std::map<PortId, OneTimeReceiver> receivers; |
| 58 | std::vector<std::unique_ptr<OneTimeMessageCallback>> pending_callbacks; |
| 59 | }; |
| 60 | |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 61 | constexpr char OneTimeMessageContextData::kPerContextDataKey[]; |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 62 | |
| 63 | int RoutingIdForScriptContext(ScriptContext* script_context) { |
| 64 | content::RenderFrame* render_frame = script_context->GetRenderFrame(); |
| 65 | return render_frame ? render_frame->GetRoutingID() : MSG_ROUTING_NONE; |
| 66 | } |
| 67 | |
| 68 | void OneTimeMessageResponseHelper( |
| 69 | const v8::FunctionCallbackInfo<v8::Value>& info) { |
| 70 | CHECK(info.Data()->IsExternal()); |
| 71 | |
| 72 | gin::Arguments arguments(info); |
| 73 | v8::Isolate* isolate = arguments.isolate(); |
| 74 | v8::HandleScope handle_scope(isolate); |
| 75 | v8::Local<v8::Context> context = isolate->GetCurrentContext(); |
| 76 | |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 77 | OneTimeMessageContextData* data = |
| 78 | GetPerContextData<OneTimeMessageContextData>(context, |
| 79 | kDontCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 80 | if (!data) |
| 81 | return; |
| 82 | |
| 83 | v8::Local<v8::External> external = info.Data().As<v8::External>(); |
| 84 | auto* raw_callback = static_cast<OneTimeMessageCallback*>(external->Value()); |
| 85 | auto iter = std::find_if( |
| 86 | data->pending_callbacks.begin(), data->pending_callbacks.end(), |
| 87 | [raw_callback](const std::unique_ptr<OneTimeMessageCallback>& callback) { |
| 88 | return callback.get() == raw_callback; |
| 89 | }); |
| 90 | if (iter == data->pending_callbacks.end()) |
| 91 | return; |
| 92 | |
| 93 | std::unique_ptr<OneTimeMessageCallback> callback = std::move(*iter); |
| 94 | data->pending_callbacks.erase(iter); |
| 95 | std::move(*callback).Run(&arguments); |
| 96 | } |
| 97 | |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 98 | // Called with the results of dispatching an onMessage event to listeners. |
| 99 | // Returns true if any of the listeners responded with `true`, indicating they |
| 100 | // will respond to the call asynchronously. |
| 101 | bool WillListenerReplyAsync(v8::Local<v8::Context> context, |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 102 | v8::MaybeLocal<v8::Value> maybe_results) { |
| 103 | v8::Local<v8::Value> results; |
| 104 | // |maybe_results| can be empty if the context was destroyed before the |
| 105 | // listeners were ran (or while they were running). |
| 106 | if (!maybe_results.ToLocal(&results)) |
| 107 | return false; |
| 108 | |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 109 | if (!results->IsObject()) |
| 110 | return false; |
| 111 | |
| 112 | // Suppress any script errors, but bail out if they happen (in theory, we |
| 113 | // shouldn't have any). |
| 114 | v8::Isolate* isolate = context->GetIsolate(); |
| 115 | v8::TryCatch try_catch(isolate); |
| 116 | // We expect results in the form of an object with an array of results as |
| 117 | // a `results` property. |
| 118 | v8::Local<v8::Value> results_property; |
| 119 | if (!results.As<v8::Object>() |
| 120 | ->Get(context, gin::StringToSymbol(isolate, "results")) |
| 121 | .ToLocal(&results_property) || |
| 122 | !results_property->IsArray()) { |
| 123 | return false; |
| 124 | } |
| 125 | |
| 126 | // Check if any of the results is `true`. |
| 127 | v8::Local<v8::Array> array = results_property.As<v8::Array>(); |
| 128 | uint32_t length = array->Length(); |
| 129 | for (uint32_t i = 0; i < length; ++i) { |
| 130 | v8::Local<v8::Value> val; |
| 131 | if (!array->Get(context, i).ToLocal(&val)) |
| 132 | return false; |
| 133 | |
| 134 | if (val->IsTrue()) |
| 135 | return true; |
| 136 | } |
| 137 | |
| 138 | return false; |
| 139 | } |
| 140 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 141 | } // namespace |
| 142 | |
| 143 | OneTimeMessageHandler::OneTimeMessageHandler( |
| 144 | NativeExtensionBindingsSystem* bindings_system) |
Jeremy Roman | 9fc2de6 | 2019-07-12 14:15:03 | [diff] [blame] | 145 | : bindings_system_(bindings_system) {} |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 146 | OneTimeMessageHandler::~OneTimeMessageHandler() {} |
| 147 | |
| 148 | bool OneTimeMessageHandler::HasPort(ScriptContext* script_context, |
| 149 | const PortId& port_id) { |
| 150 | v8::Isolate* isolate = script_context->isolate(); |
| 151 | v8::HandleScope handle_scope(isolate); |
| 152 | |
| 153 | OneTimeMessageContextData* data = |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 154 | GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(), |
| 155 | kDontCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 156 | if (!data) |
| 157 | return false; |
Jan Wilken Dörrie | 0fd53a2 | 2019-06-07 09:55:46 | [diff] [blame] | 158 | return port_id.is_opener ? base::Contains(data->openers, port_id) |
| 159 | : base::Contains(data->receivers, port_id); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 160 | } |
| 161 | |
| 162 | void OneTimeMessageHandler::SendMessage( |
| 163 | ScriptContext* script_context, |
| 164 | const PortId& new_port_id, |
Devlin Cronin | d4532eb | 2017-10-06 20:26:16 | [diff] [blame] | 165 | const MessageTarget& target, |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 166 | const std::string& method_name, |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 167 | const Message& message, |
| 168 | v8::Local<v8::Function> response_callback) { |
| 169 | v8::Isolate* isolate = script_context->isolate(); |
| 170 | v8::HandleScope handle_scope(isolate); |
| 171 | |
| 172 | DCHECK(new_port_id.is_opener); |
| 173 | DCHECK_EQ(script_context->context_id(), new_port_id.context_id); |
| 174 | |
| 175 | OneTimeMessageContextData* data = |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 176 | GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(), |
| 177 | kCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 178 | DCHECK(data); |
| 179 | |
| 180 | bool wants_response = !response_callback.IsEmpty(); |
| 181 | int routing_id = RoutingIdForScriptContext(script_context); |
| 182 | if (wants_response) { |
| 183 | int request_id = |
| 184 | bindings_system_->api_system()->request_handler()->AddPendingRequest( |
| 185 | script_context->v8_context(), response_callback); |
| 186 | OneTimeOpener& port = data->openers[new_port_id]; |
| 187 | port.request_id = request_id; |
| 188 | port.routing_id = routing_id; |
| 189 | } |
| 190 | |
| 191 | IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender(); |
Devlin Cronin | d4532eb | 2017-10-06 20:26:16 | [diff] [blame] | 192 | ipc_sender->SendOpenMessageChannel(script_context, new_port_id, target, |
Nick Harper | 41374d5 | 2020-01-30 22:36:47 | [diff] [blame] | 193 | method_name); |
Istiaque Ahmed | 39619d8 | 2019-02-06 17:36:04 | [diff] [blame] | 194 | ipc_sender->SendPostMessageToPort(new_port_id, message); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 195 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 196 | // If the sender doesn't provide a response callback, we can immediately |
| 197 | // close the channel. Note: we only do this for extension messages, not |
| 198 | // native apps. |
| 199 | // TODO(devlin): This is because of some subtle ordering in the browser side, |
| 200 | // where closing the channel after sending the message causes things to be |
| 201 | // destroyed in the wrong order. That would be nice to fix. |
| 202 | if (!wants_response && target.type != MessageTarget::NATIVE_APP) { |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 203 | bool close_channel = true; |
| 204 | ipc_sender->SendCloseMessagePort(routing_id, new_port_id, close_channel); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | void OneTimeMessageHandler::AddReceiver(ScriptContext* script_context, |
| 209 | const PortId& target_port_id, |
| 210 | v8::Local<v8::Object> sender, |
Devlin Cronin | 182b089 | 2017-11-10 22:22:16 | [diff] [blame] | 211 | const std::string& event_name) { |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 212 | DCHECK(!target_port_id.is_opener); |
| 213 | DCHECK_NE(script_context->context_id(), target_port_id.context_id); |
| 214 | |
| 215 | v8::Isolate* isolate = script_context->isolate(); |
| 216 | v8::HandleScope handle_scope(isolate); |
| 217 | v8::Local<v8::Context> context = script_context->v8_context(); |
| 218 | |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 219 | OneTimeMessageContextData* data = |
| 220 | GetPerContextData<OneTimeMessageContextData>(context, kCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 221 | DCHECK(data); |
Jan Wilken Dörrie | 0fd53a2 | 2019-06-07 09:55:46 | [diff] [blame] | 222 | DCHECK(!base::Contains(data->receivers, target_port_id)); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 223 | OneTimeReceiver& receiver = data->receivers[target_port_id]; |
| 224 | receiver.sender.Reset(isolate, sender); |
| 225 | receiver.routing_id = RoutingIdForScriptContext(script_context); |
Devlin Cronin | 182b089 | 2017-11-10 22:22:16 | [diff] [blame] | 226 | receiver.event_name = event_name; |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 227 | } |
| 228 | |
| 229 | bool OneTimeMessageHandler::DeliverMessage(ScriptContext* script_context, |
| 230 | const Message& message, |
| 231 | const PortId& target_port_id) { |
| 232 | v8::Isolate* isolate = script_context->isolate(); |
| 233 | v8::HandleScope handle_scope(isolate); |
| 234 | |
| 235 | return target_port_id.is_opener |
| 236 | ? DeliverReplyToOpener(script_context, message, target_port_id) |
| 237 | : DeliverMessageToReceiver(script_context, message, |
| 238 | target_port_id); |
| 239 | } |
| 240 | |
| 241 | bool OneTimeMessageHandler::Disconnect(ScriptContext* script_context, |
| 242 | const PortId& port_id, |
| 243 | const std::string& error_message) { |
| 244 | v8::Isolate* isolate = script_context->isolate(); |
| 245 | v8::HandleScope handle_scope(isolate); |
| 246 | |
| 247 | return port_id.is_opener |
| 248 | ? DisconnectOpener(script_context, port_id, error_message) |
| 249 | : DisconnectReceiver(script_context, port_id); |
| 250 | } |
| 251 | |
| 252 | bool OneTimeMessageHandler::DeliverMessageToReceiver( |
| 253 | ScriptContext* script_context, |
| 254 | const Message& message, |
| 255 | const PortId& target_port_id) { |
| 256 | DCHECK(!target_port_id.is_opener); |
| 257 | |
| 258 | v8::Isolate* isolate = script_context->isolate(); |
| 259 | v8::Local<v8::Context> context = script_context->v8_context(); |
| 260 | |
| 261 | bool handled = false; |
| 262 | |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 263 | OneTimeMessageContextData* data = |
| 264 | GetPerContextData<OneTimeMessageContextData>(context, |
| 265 | kDontCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 266 | if (!data) |
| 267 | return handled; |
| 268 | |
| 269 | auto iter = data->receivers.find(target_port_id); |
| 270 | if (iter == data->receivers.end()) |
| 271 | return handled; |
| 272 | |
| 273 | handled = true; |
| 274 | OneTimeReceiver& port = iter->second; |
| 275 | |
| 276 | // This port is a receiver, so we invoke the onMessage event and provide a |
| 277 | // callback through which the port can respond. The port stays open until |
| 278 | // we receive a response. |
| 279 | // TODO(devlin): With chrome.runtime.sendMessage, we actually require that a |
| 280 | // listener return `true` if they intend to respond asynchronously; otherwise |
Devlin Cronin | 75bd3602 | 2017-11-22 19:03:07 | [diff] [blame] | 281 | // we close the port. |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 282 | auto callback = std::make_unique<OneTimeMessageCallback>( |
| 283 | base::Bind(&OneTimeMessageHandler::OnOneTimeMessageResponse, |
| 284 | weak_factory_.GetWeakPtr(), target_port_id)); |
| 285 | v8::Local<v8::External> external = v8::External::New(isolate, callback.get()); |
| 286 | v8::Local<v8::Function> response_function; |
| 287 | |
| 288 | if (!v8::Function::New(context, &OneTimeMessageResponseHelper, external) |
| 289 | .ToLocal(&response_function)) { |
| 290 | NOTREACHED(); |
| 291 | return handled; |
| 292 | } |
| 293 | |
Devlin Cronin | 75bd3602 | 2017-11-22 19:03:07 | [diff] [blame] | 294 | // We shouldn't need to monitor context invalidation here. We store the ports |
| 295 | // for the context in PerContextData (cleaned up on context destruction), and |
| 296 | // the browser watches for frame navigation or destruction, and cleans up |
| 297 | // orphaned channels. |
| 298 | base::Closure on_context_invalidated; |
| 299 | |
| 300 | new GCCallback( |
| 301 | script_context, response_function, |
| 302 | base::Bind(&OneTimeMessageHandler::OnResponseCallbackCollected, |
| 303 | weak_factory_.GetWeakPtr(), script_context, target_port_id), |
| 304 | base::Closure()); |
| 305 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 306 | v8::HandleScope handle_scope(isolate); |
| 307 | v8::Local<v8::Value> v8_message = |
| 308 | messaging_util::MessageToV8(context, message); |
| 309 | v8::Local<v8::Object> v8_sender = port.sender.Get(isolate); |
| 310 | std::vector<v8::Local<v8::Value>> args = {v8_message, v8_sender, |
| 311 | response_function}; |
| 312 | |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 313 | JSRunner::ResultCallback dispatch_callback; |
| 314 | // For runtime.onMessage, we require that the listener return `true` if they |
| 315 | // intend to respond asynchronously. Check the results of the listeners. |
| 316 | if (port.event_name == messaging_util::kOnMessageEvent) { |
| 317 | dispatch_callback = |
| 318 | base::BindOnce(&OneTimeMessageHandler::OnEventFired, |
| 319 | weak_factory_.GetWeakPtr(), target_port_id); |
| 320 | } |
| 321 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 322 | data->pending_callbacks.push_back(std::move(callback)); |
| 323 | bindings_system_->api_system()->event_handler()->FireEventInContext( |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 324 | port.event_name, context, &args, nullptr, std::move(dispatch_callback)); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 325 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 326 | // Note: The context could be invalidated at this point! |
| 327 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 328 | return handled; |
| 329 | } |
| 330 | |
| 331 | bool OneTimeMessageHandler::DeliverReplyToOpener(ScriptContext* script_context, |
| 332 | const Message& message, |
| 333 | const PortId& target_port_id) { |
| 334 | DCHECK(target_port_id.is_opener); |
| 335 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 336 | v8::Local<v8::Context> v8_context = script_context->v8_context(); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 337 | bool handled = false; |
| 338 | |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 339 | OneTimeMessageContextData* data = |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 340 | GetPerContextData<OneTimeMessageContextData>(v8_context, |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 341 | kDontCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 342 | if (!data) |
| 343 | return handled; |
| 344 | |
| 345 | auto iter = data->openers.find(target_port_id); |
| 346 | if (iter == data->openers.end()) |
| 347 | return handled; |
| 348 | |
| 349 | handled = true; |
| 350 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 351 | // Note: make a copy of port, since we're about to free it. |
| 352 | const OneTimeOpener port = iter->second; |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 353 | DCHECK_NE(-1, port.request_id); |
| 354 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 355 | // We erase the opener now, since delivering the reply can cause JS to run, |
| 356 | // which could either invalidate the context or modify the |openers| |
| 357 | // collection (e.g., by sending another message). |
| 358 | data->openers.erase(iter); |
| 359 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 360 | // This port was the opener, so the message is the response from the |
| 361 | // receiver. Invoke the callback and close the message port. |
| 362 | v8::Local<v8::Value> v8_message = |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 363 | messaging_util::MessageToV8(v8_context, message); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 364 | std::vector<v8::Local<v8::Value>> args = {v8_message}; |
| 365 | bindings_system_->api_system()->request_handler()->CompleteRequest( |
| 366 | port.request_id, args, std::string()); |
| 367 | |
| 368 | bool close_channel = true; |
| 369 | bindings_system_->GetIPCMessageSender()->SendCloseMessagePort( |
| 370 | port.routing_id, target_port_id, close_channel); |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 371 | |
| 372 | // Note: The context could be invalidated at this point! |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 373 | |
| 374 | return handled; |
| 375 | } |
| 376 | |
| 377 | bool OneTimeMessageHandler::DisconnectReceiver(ScriptContext* script_context, |
| 378 | const PortId& port_id) { |
| 379 | v8::Local<v8::Context> context = script_context->v8_context(); |
| 380 | bool handled = false; |
| 381 | |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 382 | OneTimeMessageContextData* data = |
| 383 | GetPerContextData<OneTimeMessageContextData>(context, |
| 384 | kDontCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 385 | if (!data) |
| 386 | return handled; |
| 387 | |
| 388 | auto iter = data->receivers.find(port_id); |
| 389 | if (iter == data->receivers.end()) |
| 390 | return handled; |
| 391 | |
| 392 | handled = true; |
| 393 | data->receivers.erase(iter); |
| 394 | return handled; |
| 395 | } |
| 396 | |
| 397 | bool OneTimeMessageHandler::DisconnectOpener(ScriptContext* script_context, |
| 398 | const PortId& port_id, |
| 399 | const std::string& error_message) { |
| 400 | bool handled = false; |
| 401 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 402 | v8::Local<v8::Context> v8_context = script_context->v8_context(); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 403 | OneTimeMessageContextData* data = |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 404 | GetPerContextData<OneTimeMessageContextData>(v8_context, |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 405 | kDontCreateIfMissing); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 406 | if (!data) |
| 407 | return handled; |
| 408 | |
| 409 | auto iter = data->openers.find(port_id); |
| 410 | if (iter == data->openers.end()) |
| 411 | return handled; |
| 412 | |
| 413 | handled = true; |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 414 | |
| 415 | // Note: make a copy of port, since we're about to free it. |
| 416 | const OneTimeOpener port = iter->second; |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 417 | DCHECK_NE(-1, port.request_id); |
| 418 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 419 | // We erase the opener now, since delivering the reply can cause JS to run, |
| 420 | // which could either invalidate the context or modify the |openers| |
| 421 | // collection (e.g., by sending another message). |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 422 | data->openers.erase(iter); |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 423 | |
| 424 | bindings_system_->api_system()->request_handler()->CompleteRequest( |
| 425 | port.request_id, std::vector<v8::Local<v8::Value>>(), |
| 426 | // If the browser doesn't supply an error message, we supply a generic |
| 427 | // one. |
| 428 | error_message.empty() |
| 429 | ? "The message port closed before a response was received." |
| 430 | : error_message); |
| 431 | |
| 432 | // Note: The context could be invalidated at this point! |
| 433 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 434 | return handled; |
| 435 | } |
| 436 | |
| 437 | void OneTimeMessageHandler::OnOneTimeMessageResponse( |
| 438 | const PortId& port_id, |
| 439 | gin::Arguments* arguments) { |
| 440 | v8::Isolate* isolate = arguments->isolate(); |
| 441 | v8::Local<v8::Context> context = isolate->GetCurrentContext(); |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 442 | |
| 443 | // The listener may try replying after the context or the channel has been |
| 444 | // closed. Fail gracefully. |
| 445 | // TODO(devlin): At least in the case of the channel being closed (e.g. |
| 446 | // because the listener did not return `true`), it might be good to surface an |
| 447 | // error. |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 448 | OneTimeMessageContextData* data = |
| 449 | GetPerContextData<OneTimeMessageContextData>(context, |
| 450 | kDontCreateIfMissing); |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 451 | if (!data) |
| 452 | return; |
| 453 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 454 | auto iter = data->receivers.find(port_id); |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 455 | if (iter == data->receivers.end()) |
| 456 | return; |
| 457 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 458 | int routing_id = iter->second.routing_id; |
| 459 | data->receivers.erase(iter); |
| 460 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 461 | v8::Local<v8::Value> value; |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 462 | // We allow omitting the message argument (e.g., sendMessage()). Default the |
| 463 | // value to undefined. |
| 464 | if (arguments->Length() > 0) |
| 465 | CHECK(arguments->GetNext(&value)); |
| 466 | else |
| 467 | value = v8::Undefined(isolate); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 468 | |
Devlin Cronin | fe7aae6 | 2017-11-16 03:49:55 | [diff] [blame] | 469 | std::string error; |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 470 | std::unique_ptr<Message> message = |
Devlin Cronin | fe7aae6 | 2017-11-16 03:49:55 | [diff] [blame] | 471 | messaging_util::MessageFromV8(context, value, &error); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 472 | if (!message) { |
Devlin Cronin | fe7aae6 | 2017-11-16 03:49:55 | [diff] [blame] | 473 | arguments->ThrowTypeError(error); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 474 | return; |
| 475 | } |
| 476 | IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender(); |
Istiaque Ahmed | 39619d8 | 2019-02-06 17:36:04 | [diff] [blame] | 477 | ipc_sender->SendPostMessageToPort(port_id, *message); |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 478 | bool close_channel = true; |
| 479 | ipc_sender->SendCloseMessagePort(routing_id, port_id, close_channel); |
| 480 | } |
| 481 | |
Devlin Cronin | 75bd3602 | 2017-11-22 19:03:07 | [diff] [blame] | 482 | void OneTimeMessageHandler::OnResponseCallbackCollected( |
| 483 | ScriptContext* script_context, |
| 484 | const PortId& port_id) { |
| 485 | // Note: we know |script_context| is still valid because the GC callback won't |
| 486 | // be called after context invalidation. |
| 487 | v8::HandleScope handle_scope(script_context->isolate()); |
| 488 | OneTimeMessageContextData* data = |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 489 | GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(), |
| 490 | kDontCreateIfMissing); |
Devlin Cronin | 75bd3602 | 2017-11-22 19:03:07 | [diff] [blame] | 491 | // ScriptContext invalidation and PerContextData cleanup happen "around" the |
| 492 | // same time, but there aren't strict guarantees about ordering. It's possible |
| 493 | // the data was collected. |
| 494 | if (!data) |
| 495 | return; |
| 496 | |
| 497 | auto iter = data->receivers.find(port_id); |
| 498 | // The channel may already be closed (if the receiver replied before the reply |
| 499 | // callback was collected). |
| 500 | if (iter == data->receivers.end()) |
| 501 | return; |
| 502 | |
| 503 | int routing_id = iter->second.routing_id; |
| 504 | data->receivers.erase(iter); |
| 505 | |
| 506 | // Close the message port. There's no way to send a reply anymore. Don't |
| 507 | // close the channel because another listener may reply. |
| 508 | IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender(); |
| 509 | bool close_channel = false; |
| 510 | ipc_sender->SendCloseMessagePort(routing_id, port_id, close_channel); |
| 511 | } |
| 512 | |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 513 | void OneTimeMessageHandler::OnEventFired(const PortId& port_id, |
| 514 | v8::Local<v8::Context> context, |
| 515 | v8::MaybeLocal<v8::Value> result) { |
| 516 | // The context could be tearing down by the time the event is fully |
| 517 | // dispatched. |
Jens Widell | ece0eae | 2018-01-11 20:37:05 | [diff] [blame] | 518 | OneTimeMessageContextData* data = |
| 519 | GetPerContextData<OneTimeMessageContextData>(context, |
| 520 | kDontCreateIfMissing); |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 521 | if (!data) |
| 522 | return; |
| 523 | |
Devlin Cronin | b15f7f0 | 2018-01-31 19:37:32 | [diff] [blame] | 524 | if (WillListenerReplyAsync(context, result)) |
Devlin Cronin | e086c76 | 2017-12-21 15:48:43 | [diff] [blame] | 525 | return; // The listener will reply later; leave the channel open. |
| 526 | |
| 527 | auto iter = data->receivers.find(port_id); |
| 528 | // The channel may already be closed (if the listener replied). |
| 529 | if (iter == data->receivers.end()) |
| 530 | return; |
| 531 | |
| 532 | int routing_id = iter->second.routing_id; |
| 533 | data->receivers.erase(iter); |
| 534 | |
| 535 | // The listener did not reply and did not return `true` from any of its |
| 536 | // listeners. Close the message port. Don't close the channel because another |
| 537 | // listener (in a separate context) may reply. |
| 538 | IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender(); |
| 539 | bool close_channel = false; |
| 540 | ipc_sender->SendCloseMessagePort(routing_id, port_id, close_channel); |
| 541 | } |
| 542 | |
Devlin Cronin | 0b87567 | 2017-10-06 00:49:21 | [diff] [blame] | 543 | } // namespace extensions |