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"
16 #include "extensions/renderer/bindings/api_binding_util.h"
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"
20 #include "extensions/renderer/bindings/get_per_context_data.h"
21 #include "extensions/renderer/gc_callback.h"
22 #include "extensions/renderer/ipc_message_sender.h"
23 #include "extensions/renderer/message_target.h"
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"
28 #include "gin/dictionary.h"
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 
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;
46   std::string event_name;
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 {
53   static constexpr char kPerContextDataKey[] =
54       "extension_one_time_message_context_data";
55 
56   std::map<PortId, OneTimeOpener> openers;
57   std::map<PortId, OneTimeReceiver> receivers;
58   std::vector<std::unique_ptr<OneTimeMessageCallback>> pending_callbacks;
59 };
60 
61 constexpr char OneTimeMessageContextData::kPerContextDataKey[];
62 
RoutingIdForScriptContext(ScriptContext * script_context)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 
OneTimeMessageResponseHelper(const v8::FunctionCallbackInfo<v8::Value> & info)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 
77   OneTimeMessageContextData* data =
78       GetPerContextData<OneTimeMessageContextData>(context,
79                                                    kDontCreateIfMissing);
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 
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.
WillListenerReplyAsync(v8::Local<v8::Context> context,v8::MaybeLocal<v8::Value> maybe_results)101 bool WillListenerReplyAsync(v8::Local<v8::Context> context,
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 
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 
141 }  // namespace
142 
OneTimeMessageHandler(NativeExtensionBindingsSystem * bindings_system)143 OneTimeMessageHandler::OneTimeMessageHandler(
144     NativeExtensionBindingsSystem* bindings_system)
145     : bindings_system_(bindings_system) {}
~OneTimeMessageHandler()146 OneTimeMessageHandler::~OneTimeMessageHandler() {}
147 
HasPort(ScriptContext * script_context,const PortId & port_id)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 =
154       GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
155                                                    kDontCreateIfMissing);
156   if (!data)
157     return false;
158   return port_id.is_opener ? base::Contains(data->openers, port_id)
159                            : base::Contains(data->receivers, port_id);
160 }
161 
SendMessage(ScriptContext * script_context,const PortId & new_port_id,const MessageTarget & target,const std::string & method_name,const Message & message,v8::Local<v8::Function> response_callback)162 void OneTimeMessageHandler::SendMessage(
163     ScriptContext* script_context,
164     const PortId& new_port_id,
165     const MessageTarget& target,
166     const std::string& method_name,
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 =
176       GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
177                                                    kCreateIfMissing);
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();
192   ipc_sender->SendOpenMessageChannel(script_context, new_port_id, target,
193                                      method_name);
194   ipc_sender->SendPostMessageToPort(new_port_id, message);
195 
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) {
203     bool close_channel = true;
204     ipc_sender->SendCloseMessagePort(routing_id, new_port_id, close_channel);
205   }
206 }
207 
AddReceiver(ScriptContext * script_context,const PortId & target_port_id,v8::Local<v8::Object> sender,const std::string & event_name)208 void OneTimeMessageHandler::AddReceiver(ScriptContext* script_context,
209                                         const PortId& target_port_id,
210                                         v8::Local<v8::Object> sender,
211                                         const std::string& event_name) {
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 
219   OneTimeMessageContextData* data =
220       GetPerContextData<OneTimeMessageContextData>(context, kCreateIfMissing);
221   DCHECK(data);
222   DCHECK(!base::Contains(data->receivers, target_port_id));
223   OneTimeReceiver& receiver = data->receivers[target_port_id];
224   receiver.sender.Reset(isolate, sender);
225   receiver.routing_id = RoutingIdForScriptContext(script_context);
226   receiver.event_name = event_name;
227 }
228 
DeliverMessage(ScriptContext * script_context,const Message & message,const PortId & target_port_id)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 
Disconnect(ScriptContext * script_context,const PortId & port_id,const std::string & error_message)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 
DeliverMessageToReceiver(ScriptContext * script_context,const Message & message,const PortId & target_port_id)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 
263   OneTimeMessageContextData* data =
264       GetPerContextData<OneTimeMessageContextData>(context,
265                                                    kDontCreateIfMissing);
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
281   // we close the port.
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 
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 
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 
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 
322   data->pending_callbacks.push_back(std::move(callback));
323   bindings_system_->api_system()->event_handler()->FireEventInContext(
324       port.event_name, context, &args, nullptr, std::move(dispatch_callback));
325 
326   // Note: The context could be invalidated at this point!
327 
328   return handled;
329 }
330 
DeliverReplyToOpener(ScriptContext * script_context,const Message & message,const PortId & target_port_id)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 
336   v8::Local<v8::Context> v8_context = script_context->v8_context();
337   bool handled = false;
338 
339   OneTimeMessageContextData* data =
340       GetPerContextData<OneTimeMessageContextData>(v8_context,
341                                                    kDontCreateIfMissing);
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 
351   // Note: make a copy of port, since we're about to free it.
352   const OneTimeOpener port = iter->second;
353   DCHECK_NE(-1, port.request_id);
354 
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 
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 =
363       messaging_util::MessageToV8(v8_context, message);
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);
371 
372   // Note: The context could be invalidated at this point!
373 
374   return handled;
375 }
376 
DisconnectReceiver(ScriptContext * script_context,const PortId & port_id)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 
382   OneTimeMessageContextData* data =
383       GetPerContextData<OneTimeMessageContextData>(context,
384                                                    kDontCreateIfMissing);
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 
DisconnectOpener(ScriptContext * script_context,const PortId & port_id,const std::string & error_message)397 bool OneTimeMessageHandler::DisconnectOpener(ScriptContext* script_context,
398                                              const PortId& port_id,
399                                              const std::string& error_message) {
400   bool handled = false;
401 
402   v8::Local<v8::Context> v8_context = script_context->v8_context();
403   OneTimeMessageContextData* data =
404       GetPerContextData<OneTimeMessageContextData>(v8_context,
405                                                    kDontCreateIfMissing);
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;
414 
415   // Note: make a copy of port, since we're about to free it.
416   const OneTimeOpener port = iter->second;
417   DCHECK_NE(-1, port.request_id);
418 
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).
422   data->openers.erase(iter);
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 
434   return handled;
435 }
436 
OnOneTimeMessageResponse(const PortId & port_id,gin::Arguments * arguments)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();
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.
448   OneTimeMessageContextData* data =
449       GetPerContextData<OneTimeMessageContextData>(context,
450                                                    kDontCreateIfMissing);
451   if (!data)
452     return;
453 
454   auto iter = data->receivers.find(port_id);
455   if (iter == data->receivers.end())
456     return;
457 
458   int routing_id = iter->second.routing_id;
459   data->receivers.erase(iter);
460 
461   v8::Local<v8::Value> value;
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);
468 
469   std::string error;
470   std::unique_ptr<Message> message =
471       messaging_util::MessageFromV8(context, value, &error);
472   if (!message) {
473     arguments->ThrowTypeError(error);
474     return;
475   }
476   IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender();
477   ipc_sender->SendPostMessageToPort(port_id, *message);
478   bool close_channel = true;
479   ipc_sender->SendCloseMessagePort(routing_id, port_id, close_channel);
480 }
481 
OnResponseCallbackCollected(ScriptContext * script_context,const PortId & port_id)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 =
489       GetPerContextData<OneTimeMessageContextData>(script_context->v8_context(),
490                                                    kDontCreateIfMissing);
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 
OnEventFired(const PortId & port_id,v8::Local<v8::Context> context,v8::MaybeLocal<v8::Value> result)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.
518   OneTimeMessageContextData* data =
519       GetPerContextData<OneTimeMessageContextData>(context,
520                                                    kDontCreateIfMissing);
521   if (!data)
522     return;
523 
524   if (WillListenerReplyAsync(context, result))
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 
543 }  // namespace extensions
544