blob: db61703c19bd1a25280fd5dc10305ae0c216d946 [file] [log] [blame]
[email protected]e6893672014-05-01 17:29:131// Copyright 2014 The Chromium Authors. All rights reserved.
[email protected]0aa477bd2009-03-23 22:21:432// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]e6893672014-05-01 17:29:135#include "extensions/renderer/messaging_bindings.h"
[email protected]0aa477bd2009-03-23 22:21:436
avi2d124c02015-12-23 06:36:427#include <stdint.h>
8
[email protected]a672cb92009-12-29 00:28:439#include <map>
10#include <string>
11
[email protected]09e9d642013-03-14 17:00:1012#include "base/bind.h"
[email protected]204d8ee2013-07-24 07:43:0613#include "base/bind_helpers.h"
kalman70c00e242015-05-15 23:42:2714#include "base/callback.h"
[email protected]204d8ee2013-07-24 07:43:0615#include "base/message_loop/message_loop.h"
[email protected]686914f2013-04-25 04:54:5816#include "base/values.h"
mek87e0ab52015-02-13 01:18:2617#include "content/public/child/v8_value_converter.h"
vrk85dfc5c2015-01-10 00:05:3218#include "content/public/common/child_process_host.h"
rob248d6a82014-11-21 02:04:4819#include "content/public/renderer/render_frame.h"
[email protected]526476902011-10-06 20:34:0620#include "content/public/renderer/render_thread.h"
[email protected]91add6a2014-03-21 11:48:2221#include "extensions/common/api/messaging/message.h"
[email protected]fb820c02014-03-13 15:07:0822#include "extensions/common/extension_messages.h"
thestigda556af2014-11-01 00:51:3123#include "extensions/common/manifest_handlers/externally_connectable.h"
rdevlin.croninb05a80f2015-10-06 00:58:1824#include "extensions/renderer/extension_frame_helper.h"
kalman32d7af22015-07-24 05:10:5925#include "extensions/renderer/gc_callback.h"
[email protected]bcd9580f2014-04-17 19:17:5926#include "extensions/renderer/script_context.h"
27#include "extensions/renderer/script_context_set.h"
bashi7b0e3c92015-06-23 05:19:5628#include "extensions/renderer/v8_helpers.h"
mlamouri9b4c0242015-04-21 10:43:5429#include "third_party/WebKit/public/web/WebDocument.h"
rob4ae97b72014-12-10 00:16:0430#include "third_party/WebKit/public/web/WebLocalFrame.h"
[email protected]8865fea2013-10-23 01:17:2631#include "third_party/WebKit/public/web/WebScopedUserGesture.h"
[email protected]2d76a392013-12-23 08:43:4032#include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
[email protected]8865fea2013-10-23 01:17:2633#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
[email protected]82fc0f52011-09-06 23:39:2234#include "v8/include/v8.h"
[email protected]0aa477bd2009-03-23 22:21:4335
[email protected]0aa477bd2009-03-23 22:21:4336// Message passing API example (in a content script):
37// var extension =
[email protected]28c47ee92009-05-05 18:25:0138// new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
[email protected]754ea8b72013-01-08 15:10:3139// var port = runtime.connect();
[email protected]a40caa972009-04-08 18:35:3440// port.postMessage('Can you hear me now?');
41// port.onmessage.addListener(function(msg, port) {
[email protected]75e5a872009-04-02 23:56:1142// alert('response=' + msg);
43// port.postMessage('I got your reponse');
[email protected]a40caa972009-04-08 18:35:3444// });
[email protected]75e5a872009-04-02 23:56:1145
[email protected]eb7ef5f2014-02-06 09:59:1946namespace extensions {
47
bashi7b0e3c92015-06-23 05:19:5648using v8_helpers::ToV8String;
kalman33076cb2015-08-11 19:12:0749using v8_helpers::IsEmptyOrUndefied;
bashi7b0e3c92015-06-23 05:19:5650
[email protected]75e5a872009-04-02 23:56:1151namespace {
52
robd1db9f62016-05-25 11:03:1053void HasMessagePort(int port_id,
54 bool* has_port,
55 ScriptContext* script_context) {
56 if (*has_port)
57 return; // Stop checking if the port was found.
58
59 v8::Isolate* isolate = script_context->isolate();
60 v8::HandleScope handle_scope(isolate);
61
62 v8::Local<v8::Value> port_id_handle = v8::Integer::New(isolate, port_id);
63 v8::Local<v8::Value> v8_has_port =
64 script_context->module_system()->CallModuleMethod("messaging", "hasPort",
65 1, &port_id_handle);
66 if (IsEmptyOrUndefied(v8_has_port))
67 return;
68 CHECK(v8_has_port->IsBoolean());
69 if (!v8_has_port.As<v8::Boolean>()->Value())
70 return;
71 *has_port = true;
72}
73
[email protected]800f9872014-06-12 04:12:5174void DispatchOnConnectToScriptContext(
75 int target_port_id,
76 const std::string& channel_name,
rob248d6a82014-11-21 02:04:4877 const ExtensionMsg_TabConnectionInfo* source,
[email protected]800f9872014-06-12 04:12:5178 const ExtensionMsg_ExternalConnectionInfo& info,
79 const std::string& tls_channel_id,
kalman62b5911d2015-04-04 02:33:0980 bool* port_created,
[email protected]800f9872014-06-12 04:12:5181 ScriptContext* script_context) {
[email protected]800f9872014-06-12 04:12:5182 v8::Isolate* isolate = script_context->isolate();
83 v8::HandleScope handle_scope(isolate);
84
rdevlin.cronin4012b3b2016-08-31 18:36:4785 std::unique_ptr<content::V8ValueConverter> converter(
86 content::V8ValueConverter::create());
[email protected]800f9872014-06-12 04:12:5187
88 const std::string& source_url_spec = info.source_url.spec();
89 std::string target_extension_id = script_context->GetExtensionID();
90 const Extension* extension = script_context->extension();
91
tfarinaf85316f2015-04-29 17:03:4092 v8::Local<v8::Value> tab = v8::Null(isolate);
93 v8::Local<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
94 v8::Local<v8::Value> guest_process_id = v8::Undefined(isolate);
lazyboy0b46a972015-06-16 02:54:4195 v8::Local<v8::Value> guest_render_frame_routing_id = v8::Undefined(isolate);
[email protected]800f9872014-06-12 04:12:5196
97 if (extension) {
rob248d6a82014-11-21 02:04:4898 if (!source->tab.empty() && !extension->is_platform_app())
99 tab = converter->ToV8Value(&source->tab, script_context->v8_context());
[email protected]800f9872014-06-12 04:12:51100
101 ExternallyConnectableInfo* externally_connectable =
102 ExternallyConnectableInfo::Get(extension);
103 if (externally_connectable &&
104 externally_connectable->accepts_tls_channel_id) {
bashi7b0e3c92015-06-23 05:19:56105 v8::Local<v8::String> v8_tls_channel_id;
106 if (ToV8String(isolate, tls_channel_id.c_str(), &v8_tls_channel_id))
107 tls_channel_id_value = v8_tls_channel_id;
[email protected]800f9872014-06-12 04:12:51108 }
vrk85dfc5c2015-01-10 00:05:32109
lazyboy0b46a972015-06-16 02:54:41110 if (info.guest_process_id != content::ChildProcessHost::kInvalidUniqueID) {
vrk85dfc5c2015-01-10 00:05:32111 guest_process_id = v8::Integer::New(isolate, info.guest_process_id);
lazyboy0b46a972015-06-16 02:54:41112 guest_render_frame_routing_id =
113 v8::Integer::New(isolate, info.guest_render_frame_routing_id);
114 }
[email protected]800f9872014-06-12 04:12:51115 }
116
bashi7b0e3c92015-06-23 05:19:56117 v8::Local<v8::String> v8_channel_name;
118 v8::Local<v8::String> v8_source_id;
119 v8::Local<v8::String> v8_target_extension_id;
120 v8::Local<v8::String> v8_source_url_spec;
121 if (!ToV8String(isolate, channel_name.c_str(), &v8_channel_name) ||
122 !ToV8String(isolate, info.source_id.c_str(), &v8_source_id) ||
123 !ToV8String(isolate, target_extension_id.c_str(),
124 &v8_target_extension_id) ||
125 !ToV8String(isolate, source_url_spec.c_str(), &v8_source_url_spec)) {
126 NOTREACHED() << "dispatchOnConnect() passed non-string argument";
127 return;
128 }
129
tfarinaf85316f2015-04-29 17:03:40130 v8::Local<v8::Value> arguments[] = {
[email protected]800f9872014-06-12 04:12:51131 // portId
132 v8::Integer::New(isolate, target_port_id),
133 // channelName
bashi7b0e3c92015-06-23 05:19:56134 v8_channel_name,
[email protected]800f9872014-06-12 04:12:51135 // sourceTab
136 tab,
rob248d6a82014-11-21 02:04:48137 // source_frame_id
138 v8::Integer::New(isolate, source->frame_id),
vrk85dfc5c2015-01-10 00:05:32139 // guestProcessId
140 guest_process_id,
lazyboy0b46a972015-06-16 02:54:41141 // guestRenderFrameRoutingId
142 guest_render_frame_routing_id,
[email protected]800f9872014-06-12 04:12:51143 // sourceExtensionId
bashi7b0e3c92015-06-23 05:19:56144 v8_source_id,
[email protected]800f9872014-06-12 04:12:51145 // targetExtensionId
bashi7b0e3c92015-06-23 05:19:56146 v8_target_extension_id,
[email protected]800f9872014-06-12 04:12:51147 // sourceUrl
bashi7b0e3c92015-06-23 05:19:56148 v8_source_url_spec,
[email protected]800f9872014-06-12 04:12:51149 // tlsChannelId
150 tls_channel_id_value,
151 };
152
tfarinaf85316f2015-04-29 17:03:40153 v8::Local<v8::Value> retval =
[email protected]800f9872014-06-12 04:12:51154 script_context->module_system()->CallModuleMethod(
155 "messaging", "dispatchOnConnect", arraysize(arguments), arguments);
156
kalman33076cb2015-08-11 19:12:07157 if (!IsEmptyOrUndefied(retval)) {
[email protected]800f9872014-06-12 04:12:51158 CHECK(retval->IsBoolean());
bashi7b0e3c92015-06-23 05:19:56159 *port_created |= retval.As<v8::Boolean>()->Value();
[email protected]800f9872014-06-12 04:12:51160 } else {
161 LOG(ERROR) << "Empty return value from dispatchOnConnect.";
162 }
163}
164
mlamouri9b4c0242015-04-21 10:43:54165void DeliverMessageToScriptContext(const Message& message,
[email protected]800f9872014-06-12 04:12:51166 int target_port_id,
167 ScriptContext* script_context) {
kalmanfb6f10ac2014-11-06 23:55:35168 v8::Isolate* isolate = script_context->isolate();
[email protected]800f9872014-06-12 04:12:51169 v8::HandleScope handle_scope(isolate);
170
171 // Check to see whether the context has this port before bothering to create
172 // the message.
tfarinaf85316f2015-04-29 17:03:40173 v8::Local<v8::Value> port_id_handle =
[email protected]800f9872014-06-12 04:12:51174 v8::Integer::New(isolate, target_port_id);
tfarinaf85316f2015-04-29 17:03:40175 v8::Local<v8::Value> has_port =
176 script_context->module_system()->CallModuleMethod("messaging", "hasPort",
177 1, &port_id_handle);
bashi5794de272015-07-15 00:07:14178 // Could be empty/undefined if an exception was thrown.
179 // TODO(kalman): Should this be built into CallModuleMethod?
kalman33076cb2015-08-11 19:12:07180 if (IsEmptyOrUndefied(has_port))
bashi5794de272015-07-15 00:07:14181 return;
182 CHECK(has_port->IsBoolean());
bashi7b0e3c92015-06-23 05:19:56183 if (!has_port.As<v8::Boolean>()->Value())
[email protected]800f9872014-06-12 04:12:51184 return;
185
bashi7b0e3c92015-06-23 05:19:56186 v8::Local<v8::String> v8_data;
187 if (!ToV8String(isolate, message.data.c_str(), &v8_data))
188 return;
tfarinaf85316f2015-04-29 17:03:40189 std::vector<v8::Local<v8::Value>> arguments;
bashi7b0e3c92015-06-23 05:19:56190 arguments.push_back(v8_data);
[email protected]800f9872014-06-12 04:12:51191 arguments.push_back(port_id_handle);
mlamouri9b4c0242015-04-21 10:43:54192
dchengf6f80662016-04-20 20:26:04193 std::unique_ptr<blink::WebScopedUserGesture> web_user_gesture;
194 std::unique_ptr<blink::WebScopedWindowFocusAllowedIndicator>
195 allow_window_focus;
mlamouri9b4c0242015-04-21 10:43:54196 if (message.user_gesture) {
197 web_user_gesture.reset(new blink::WebScopedUserGesture);
198
199 if (script_context->web_frame()) {
200 blink::WebDocument document = script_context->web_frame()->document();
201 allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator(
202 &document));
203 }
204 }
205
[email protected]800f9872014-06-12 04:12:51206 script_context->module_system()->CallModuleMethod(
207 "messaging", "dispatchOnMessage", &arguments);
208}
209
210void DispatchOnDisconnectToScriptContext(int port_id,
211 const std::string& error_message,
212 ScriptContext* script_context) {
213 v8::Isolate* isolate = script_context->isolate();
214 v8::HandleScope handle_scope(isolate);
215
tfarinaf85316f2015-04-29 17:03:40216 std::vector<v8::Local<v8::Value>> arguments;
[email protected]800f9872014-06-12 04:12:51217 arguments.push_back(v8::Integer::New(isolate, port_id));
bashi7b0e3c92015-06-23 05:19:56218 v8::Local<v8::String> v8_error_message;
219 if (!error_message.empty())
220 ToV8String(isolate, error_message.c_str(), &v8_error_message);
221 if (!v8_error_message.IsEmpty()) {
222 arguments.push_back(v8_error_message);
[email protected]800f9872014-06-12 04:12:51223 } else {
224 arguments.push_back(v8::Null(isolate));
225 }
226
227 script_context->module_system()->CallModuleMethod(
228 "messaging", "dispatchOnDisconnect", &arguments);
229}
230
[email protected]0aa477bd2009-03-23 22:21:43231} // namespace
232
rdevlin.cronin4012b3b2016-08-31 18:36:47233MessagingBindings::MessagingBindings(ScriptContext* context)
234 : ObjectBackedNativeHandler(context), weak_ptr_factory_(this) {
235 RouteFunction("CloseChannel", base::Bind(&MessagingBindings::CloseChannel,
236 base::Unretained(this)));
237 RouteFunction("PostMessage", base::Bind(&MessagingBindings::PostMessage,
238 base::Unretained(this)));
239 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
240 RouteFunction("BindToGC", base::Bind(&MessagingBindings::BindToGC,
241 base::Unretained(this)));
robd1db9f62016-05-25 11:03:10242}
243
rdevlin.cronin4012b3b2016-08-31 18:36:47244MessagingBindings::~MessagingBindings() {}
245
246// static
robd1db9f62016-05-25 11:03:10247void MessagingBindings::ValidateMessagePort(
248 const ScriptContextSet& context_set,
249 int port_id,
250 content::RenderFrame* render_frame) {
251 int routing_id = render_frame->GetRoutingID();
252
253 bool has_port = false;
254 context_set.ForEach(render_frame,
255 base::Bind(&HasMessagePort, port_id, &has_port));
256 // Note: HasMessagePort invokes a JavaScript function. If the runtime of the
257 // extension bindings in JS have been compromised, then |render_frame| may be
258 // invalid at this point.
259
260 // A reply is only sent if the port is missing, because the port is assumed to
261 // exist unless stated otherwise.
262 if (!has_port) {
263 content::RenderThread::Get()->Send(
264 new ExtensionHostMsg_CloseMessagePort(routing_id, port_id, false));
265 }
[email protected]0aa477bd2009-03-23 22:21:43266}
[email protected]b2e86ec12011-09-15 01:59:06267
[email protected]f9db2472012-04-02 20:42:45268// static
[email protected]d710efe2013-07-30 22:25:48269void MessagingBindings::DispatchOnConnect(
[email protected]800f9872014-06-12 04:12:51270 const ScriptContextSet& context_set,
[email protected]f9db2472012-04-02 20:42:45271 int target_port_id,
272 const std::string& channel_name,
rob248d6a82014-11-21 02:04:48273 const ExtensionMsg_TabConnectionInfo& source,
[email protected]800f9872014-06-12 04:12:51274 const ExtensionMsg_ExternalConnectionInfo& info,
[email protected]8ad95b72013-10-16 02:54:11275 const std::string& tls_channel_id,
rob248d6a82014-11-21 02:04:48276 content::RenderFrame* restrict_to_render_frame) {
rob02aac0f2016-05-02 17:07:47277 int routing_id = restrict_to_render_frame
278 ? restrict_to_render_frame->GetRoutingID()
279 : MSG_ROUTING_NONE;
kalman62b5911d2015-04-04 02:33:09280 bool port_created = false;
281 context_set.ForEach(
rdevlin.croninc5d9a0ea2015-06-23 21:29:10282 info.target_id, restrict_to_render_frame,
rob248d6a82014-11-21 02:04:48283 base::Bind(&DispatchOnConnectToScriptContext, target_port_id,
kalman62b5911d2015-04-04 02:33:09284 channel_name, &source, info, tls_channel_id, &port_created));
rob02aac0f2016-05-02 17:07:47285 // Note: |restrict_to_render_frame| may have been deleted at this point!
kalman62b5911d2015-04-04 02:33:09286
rob3e2a0732016-01-06 21:22:09287 if (port_created) {
288 content::RenderThread::Get()->Send(
289 new ExtensionHostMsg_OpenMessagePort(routing_id, target_port_id));
290 } else {
291 content::RenderThread::Get()->Send(new ExtensionHostMsg_CloseMessagePort(
292 routing_id, target_port_id, false));
kalman62b5911d2015-04-04 02:33:09293 }
[email protected]f9db2472012-04-02 20:42:45294}
295
296// static
[email protected]d710efe2013-07-30 22:25:48297void MessagingBindings::DeliverMessage(
[email protected]800f9872014-06-12 04:12:51298 const ScriptContextSet& context_set,
[email protected]b2e86ec12011-09-15 01:59:06299 int target_port_id,
[email protected]8865fea2013-10-23 01:17:26300 const Message& message,
rob248d6a82014-11-21 02:04:48301 content::RenderFrame* restrict_to_render_frame) {
kalman62b5911d2015-04-04 02:33:09302 context_set.ForEach(
rdevlin.croninc5d9a0ea2015-06-23 21:29:10303 restrict_to_render_frame,
mlamouri9b4c0242015-04-21 10:43:54304 base::Bind(&DeliverMessageToScriptContext, message, target_port_id));
[email protected]b2e86ec12011-09-15 01:59:06305}
[email protected]83820d42011-11-12 22:03:11306
[email protected]f9db2472012-04-02 20:42:45307// static
[email protected]d710efe2013-07-30 22:25:48308void MessagingBindings::DispatchOnDisconnect(
[email protected]800f9872014-06-12 04:12:51309 const ScriptContextSet& context_set,
[email protected]f9db2472012-04-02 20:42:45310 int port_id,
[email protected]d6b39612013-03-08 02:33:13311 const std::string& error_message,
rob248d6a82014-11-21 02:04:48312 content::RenderFrame* restrict_to_render_frame) {
kalman62b5911d2015-04-04 02:33:09313 context_set.ForEach(
rdevlin.croninc5d9a0ea2015-06-23 21:29:10314 restrict_to_render_frame,
[email protected]800f9872014-06-12 04:12:51315 base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message));
[email protected]f9db2472012-04-02 20:42:45316}
317
rdevlin.cronin4012b3b2016-08-31 18:36:47318void MessagingBindings::PostMessage(
319 const v8::FunctionCallbackInfo<v8::Value>& args) {
320 // Arguments are (int32_t port_id, string message).
321 CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString());
322
323 int port_id = args[0].As<v8::Int32>()->Value();
324
325 content::RenderFrame* render_frame = context()->GetRenderFrame();
326 if (render_frame) {
327 render_frame->Send(new ExtensionHostMsg_PostMessage(
328 render_frame->GetRoutingID(), port_id,
329 Message(*v8::String::Utf8Value(args[1]),
330 blink::WebUserGestureIndicator::isProcessingUserGesture())));
331 }
332}
333
334void MessagingBindings::CloseChannel(
335 const v8::FunctionCallbackInfo<v8::Value>& args) {
336 // Arguments are (int32_t port_id, bool force_close).
337 CHECK_EQ(2, args.Length());
338 CHECK(args[0]->IsInt32());
339 CHECK(args[1]->IsBoolean());
340
341 int port_id = args[0].As<v8::Int32>()->Value();
342 bool force_close = args[1].As<v8::Boolean>()->Value();
343 ClosePort(port_id, force_close);
344}
345
346void MessagingBindings::BindToGC(
347 const v8::FunctionCallbackInfo<v8::Value>& args) {
348 CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() &&
349 args[2]->IsInt32());
350 int port_id = args[2].As<v8::Int32>()->Value();
351 base::Closure fallback = base::Bind(&base::DoNothing);
352 if (port_id >= 0) {
353 // TODO(robwu): Falling back to closing the port shouldn't be needed. If
354 // the script context is destroyed, then the frame has navigated. But that
355 // is already detected by the browser, so this logic is redundant. Remove
356 // this fallback (and move BindToGC out of messaging because it is also
357 // used in other places that have nothing to do with messaging...).
358 fallback = base::Bind(&MessagingBindings::ClosePort,
359 weak_ptr_factory_.GetWeakPtr(), port_id,
360 false /* force_close */);
361 }
362 // Destroys itself when the object is GC'd or context is invalidated.
363 new GCCallback(context(), args[0].As<v8::Object>(),
364 args[1].As<v8::Function>(), fallback);
365}
366
367void MessagingBindings::ClosePort(int port_id, bool force_close) {
368 // TODO(robwu): Merge this logic with CloseChannel once the TODO in BindToGC
369 // has been addressed.
370 content::RenderFrame* render_frame = context()->GetRenderFrame();
371 if (render_frame) {
372 render_frame->Send(new ExtensionHostMsg_CloseMessagePort(
373 render_frame->GetRoutingID(), port_id, force_close));
374 }
375}
376
[email protected]8fe74bf2012-08-07 21:08:42377} // namespace extensions