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/api/messaging/message.h"
17 #include "chrome/common/extensions/extension_messages.h"
18 #include "chrome/common/extensions/manifest_handlers/externally_connectable.h"
19 #include "chrome/common/extensions/message_bundle.h"
20 #include "chrome/common/url_constants.h"
21 #include "chrome/renderer/extensions/chrome_v8_context.h"
22 #include "chrome/renderer/extensions/chrome_v8_context_set.h"
23 #include "chrome/renderer/extensions/chrome_v8_extension.h"
24 #include "chrome/renderer/extensions/dispatcher.h"
25 #include "chrome/renderer/extensions/event_bindings.h"
26 #include "chrome/renderer/extensions/scoped_persistent.h"
27 #include "content/public/renderer/render_thread.h"
28 #include "content/public/renderer/render_view.h"
29 #include "content/public/renderer/v8_value_converter.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/WebUserGestureIndicator.h"
34 #include "v8/include/v8.h"
36 // Message passing API example (in a content script):
38 // new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
39 // var port = runtime.connect();
40 // port.postMessage('Can you hear me now?');
41 // port.onmessage.addListener(function(msg, port) {
42 // alert('response=' + msg);
43 // port.postMessage('I got your reponse');
46 using content::RenderThread;
47 using content::V8ValueConverter;
51 struct ExtensionData {
53 int ref_count; // how many contexts have a handle to this port
54 PortData() : ref_count(0) {}
56 std::map<int, PortData> ports; // port ID -> data
59 static base::LazyInstance<ExtensionData> g_extension_data =
60 LAZY_INSTANCE_INITIALIZER;
62 static bool HasPortData(int port_id) {
63 return g_extension_data.Get().ports.find(port_id) !=
64 g_extension_data.Get().ports.end();
67 static ExtensionData::PortData& GetPortData(int port_id) {
68 return g_extension_data.Get().ports[port_id];
71 static void ClearPortData(int port_id) {
72 g_extension_data.Get().ports.erase(port_id);
75 const char kPortClosedError[] = "Attempting to use a disconnected port object";
76 const char kReceivingEndDoesntExistError[] =
77 "Could not establish connection. Receiving end does not exist.";
79 class ExtensionImpl : public extensions::ChromeV8Extension {
81 explicit ExtensionImpl(extensions::Dispatcher* dispatcher,
82 extensions::ChromeV8Context* context)
83 : extensions::ChromeV8Extension(dispatcher, context) {
84 RouteFunction("CloseChannel",
85 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
86 RouteFunction("PortAddRef",
87 base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
88 RouteFunction("PortRelease",
89 base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
90 RouteFunction("PostMessage",
91 base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
92 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
93 RouteFunction("BindToGC",
94 base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
97 virtual ~ExtensionImpl() {}
99 // Sends a message along the given channel.
100 void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
101 content::RenderView* renderview = GetRenderView();
105 // Arguments are (int32 port_id, string message).
106 CHECK(args.Length() == 2 &&
107 args[0]->IsInt32() &&
108 args[1]->IsString());
110 int port_id = args[0]->Int32Value();
111 if (!HasPortData(port_id)) {
112 v8::ThrowException(v8::Exception::Error(
113 v8::String::New(kPortClosedError)));
117 renderview->Send(new ExtensionHostMsg_PostMessage(
118 renderview->GetRoutingID(), port_id,
120 *v8::String::AsciiValue(args[1]),
121 WebKit::WebUserGestureIndicator::isProcessingUserGesture())));
124 // Forcefully disconnects a port.
125 void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
126 // Arguments are (int32 port_id, boolean notify_browser).
127 CHECK_EQ(2, args.Length());
128 CHECK(args[0]->IsInt32());
129 CHECK(args[1]->IsBoolean());
131 int port_id = args[0]->Int32Value();
132 if (!HasPortData(port_id))
135 // Send via the RenderThread because the RenderView might be closing.
136 bool notify_browser = args[1]->BooleanValue();
137 if (notify_browser) {
138 content::RenderThread::Get()->Send(
139 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
142 ClearPortData(port_id);
145 // A new port has been created for a context. This occurs both when script
146 // opens a connection, and when a connection is opened to this script.
147 void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
148 // Arguments are (int32 port_id).
149 CHECK_EQ(1, args.Length());
150 CHECK(args[0]->IsInt32());
152 int port_id = args[0]->Int32Value();
153 ++GetPortData(port_id).ref_count;
156 // The frame a port lived in has been destroyed. When there are no more
157 // frames with a reference to a given port, we will disconnect it and notify
158 // the other end of the channel.
159 void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
160 // Arguments are (int32 port_id).
161 CHECK_EQ(1, args.Length());
162 CHECK(args[0]->IsInt32());
164 int port_id = args[0]->Int32Value();
165 if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) {
166 // Send via the RenderThread because the RenderView might be closing.
167 content::RenderThread::Get()->Send(
168 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
169 ClearPortData(port_id);
173 // Holds a |callback| to run sometime after |object| is GC'ed. |callback| will
174 // not be executed re-entrantly to avoid running JS in an unexpected state.
177 static void Bind(v8::Handle<v8::Object> object,
178 v8::Handle<v8::Function> callback,
179 v8::Isolate* isolate) {
180 GCCallback* cb = new GCCallback(object, callback, isolate);
181 cb->object_.MakeWeak(cb, NearDeathCallback);
185 static void NearDeathCallback(v8::Isolate* isolate,
186 v8::Persistent<v8::Object>* object,
188 // v8 says we need to explicitly reset weak handles from their callbacks.
189 // It's not implicit as one might expect.
190 self->object_.reset();
191 base::MessageLoop::current()->PostTask(FROM_HERE,
192 base::Bind(&GCCallback::RunCallback, base::Owned(self)));
195 GCCallback(v8::Handle<v8::Object> object,
196 v8::Handle<v8::Function> callback,
197 v8::Isolate* isolate)
198 : object_(object), callback_(callback), isolate_(isolate) {}
201 v8::HandleScope handle_scope(isolate_);
202 v8::Handle<v8::Function> callback = callback_.NewHandle(isolate_);
203 v8::Handle<v8::Context> context = callback->CreationContext();
204 if (context.IsEmpty())
206 v8::Context::Scope context_scope(context);
207 WebKit::WebScopedMicrotaskSuppression suppression;
208 callback->Call(context->Global(), 0, NULL);
211 extensions::ScopedPersistent<v8::Object> object_;
212 extensions::ScopedPersistent<v8::Function> callback_;
213 v8::Isolate* isolate_;
215 DISALLOW_COPY_AND_ASSIGN(GCCallback);
218 // void BindToGC(object, callback)
220 // Binds |callback| to be invoked *sometime after* |object| is garbage
221 // collected. We don't call the method re-entrantly so as to avoid executing
222 // JS in some bizarro undefined mid-GC state.
223 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
224 CHECK(args.Length() == 2 && args[0]->IsObject() && args[1]->IsFunction());
225 GCCallback::Bind(args[0].As<v8::Object>(),
226 args[1].As<v8::Function>(),
233 namespace extensions {
235 ChromeV8Extension* MessagingBindings::Get(
236 Dispatcher* dispatcher,
237 ChromeV8Context* context) {
238 return new ExtensionImpl(dispatcher, context);
242 void MessagingBindings::DispatchOnConnect(
243 const ChromeV8ContextSet::ContextSet& contexts,
245 const std::string& channel_name,
246 const base::DictionaryValue& source_tab,
247 const std::string& source_extension_id,
248 const std::string& target_extension_id,
249 const GURL& source_url,
250 const std::string& tls_channel_id,
251 content::RenderView* restrict_to_render_view) {
252 v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
254 scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
256 bool port_created = false;
257 std::string source_url_spec = source_url.spec();
259 // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
260 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
261 it != contexts.end(); ++it) {
262 if (restrict_to_render_view &&
263 restrict_to_render_view != (*it)->GetRenderView()) {
267 // TODO(kalman): remove when ContextSet::ForEach is available.
268 if ((*it)->v8_context().IsEmpty())
271 v8::Handle<v8::Value> tab = v8::Null();
272 if (!source_tab.empty())
273 tab = converter->ToV8Value(&source_tab, (*it)->v8_context());
275 v8::Handle<v8::Value> tls_channel_id_value = v8::Undefined();
276 if ((*it)->extension()) {
277 ExternallyConnectableInfo* externally_connectable =
278 ExternallyConnectableInfo::Get((*it)->extension());
279 if (externally_connectable &&
280 externally_connectable->accepts_tls_channel_id) {
281 tls_channel_id_value =
282 v8::String::New(tls_channel_id.c_str(), tls_channel_id.size());
286 v8::Handle<v8::Value> arguments[] = {
287 v8::Integer::New(target_port_id),
288 v8::String::New(channel_name.c_str(), channel_name.size()),
290 v8::String::New(source_extension_id.c_str(), source_extension_id.size()),
291 v8::String::New(target_extension_id.c_str(), target_extension_id.size()),
292 v8::String::New(source_url_spec.c_str(), source_url_spec.size()),
293 tls_channel_id_value,
296 v8::Handle<v8::Value> retval = (*it)->module_system()->CallModuleMethod(
299 arraysize(arguments), arguments);
301 if (retval.IsEmpty()) {
302 LOG(ERROR) << "Empty return value from dispatchOnConnect.";
306 CHECK(retval->IsBoolean());
307 port_created |= retval->BooleanValue();
310 // If we didn't create a port, notify the other end of the channel (treat it
313 content::RenderThread::Get()->Send(
314 new ExtensionHostMsg_CloseChannel(
315 target_port_id, kReceivingEndDoesntExistError));
320 void MessagingBindings::DeliverMessage(
321 const ChromeV8ContextSet::ContextSet& contexts,
323 const Message& message,
324 content::RenderView* restrict_to_render_view) {
325 scoped_ptr<WebKit::WebScopedUserGesture> web_user_gesture;
326 if (message.user_gesture)
327 web_user_gesture.reset(new WebKit::WebScopedUserGesture);
329 v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
331 // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
332 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
333 it != contexts.end(); ++it) {
334 if (restrict_to_render_view &&
335 restrict_to_render_view != (*it)->GetRenderView()) {
339 // TODO(kalman): remove when ContextSet::ForEach is available.
340 if ((*it)->v8_context().IsEmpty())
343 // Check to see whether the context has this port before bothering to create
345 v8::Handle<v8::Value> port_id_handle = v8::Integer::New(target_port_id);
346 v8::Handle<v8::Value> has_port = (*it)->module_system()->CallModuleMethod(
351 CHECK(!has_port.IsEmpty());
352 if (!has_port->BooleanValue())
355 std::vector<v8::Handle<v8::Value> > arguments;
356 arguments.push_back(v8::String::New(message.data.c_str(),
357 message.data.size()));
358 arguments.push_back(port_id_handle);
359 (*it)->module_system()->CallModuleMethod("messaging",
366 void MessagingBindings::DispatchOnDisconnect(
367 const ChromeV8ContextSet::ContextSet& contexts,
369 const std::string& error_message,
370 content::RenderView* restrict_to_render_view) {
371 v8::HandleScope handle_scope(v8::Isolate::GetCurrent());
373 // TODO(kalman): pass in the full ChromeV8ContextSet; call ForEach.
374 for (ChromeV8ContextSet::ContextSet::const_iterator it = contexts.begin();
375 it != contexts.end(); ++it) {
376 if (restrict_to_render_view &&
377 restrict_to_render_view != (*it)->GetRenderView()) {
381 // TODO(kalman): remove when ContextSet::ForEach is available.
382 if ((*it)->v8_context().IsEmpty())
385 std::vector<v8::Handle<v8::Value> > arguments;
386 arguments.push_back(v8::Integer::New(port_id));
387 if (!error_message.empty()) {
388 arguments.push_back(v8::String::New(error_message.c_str()));
390 arguments.push_back(v8::Null());
392 (*it)->module_system()->CallModuleMethod("messaging",
393 "dispatchOnDisconnect",
398 } // namespace extensions