blob: a118fd0de6b8bfc2a36a243b352c9fddec66683c [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"
rdevlin.cronindbbe69e72016-09-10 00:48:0215#include "base/callback_helpers.h"
16#include "base/lazy_instance.h"
fdorayba121422016-12-23 19:51:4817#include "base/memory/ptr_util.h"
rdevlin.cronindbbe69e72016-09-10 00:48:0218#include "base/metrics/histogram_macros.h"
[email protected]686914f2013-04-25 04:54:5819#include "base/values.h"
rob248d6a82014-11-21 02:04:4820#include "content/public/renderer/render_frame.h"
[email protected]91add6a2014-03-21 11:48:2221#include "extensions/common/api/messaging/message.h"
rdevlin.cronin8e744a42016-12-06 18:40:2722#include "extensions/common/api/messaging/port_id.h"
[email protected]fb820c02014-03-13 15:07:0823#include "extensions/common/extension_messages.h"
rdevlin.croninb05a80f2015-10-06 00:58:1824#include "extensions/renderer/extension_frame_helper.h"
rdevlin.cronindbbe69e72016-09-10 00:48:0225#include "extensions/renderer/extension_port.h"
kalman32d7af22015-07-24 05:10:5926#include "extensions/renderer/gc_callback.h"
[email protected]bcd9580f2014-04-17 19:17:5927#include "extensions/renderer/script_context.h"
28#include "extensions/renderer/script_context_set.h"
bashi7b0e3c92015-06-23 05:19:5629#include "extensions/renderer/v8_helpers.h"
[email protected]8865fea2013-10-23 01:17:2630#include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
[email protected]82fc0f52011-09-06 23:39:2231#include "v8/include/v8.h"
[email protected]0aa477bd2009-03-23 22:21:4332
[email protected]0aa477bd2009-03-23 22:21:4333// Message passing API example (in a content script):
[email protected]754ea8b72013-01-08 15:10:3134// var port = runtime.connect();
[email protected]a40caa972009-04-08 18:35:3435// port.postMessage('Can you hear me now?');
36// port.onmessage.addListener(function(msg, port) {
[email protected]75e5a872009-04-02 23:56:1137// alert('response=' + msg);
38// port.postMessage('I got your reponse');
[email protected]a40caa972009-04-08 18:35:3439// });
[email protected]75e5a872009-04-02 23:56:1140
[email protected]eb7ef5f2014-02-06 09:59:1941namespace extensions {
42
bashi7b0e3c92015-06-23 05:19:5643using v8_helpers::ToV8String;
bashi7b0e3c92015-06-23 05:19:5644
[email protected]75e5a872009-04-02 23:56:1145namespace {
46
rdevlin.cronindbbe69e72016-09-10 00:48:0247// A global map between ScriptContext and MessagingBindings.
scottmg5e65e3a2017-03-08 08:48:4648base::LazyInstance<std::map<ScriptContext*, MessagingBindings*>>::
49 DestructorAtExit g_messaging_map = LAZY_INSTANCE_INITIALIZER;
rdevlin.cronindbbe69e72016-09-10 00:48:0250
[email protected]0aa477bd2009-03-23 22:21:4351} // namespace
52
rdevlin.cronin4012b3b2016-08-31 18:36:4753MessagingBindings::MessagingBindings(ScriptContext* context)
rdevlin.cronin8e744a42016-12-06 18:40:2754 : ObjectBackedNativeHandler(context),
55 context_id_(base::UnguessableToken::Create()),
56 weak_ptr_factory_(this) {
rdevlin.cronindbbe69e72016-09-10 00:48:0257 g_messaging_map.Get()[context] = this;
rdevlin.cronin4012b3b2016-08-31 18:36:4758 RouteFunction("CloseChannel", base::Bind(&MessagingBindings::CloseChannel,
59 base::Unretained(this)));
60 RouteFunction("PostMessage", base::Bind(&MessagingBindings::PostMessage,
61 base::Unretained(this)));
62 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
63 RouteFunction("BindToGC", base::Bind(&MessagingBindings::BindToGC,
64 base::Unretained(this)));
rdevlin.cronindbbe69e72016-09-10 00:48:0265 RouteFunction("OpenChannelToExtension", "runtime.connect",
66 base::Bind(&MessagingBindings::OpenChannelToExtension,
67 base::Unretained(this)));
68 RouteFunction("OpenChannelToNativeApp", "runtime.connectNative",
69 base::Bind(&MessagingBindings::OpenChannelToNativeApp,
70 base::Unretained(this)));
71 RouteFunction(
72 "OpenChannelToTab",
73 base::Bind(&MessagingBindings::OpenChannelToTab, base::Unretained(this)));
robd1db9f62016-05-25 11:03:1074}
75
rdevlin.cronindbbe69e72016-09-10 00:48:0276MessagingBindings::~MessagingBindings() {
77 g_messaging_map.Get().erase(context());
rdevlin.cronin8e744a42016-12-06 18:40:2778 if (num_extension_ports_ > 0) {
rdevlin.cronin69424802016-11-14 22:41:5679 UMA_HISTOGRAM_COUNTS_1000(
rdevlin.cronin8e744a42016-12-06 18:40:2780 "Extensions.Messaging.ExtensionPortsCreated.Total", next_js_id_);
rdevlin.cronin69424802016-11-14 22:41:5681 }
rdevlin.cronindbbe69e72016-09-10 00:48:0282}
rdevlin.cronin4012b3b2016-08-31 18:36:4783
84// static
Devlin Cronin525e2fd2017-09-12 01:19:5585MessagingBindings* MessagingBindings::ForContext(ScriptContext* context) {
86 return g_messaging_map.Get()[context];
[email protected]f9db2472012-04-02 20:42:4587}
88
rdevlin.cronin8e744a42016-12-06 18:40:2789ExtensionPort* MessagingBindings::GetPortWithId(const PortId& id) {
rdevlin.cronindbbe69e72016-09-10 00:48:0290 for (const auto& key_value : ports_) {
rdevlin.cronin8e744a42016-12-06 18:40:2791 if (key_value.second->id() == id)
rdevlin.cronindbbe69e72016-09-10 00:48:0292 return key_value.second.get();
93 }
94 return nullptr;
95}
96
rdevlin.cronin8e744a42016-12-06 18:40:2797ExtensionPort* MessagingBindings::CreateNewPortWithId(const PortId& id) {
98 int js_id = GetNextJsId();
Jeremy Roman16529d0e2017-08-24 18:13:4799 auto port = std::make_unique<ExtensionPort>(context(), id, js_id);
rdevlin.cronin8e744a42016-12-06 18:40:27100 return ports_.insert(std::make_pair(js_id, std::move(port)))
101 .first->second.get();
rdevlin.cronindbbe69e72016-09-10 00:48:02102}
103
rdevlin.cronin4012b3b2016-08-31 18:36:47104void MessagingBindings::PostMessage(
105 const v8::FunctionCallbackInfo<v8::Value>& args) {
106 // Arguments are (int32_t port_id, string message).
rdevlin.cronindbbe69e72016-09-10 00:48:02107 CHECK(args.Length() == 2);
108 CHECK(args[0]->IsInt32());
109 CHECK(args[1]->IsString());
rdevlin.cronin4012b3b2016-08-31 18:36:47110
rdevlin.cronin8e744a42016-12-06 18:40:27111 int js_port_id = args[0].As<v8::Int32>()->Value();
112 auto iter = ports_.find(js_port_id);
rdevlin.cronindbbe69e72016-09-10 00:48:02113 if (iter != ports_.end()) {
Jeremy Roman16529d0e2017-08-24 18:13:47114 iter->second->PostExtensionMessage(std::make_unique<Message>(
rdevlin.cronindbbe69e72016-09-10 00:48:02115 *v8::String::Utf8Value(args[1]),
Blink Reformat1c4d759e2017-04-09 16:34:54116 blink::WebUserGestureIndicator::IsProcessingUserGesture()));
rdevlin.cronin4012b3b2016-08-31 18:36:47117 }
118}
119
120void MessagingBindings::CloseChannel(
121 const v8::FunctionCallbackInfo<v8::Value>& args) {
122 // Arguments are (int32_t port_id, bool force_close).
123 CHECK_EQ(2, args.Length());
124 CHECK(args[0]->IsInt32());
125 CHECK(args[1]->IsBoolean());
126
rdevlin.cronin8e744a42016-12-06 18:40:27127 int js_port_id = args[0].As<v8::Int32>()->Value();
rdevlin.cronin4012b3b2016-08-31 18:36:47128 bool force_close = args[1].As<v8::Boolean>()->Value();
rdevlin.cronin8e744a42016-12-06 18:40:27129 ClosePort(js_port_id, force_close);
rdevlin.cronin4012b3b2016-08-31 18:36:47130}
131
132void MessagingBindings::BindToGC(
133 const v8::FunctionCallbackInfo<v8::Value>& args) {
rdevlin.cronindbbe69e72016-09-10 00:48:02134 CHECK(args.Length() == 3);
135 CHECK(args[0]->IsObject());
136 CHECK(args[1]->IsFunction());
137 CHECK(args[2]->IsInt32());
rdevlin.cronin8e744a42016-12-06 18:40:27138 int js_port_id = args[2].As<v8::Int32>()->Value();
rdevlin.cronin4012b3b2016-08-31 18:36:47139 base::Closure fallback = base::Bind(&base::DoNothing);
rdevlin.cronin8e744a42016-12-06 18:40:27140 if (js_port_id >= 0) {
rdevlin.cronin4012b3b2016-08-31 18:36:47141 // TODO(robwu): Falling back to closing the port shouldn't be needed. If
142 // the script context is destroyed, then the frame has navigated. But that
143 // is already detected by the browser, so this logic is redundant. Remove
144 // this fallback (and move BindToGC out of messaging because it is also
145 // used in other places that have nothing to do with messaging...).
146 fallback = base::Bind(&MessagingBindings::ClosePort,
rdevlin.cronin8e744a42016-12-06 18:40:27147 weak_ptr_factory_.GetWeakPtr(), js_port_id,
rdevlin.cronin4012b3b2016-08-31 18:36:47148 false /* force_close */);
149 }
150 // Destroys itself when the object is GC'd or context is invalidated.
151 new GCCallback(context(), args[0].As<v8::Object>(),
152 args[1].As<v8::Function>(), fallback);
153}
154
rdevlin.cronindbbe69e72016-09-10 00:48:02155void MessagingBindings::OpenChannelToExtension(
156 const v8::FunctionCallbackInfo<v8::Value>& args) {
157 content::RenderFrame* render_frame = context()->GetRenderFrame();
158 if (!render_frame)
159 return;
160
161 // The Javascript code should validate/fill the arguments.
162 CHECK_EQ(args.Length(), 3);
163 CHECK(args[0]->IsString());
164 CHECK(args[1]->IsString());
165 CHECK(args[2]->IsBoolean());
166
rdevlin.cronin8e744a42016-12-06 18:40:27167 int js_id = GetNextJsId();
168 PortId port_id(context_id_, js_id, true);
Jeremy Roman16529d0e2017-08-24 18:13:47169 ports_[js_id] = std::make_unique<ExtensionPort>(context(), port_id, js_id);
rdevlin.cronindbbe69e72016-09-10 00:48:02170
171 ExtensionMsg_ExternalConnectionInfo info;
172 // For messaging APIs, hosted apps should be considered a web page so hide
173 // its extension ID.
174 const Extension* extension = context()->extension();
175 if (extension && !extension->is_hosted_app())
176 info.source_id = extension->id();
177
178 info.target_id = *v8::String::Utf8Value(args[0]);
179 info.source_url = context()->url();
180 std::string channel_name = *v8::String::Utf8Value(args[1]);
181 // TODO(devlin): Why is this not part of info?
182 bool include_tls_channel_id =
183 args.Length() > 2 ? args[2]->BooleanValue() : false;
184
rdevlin.cronin8e744a42016-12-06 18:40:27185 {
186 SCOPED_UMA_HISTOGRAM_TIMER(
187 "Extensions.Messaging.SetPortIdTime.Extension");
188 render_frame->Send(new ExtensionHostMsg_OpenChannelToExtension(
189 render_frame->GetRoutingID(), info, channel_name,
190 include_tls_channel_id, port_id));
rdevlin.cronin69424802016-11-14 22:41:56191 }
rdevlin.cronindbbe69e72016-09-10 00:48:02192
rdevlin.cronin8e744a42016-12-06 18:40:27193 ++num_extension_ports_;
194 args.GetReturnValue().Set(static_cast<int32_t>(js_id));
rdevlin.cronindbbe69e72016-09-10 00:48:02195}
196
197void MessagingBindings::OpenChannelToNativeApp(
198 const v8::FunctionCallbackInfo<v8::Value>& args) {
199 // The Javascript code should validate/fill the arguments.
200 CHECK_EQ(args.Length(), 1);
201 CHECK(args[0]->IsString());
202 // This should be checked by our function routing code.
203 CHECK(context()->GetAvailability("runtime.connectNative").is_available());
204
205 content::RenderFrame* render_frame = context()->GetRenderFrame();
206 if (!render_frame)
207 return;
208
209 std::string native_app_name = *v8::String::Utf8Value(args[0]);
210
rdevlin.cronin8e744a42016-12-06 18:40:27211 int js_id = GetNextJsId();
212 PortId port_id(context_id_, js_id, true);
Jeremy Roman16529d0e2017-08-24 18:13:47213 ports_[js_id] = std::make_unique<ExtensionPort>(context(), port_id, js_id);
rdevlin.cronindbbe69e72016-09-10 00:48:02214
rdevlin.cronin8e744a42016-12-06 18:40:27215 {
216 SCOPED_UMA_HISTOGRAM_TIMER(
217 "Extensions.Messaging.SetPortIdTime.NativeApp");
218 render_frame->Send(new ExtensionHostMsg_OpenChannelToNativeApp(
219 render_frame->GetRoutingID(), native_app_name, port_id));
220 }
221
222 args.GetReturnValue().Set(static_cast<int32_t>(js_id));
rdevlin.cronindbbe69e72016-09-10 00:48:02223}
224
225void MessagingBindings::OpenChannelToTab(
226 const v8::FunctionCallbackInfo<v8::Value>& args) {
227 content::RenderFrame* render_frame = context()->GetRenderFrame();
228 if (!render_frame)
229 return;
230
231 // tabs_custom_bindings.js unwraps arguments to tabs.connect/sendMessage and
232 // passes them to OpenChannelToTab, in the following order:
233 // - |tab_id| - Positive number that specifies the destination of the channel.
234 // - |frame_id| - Target frame(s) in the tab where onConnect is dispatched:
235 // -1 for all frames, 0 for the main frame, >0 for a child frame.
236 // - |extension_id| - ID of the initiating extension.
237 // - |channel_name| - A user-defined channel name.
238 CHECK(args.Length() == 4);
239 CHECK(args[0]->IsInt32());
240 CHECK(args[1]->IsInt32());
241 CHECK(args[2]->IsString());
242 CHECK(args[3]->IsString());
243
rdevlin.cronin8e744a42016-12-06 18:40:27244 int js_id = GetNextJsId();
245 PortId port_id(context_id_, js_id, true);
Jeremy Roman16529d0e2017-08-24 18:13:47246 ports_[js_id] = std::make_unique<ExtensionPort>(context(), port_id, js_id);
rdevlin.cronindbbe69e72016-09-10 00:48:02247
248 ExtensionMsg_TabTargetConnectionInfo info;
249 info.tab_id = args[0]->Int32Value();
250 info.frame_id = args[1]->Int32Value();
rdevlin.cronine6037622016-09-13 23:10:43251 // TODO(devlin): Why is this not part of info?
rdevlin.cronindbbe69e72016-09-10 00:48:02252 std::string extension_id = *v8::String::Utf8Value(args[2]);
253 std::string channel_name = *v8::String::Utf8Value(args[3]);
rdevlin.cronine6037622016-09-13 23:10:43254
255 ExtensionFrameHelper* frame_helper = ExtensionFrameHelper::Get(render_frame);
256 DCHECK(frame_helper);
rdevlin.cronine6037622016-09-13 23:10:43257
rdevlin.cronin8e744a42016-12-06 18:40:27258 {
259 SCOPED_UMA_HISTOGRAM_TIMER("Extensions.Messaging.SetPortIdTime.Tab");
260 render_frame->Send(new ExtensionHostMsg_OpenChannelToTab(
261 render_frame->GetRoutingID(), info, extension_id, channel_name,
262 port_id));
263 }
264
265 args.GetReturnValue().Set(static_cast<int32_t>(js_id));
rdevlin.cronindbbe69e72016-09-10 00:48:02266}
267
rdevlin.cronin8e744a42016-12-06 18:40:27268void MessagingBindings::ClosePort(int js_port_id, bool force_close) {
rdevlin.cronin4012b3b2016-08-31 18:36:47269 // TODO(robwu): Merge this logic with CloseChannel once the TODO in BindToGC
270 // has been addressed.
rdevlin.cronin8e744a42016-12-06 18:40:27271 auto iter = ports_.find(js_port_id);
rdevlin.cronindbbe69e72016-09-10 00:48:02272 if (iter != ports_.end()) {
273 std::unique_ptr<ExtensionPort> port = std::move(iter->second);
274 ports_.erase(iter);
275 port->Close(force_close);
rdevlin.cronin4012b3b2016-08-31 18:36:47276 }
277}
278
rdevlin.cronin8e744a42016-12-06 18:40:27279int MessagingBindings::GetNextJsId() {
280 return next_js_id_++;
rdevlin.cronindbbe69e72016-09-10 00:48:02281}
282
[email protected]8fe74bf2012-08-07 21:08:42283} // namespace extensions