1 // Copyright (c) 2012 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.
5 #include "chrome/renderer/extensions/messaging_bindings.h"
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/lazy_instance.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/values.h"
16 #include "chrome/common/extensions/manifest_handlers/externally_connectable.h"
17 #include "chrome/common/extensions/message_bundle.h"
18 #include "chrome/common/url_constants.h"
19 #include "chrome/renderer/extensions/chrome_v8_context.h"
20 #include "chrome/renderer/extensions/chrome_v8_context_set.h"
21 #include "chrome/renderer/extensions/chrome_v8_extension.h"
22 #include "chrome/renderer/extensions/dispatcher.h"
23 #include "chrome/renderer/extensions/event_bindings.h"
24 #include "chrome/renderer/extensions/scoped_persistent.h"
25 #include "content/public/renderer/render_thread.h"
26 #include "content/public/renderer/render_view.h"
27 #include "content/public/renderer/v8_value_converter.h"
28 #include "extensions/common/api/messaging/message.h"
29 #include "extensions/common/extension_messages.h"
30 #include "grit/renderer_resources.h"
31 #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
32 #include "third_party/WebKit/public/web/WebScopedUserGesture.h"
33 #include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
34 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
35 #include "v8/include/v8.h"
37 // Message passing API example (in a content script):
39 // new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
40 // var port = runtime.connect();
41 // port.postMessage('Can you hear me now?');
42 // port.onmessage.addListener(function(msg, port) {
43 // alert('response=' + msg);
44 // port.postMessage('I got your reponse');
47 using content::RenderThread;
48 using content::V8ValueConverter;
50 namespace extensions {
54 struct ExtensionData {
56 int ref_count; // how many contexts have a handle to this port
57 PortData() : ref_count(0) {}
59 std::map<int, PortData> ports; // port ID -> data
62 base::LazyInstance<ExtensionData> g_extension_data =
63 LAZY_INSTANCE_INITIALIZER;
65 bool HasPortData(int port_id) {
66 return g_extension_data.Get().ports.find(port_id) !=
67 g_extension_data.Get().ports.end();
70 ExtensionData::PortData& GetPortData(int port_id) {
71 return g_extension_data.Get().ports[port_id];
74 void ClearPortData(int port_id) {
75 g_extension_data.Get().ports.erase(port_id);
78 const char kPortClosedError[] = "Attempting to use a disconnected port object";
79 const char kReceivingEndDoesntExistError[] =
80 "Could not establish connection. Receiving end does not exist.";
82 class ExtensionImpl : public ChromeV8Extension {
84 ExtensionImpl(Dispatcher* dispatcher, ChromeV8Context* context)
85 : ChromeV8Extension(dispatcher, context) {
86 RouteFunction("CloseChannel",
87 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
88 RouteFunction("PortAddRef",
89 base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
90 RouteFunction("PortRelease",
91 base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
92 RouteFunction("PostMessage",
93 base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
94 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
95 RouteFunction("BindToGC",
96 base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
99 virtual ~ExtensionImpl() {}
101 void ClearPortDataAndNotifyDispatcher(int port_id) {
102 ClearPortData(port_id);
103 dispatcher()->ClearPortData(port_id);
106 // Sends a message along the given channel.
107 void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
108 content::RenderView* renderview = GetRenderView();
112 // Arguments are (int32 port_id, string message).
113 CHECK(args.Length() == 2 &&
114 args[0]->IsInt32() &&
115 args[1]->IsString());
117 int port_id = args[0]->Int32Value();
118 if (!HasPortData(port_id)) {
119 args.GetIsolate()->ThrowException(v8::Exception::Error(
120 v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError)));
124 renderview->Send(new ExtensionHostMsg_PostMessage(
125 renderview->GetRoutingID(), port_id,
126 Message(*v8::String::Utf8Value(args[1]),
127 blink::WebUserGestureIndicator::isProcessingUserGesture())));
130 // Forcefully disconnects a port.
131 void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
132 // Arguments are (int32 port_id, boolean notify_browser).
133 CHECK_EQ(2, args.Length());
134 CHECK(args[0]->IsInt32());
135 CHECK(args[1]->IsBoolean());
137 int port_id = args[0]->Int32Value();
138 if (!HasPortData(port_id))
141 // Send via the RenderThread because the RenderView might be closing.
142 bool notify_browser = args[1]->BooleanValue();
143 if (notify_browser) {
144 content::RenderThread::Get()->Send(
145 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
148 ClearPortDataAndNotifyDispatcher(port_id);
151 // A new port has been created for a context. This occurs both when script
152 // opens a connection, and when a connection is opened to this script.
153 void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
154 // Arguments are (int32 port_id).
155 CHECK_EQ(1, args.Length());
156 CHECK(args[0]->IsInt32());
158 int port_id = args[0]->Int32Value();
159 ++GetPortData(port_id).ref_count;
162 // The frame a port lived in has been destroyed. When there are no more
163 // frames with a reference to a given port, we will disconnect it and notify
164 // the other end of the channel.
165 void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
166 // Arguments are (int32 port_id).
167 CHECK_EQ(1, args.Length());
168 CHECK(args[0]->IsInt32());
170 int port_id = args[0]->Int32Value();
171 if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) {
172 // Send via the RenderThread because the RenderView might be closing.
173 content::RenderThread::Get()->Send(
174 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
175 ClearPortDataAndNotifyDispatcher(port_id);
179 // Holds a |callback| to run sometime after |object| is GC'ed. |callback| will
180 // not be executed re-entrantly to avoid running JS in an unexpected state.
183 static void Bind(v8::Handle<v8::Object> object,
184 v8::Handle<v8::Function> callback,
185 v8::Isolate* isolate) {
186 GCCallback* cb = new GCCallback(object, callback, isolate);
187 cb->object_.SetWeak(cb, NearDeathCallback);
191 static void NearDeathCallback(
192 const v8::WeakCallbackData<v8::Object, GCCallback>& data) {
193 // v8 says we need to explicitly reset weak handles from their callbacks.
194 // It's not implicit as one might expect.
195 data.GetParameter()->object_.reset();
196 base::MessageLoop::current()->PostTask(
198 base::Bind(&GCCallback::RunCallback,
199 base::Owned(data.GetParameter())));
202 GCCallback(v8::Handle<v8::Object> object,
203 v8::Handle<v8::Function> callback,
204 v8::Isolate* isolate)
205 : object_(object), callback_(callback), isolate_(isolate) {}
208 v8::HandleScope handle_scope(isolate_);
209 v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_);
210 v8::Handle<v8::Context> context = callback->CreationContext();
211 if (context.IsEmpty())
213 v8::Context::Scope context_scope(context);
214 blink::WebScopedMicrotaskSuppression suppression;
215 callback->Call(context->Global(), 0, NULL);
218 ScopedPersistent<v8::Object> object_;
219 ScopedPersistent<v8::Function> callback_;
220 v8::Isolate* isolate_;
222 DISALLOW_COPY_AND_ASSIGN(GCCallback);
225 // void BindToGC(object, callback)
227 // Binds |callback| to be invoked *sometime after* |object| is garbage
228 // collected. We don't call the method re-entrantly so as to avoid executing
229 // JS in some bizarro undefined mid-GC state.
230 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
231 CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction());
232 GCCallback::Bind(args[0].As<v8::Object>(),
233 args[1].As<v8::Function>(),
240 ChromeV8Extension* MessagingBindings::Get(
241 Dispatcher* dispatcher,
242 ChromeV8Context* context) {
243 return new ExtensionImpl(dispatcher, context);
247 void MessagingBindings::DispatchOnConnect(
248 const ChromeV8ContextSet::ContextSet& contexts,
250 const std::string& channel_name,
251 const base::DictionaryValue& source_tab,
252 const std::string& source_extension_id,
253 const std::string& target_extension_id,
254 const GURL& source_url,
255 const std::string& tls_channel_id,
256 content::RenderView* restrict_to_render_view) {
257 v8::Isolate* isolate = v8::Isolate::GetCurrent();
258 v8::HandleScope handle_scope(isolate);
260 scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
262 bool port_created = false;
263 std::string source_url_spec = source_url.spec();
265 // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
266 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
267 it != contexts.end(); ++it) {
268 if (restrict_to_render_view &&
269 restrict_to_render_view != (*it)->GetRenderView()) {
273 // TODO(kalman): remove when ContextSet::ForEach is available.
274 if ((*it)->v8_context().IsEmpty())
277 v8::Handle<v8::Value> tab = v8::Null(isolate);
278 v8::Handle<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
279 const Extension* extension = (*it)->extension();
281 if (!source_tab.empty() && !extension->is_platform_app())
282 tab = converter->ToV8Value(&source_tab, (*it)->v8_context());
284 ExternallyConnectableInfo* externally_connectable =
285 ExternallyConnectableInfo::Get(extension);
286 if (externally_connectable &&
287 externally_connectable->accepts_tls_channel_id) {
288 tls_channel_id_value =
289 v8::String::NewFromUtf8(isolate,
290 tls_channel_id.c_str(),
291 v8::String::kNormalString,
292 tls_channel_id.size());
296 v8::Handle<v8::Value> arguments[] = {
298 v8::Integer::New(isolate, target_port_id),
300 v8::String::NewFromUtf8(isolate,
301 channel_name.c_str(),
302 v8::String::kNormalString,
303 channel_name.size()),
307 v8::String::NewFromUtf8(isolate,
308 source_extension_id.c_str(),
309 v8::String::kNormalString,
310 source_extension_id.size()),
312 v8::String::NewFromUtf8(isolate,
313 target_extension_id.c_str(),
314 v8::String::kNormalString,
315 target_extension_id.size()),
317 v8::String::NewFromUtf8(isolate,
318 source_url_spec.c_str(),
319 v8::String::kNormalString,
320 source_url_spec.size()),
322 tls_channel_id_value,
325 v8::Handle<v8::Value> retval = (*it)->module_system()->CallModuleMethod(
328 arraysize(arguments), arguments);
330 if (retval.IsEmpty()) {
331 LOG(ERROR) << "Empty return value from dispatchOnConnect.";
335 CHECK(retval->IsBoolean());
336 port_created |= retval->BooleanValue();
339 // If we didn't create a port, notify the other end of the channel (treat it
342 content::RenderThread::Get()->Send(
343 new ExtensionHostMsg_CloseChannel(
344 target_port_id, kReceivingEndDoesntExistError));
349 void MessagingBindings::DeliverMessage(
350 const ChromeV8ContextSet::ContextSet& contexts,
352 const Message& message,
353 content::RenderView* restrict_to_render_view) {
354 scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
355 scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus;
356 if (message.user_gesture) {
357 web_user_gesture.reset(new blink::WebScopedUserGesture);
358 allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator);
361 v8::Isolate* isolate = v8::Isolate::GetCurrent();
362 v8::HandleScope handle_scope(isolate);
364 // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
365 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
366 it != contexts.end(); ++it) {
367 if (restrict_to_render_view &&
368 restrict_to_render_view != (*it)->GetRenderView()) {
372 // TODO(kalman): remove when ContextSet::ForEach is available.
373 if ((*it)->v8_context().IsEmpty())
376 // Check to see whether the context has this port before bothering to create
378 v8::Handle<v8::Value> port_id_handle =
379 v8::Integer::New(isolate, target_port_id);
380 v8::Handle<v8::Value> has_port = (*it)->module_system()->CallModuleMethod(
385 CHECK(!has_port.IsEmpty());
386 if (!has_port->BooleanValue())
389 std::vector<v8::Handle<v8::Value> > arguments;
390 arguments.push_back(v8::String::NewFromUtf8(isolate,
391 message.data.c_str(),
392 v8::String::kNormalString,
393 message.data.size()));
394 arguments.push_back(port_id_handle);
395 (*it)->module_system()->CallModuleMethod("messaging",
402 void MessagingBindings::DispatchOnDisconnect(
403 const ChromeV8ContextSet::ContextSet& contexts,
405 const std::string& error_message,
406 content::RenderView* restrict_to_render_view) {
407 v8::Isolate* isolate = v8::Isolate::GetCurrent();
408 v8::HandleScope handle_scope(isolate);
410 // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
411 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
412 it != contexts.end(); ++it) {
413 if (restrict_to_render_view &&
414 restrict_to_render_view != (*it)->GetRenderView()) {
418 // TODO(kalman): remove when ContextSet::ForEach is available.
419 if ((*it)->v8_context().IsEmpty())
422 std::vector<v8::Handle<v8::Value> > arguments;
423 arguments.push_back(v8::Integer::New(isolate, port_id));
424 if (!error_message.empty()) {
426 v8::String::NewFromUtf8(isolate, error_message.c_str()));
428 arguments.push_back(v8::Null(isolate));
430 (*it)->module_system()->CallModuleMethod("messaging",
431 "dispatchOnDisconnect",
436 } // namespace extensions