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/miscellaneous_bindings.h"
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/lazy_instance.h"
13 #include "base/values.h"
14 #include "chrome/common/extensions/extension_messages.h"
15 #include "chrome/common/extensions/message_bundle.h"
16 #include "chrome/common/url_constants.h"
17 #include "chrome/renderer/extensions/chrome_v8_context.h"
18 #include "chrome/renderer/extensions/chrome_v8_context_set.h"
19 #include "chrome/renderer/extensions/chrome_v8_extension.h"
20 #include "chrome/renderer/extensions/dispatcher.h"
21 #include "chrome/renderer/extensions/event_bindings.h"
22 #include "chrome/renderer/extensions/scoped_persistent.h"
23 #include "content/public/renderer/render_thread.h"
24 #include "content/public/renderer/render_view.h"
25 #include "content/public/renderer/v8_value_converter.h"
26 #include "grit/renderer_resources.h"
27 #include "third_party/WebKit/Source/WebKit/chromium/public/WebScopedMicrotaskSuppression.h"
28 #include "v8/include/v8.h"
30 // Message passing API example (in a content script):
32 // new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
33 // var port = runtime.connect();
34 // port.postMessage('Can you hear me now?');
35 // port.onmessage.addListener(function(msg, port) {
36 // alert('response=' + msg);
37 // port.postMessage('I got your reponse');
40 using content::RenderThread
;
41 using content::V8ValueConverter
;
45 struct ExtensionData
{
47 int ref_count
; // how many contexts have a handle to this port
48 PortData() : ref_count(0) {}
50 std::map
<int, PortData
> ports
; // port ID -> data
53 static base::LazyInstance
<ExtensionData
> g_extension_data
=
54 LAZY_INSTANCE_INITIALIZER
;
56 static bool HasPortData(int port_id
) {
57 return g_extension_data
.Get().ports
.find(port_id
) !=
58 g_extension_data
.Get().ports
.end();
61 static ExtensionData::PortData
& GetPortData(int port_id
) {
62 return g_extension_data
.Get().ports
[port_id
];
65 static void ClearPortData(int port_id
) {
66 g_extension_data
.Get().ports
.erase(port_id
);
69 const char kPortClosedError
[] = "Attempting to use a disconnected port object";
70 const char kReceivingEndDoesntExistError
[] =
71 "Could not establish connection. Receiving end does not exist.";
73 class ExtensionImpl
: public extensions::ChromeV8Extension
{
75 explicit ExtensionImpl(extensions::Dispatcher
* dispatcher
,
76 v8::Handle
<v8::Context
> context
)
77 : extensions::ChromeV8Extension(dispatcher
, context
) {
78 RouteFunction("CloseChannel",
79 base::Bind(&ExtensionImpl::CloseChannel
, base::Unretained(this)));
80 RouteFunction("PortAddRef",
81 base::Bind(&ExtensionImpl::PortAddRef
, base::Unretained(this)));
82 RouteFunction("PortRelease",
83 base::Bind(&ExtensionImpl::PortRelease
, base::Unretained(this)));
84 RouteFunction("PostMessage",
85 base::Bind(&ExtensionImpl::PostMessage
, base::Unretained(this)));
86 RouteFunction("BindToGC",
87 base::Bind(&ExtensionImpl::BindToGC
, base::Unretained(this)));
90 virtual ~ExtensionImpl() {}
92 // Sends a message along the given channel.
93 v8::Handle
<v8::Value
> PostMessage(const v8::Arguments
& args
) {
94 content::RenderView
* renderview
= GetRenderView();
96 return v8::Undefined();
98 if (args
.Length() >= 2 && args
[0]->IsInt32() && args
[1]->IsString()) {
99 int port_id
= args
[0]->Int32Value();
100 if (!HasPortData(port_id
)) {
101 return v8::ThrowException(v8::Exception::Error(
102 v8::String::New(kPortClosedError
)));
104 std::string message
= *v8::String::Utf8Value(args
[1]->ToString());
105 renderview
->Send(new ExtensionHostMsg_PostMessage(
106 renderview
->GetRoutingID(), port_id
, message
));
108 return v8::Undefined();
111 // Forcefully disconnects a port.
112 v8::Handle
<v8::Value
> CloseChannel(const v8::Arguments
& args
) {
113 if (args
.Length() >= 2 && args
[0]->IsInt32() && args
[1]->IsBoolean()) {
114 int port_id
= args
[0]->Int32Value();
115 if (!HasPortData(port_id
)) {
116 return v8::Undefined();
118 // Send via the RenderThread because the RenderView might be closing.
119 bool notify_browser
= args
[1]->BooleanValue();
121 content::RenderThread::Get()->Send(
122 new ExtensionHostMsg_CloseChannel(port_id
, std::string()));
123 ClearPortData(port_id
);
125 return v8::Undefined();
128 // A new port has been created for a context. This occurs both when script
129 // opens a connection, and when a connection is opened to this script.
130 v8::Handle
<v8::Value
> PortAddRef(const v8::Arguments
& args
) {
131 if (args
.Length() >= 1 && args
[0]->IsInt32()) {
132 int port_id
= args
[0]->Int32Value();
133 ++GetPortData(port_id
).ref_count
;
135 return v8::Undefined();
138 // The frame a port lived in has been destroyed. When there are no more
139 // frames with a reference to a given port, we will disconnect it and notify
140 // the other end of the channel.
141 v8::Handle
<v8::Value
> PortRelease(const v8::Arguments
& args
) {
142 if (args
.Length() >= 1 && args
[0]->IsInt32()) {
143 int port_id
= args
[0]->Int32Value();
144 if (HasPortData(port_id
) && --GetPortData(port_id
).ref_count
== 0) {
145 // Send via the RenderThread because the RenderView might be closing.
146 content::RenderThread::Get()->Send(
147 new ExtensionHostMsg_CloseChannel(port_id
, std::string()));
148 ClearPortData(port_id
);
151 return v8::Undefined();
154 struct GCCallbackArgs
{
155 GCCallbackArgs(v8::Handle
<v8::Object
> object
,
156 v8::Handle
<v8::Function
> callback
)
157 : object(object
), callback(callback
) {}
159 extensions::ScopedPersistent
<v8::Object
> object
;
160 extensions::ScopedPersistent
<v8::Function
> callback
;
163 DISALLOW_COPY_AND_ASSIGN(GCCallbackArgs
);
166 static void GCCallback(v8::Isolate
* isolate
,
167 v8::Persistent
<v8::Value
> object
,
169 v8::HandleScope handle_scope
;
170 GCCallbackArgs
* args
= static_cast<GCCallbackArgs
*>(parameter
);
171 v8::Handle
<v8::Context
> context
= args
->callback
->CreationContext();
172 v8::Context::Scope
context_scope(context
);
173 WebKit::WebScopedMicrotaskSuppression suppression
;
174 // Wrap in try/catch here so that we don't call into any message/exception
175 // handlers during GC. That is a recipe for pain.
176 v8::TryCatch trycatch
;
177 args
->callback
->Call(context
->Global(), 0, NULL
);
181 // Binds a callback to be invoked when the given object is garbage collected.
182 v8::Handle
<v8::Value
> BindToGC(const v8::Arguments
& args
) {
183 CHECK(args
.Length() == 2 && args
[0]->IsObject() && args
[1]->IsFunction());
184 GCCallbackArgs
* context
= new GCCallbackArgs(
185 v8::Handle
<v8::Object
>::Cast(args
[0]),
186 v8::Handle
<v8::Function
>::Cast(args
[1]));
187 context
->object
.MakeWeak(context
, GCCallback
);
188 return v8::Undefined();
194 namespace extensions
{
196 ChromeV8Extension
* MiscellaneousBindings::Get(
197 Dispatcher
* dispatcher
,
198 v8::Handle
<v8::Context
> context
) {
199 return new ExtensionImpl(dispatcher
, context
);
203 void MiscellaneousBindings::DispatchOnConnect(
204 const ChromeV8ContextSet::ContextSet
& contexts
,
206 const std::string
& channel_name
,
207 const base::DictionaryValue
& source_tab
,
208 const std::string
& source_extension_id
,
209 const std::string
& target_extension_id
,
210 const GURL
& source_url
,
211 content::RenderView
* restrict_to_render_view
) {
212 v8::HandleScope handle_scope
;
214 scoped_ptr
<V8ValueConverter
> converter(V8ValueConverter::create());
216 bool port_created
= false;
217 std::string source_url_spec
= source_url
.spec();
219 for (ChromeV8ContextSet::ContextSet::const_iterator it
= contexts
.begin();
220 it
!= contexts
.end(); ++it
) {
221 if (restrict_to_render_view
&&
222 restrict_to_render_view
!= (*it
)->GetRenderView()) {
226 v8::Handle
<v8::Value
> tab
= v8::Null();
227 if (!source_tab
.empty())
228 tab
= converter
->ToV8Value(&source_tab
, (*it
)->v8_context());
230 v8::Handle
<v8::Value
> arguments
[] = {
231 v8::Integer::New(target_port_id
),
232 v8::String::New(channel_name
.c_str(), channel_name
.size()),
234 v8::String::New(source_extension_id
.c_str(), source_extension_id
.size()),
235 v8::String::New(target_extension_id
.c_str(), target_extension_id
.size()),
236 v8::String::New(source_url_spec
.c_str(), source_url_spec
.size())
239 v8::Handle
<v8::Value
> retval
;
240 v8::TryCatch try_catch
;
241 if (!(*it
)->CallChromeHiddenMethod("Port.dispatchOnConnect",
242 arraysize(arguments
), arguments
,
247 if (try_catch
.HasCaught()) {
248 LOG(ERROR
) << "Exception caught when calling Port.dispatchOnConnect.";
252 if (retval
.IsEmpty()) {
253 LOG(ERROR
) << "Empty return value from Port.dispatchOnConnect.";
257 CHECK(retval
->IsBoolean());
258 if (retval
->BooleanValue())
262 // If we didn't create a port, notify the other end of the channel (treat it
265 content::RenderThread::Get()->Send(
266 new ExtensionHostMsg_CloseChannel(
267 target_port_id
, kReceivingEndDoesntExistError
));
272 void MiscellaneousBindings::DeliverMessage(
273 const ChromeV8ContextSet::ContextSet
& contexts
,
275 const std::string
& message
,
276 content::RenderView
* restrict_to_render_view
) {
277 v8::HandleScope handle_scope
;
279 for (ChromeV8ContextSet::ContextSet::const_iterator it
= contexts
.begin();
280 it
!= contexts
.end(); ++it
) {
281 if (restrict_to_render_view
&&
282 restrict_to_render_view
!= (*it
)->GetRenderView()) {
286 // Check to see whether the context has this port before bothering to create
288 v8::Handle
<v8::Value
> port_id_handle
= v8::Integer::New(target_port_id
);
289 v8::Handle
<v8::Value
> has_port
;
290 v8::TryCatch try_catch
;
291 if (!(*it
)->CallChromeHiddenMethod("Port.hasPort", 1, &port_id_handle
,
296 if (try_catch
.HasCaught()) {
297 LOG(ERROR
) << "Exception caught when calling Port.hasPort.";
301 CHECK(!has_port
.IsEmpty());
302 if (!has_port
->BooleanValue())
305 std::vector
<v8::Handle
<v8::Value
> > arguments
;
306 arguments
.push_back(v8::String::New(message
.c_str(), message
.size()));
307 arguments
.push_back(port_id_handle
);
308 CHECK((*it
)->CallChromeHiddenMethod("Port.dispatchOnMessage",
316 void MiscellaneousBindings::DispatchOnDisconnect(
317 const ChromeV8ContextSet::ContextSet
& contexts
,
319 const std::string
& error_message
,
320 content::RenderView
* restrict_to_render_view
) {
321 v8::HandleScope handle_scope
;
323 for (ChromeV8ContextSet::ContextSet::const_iterator it
= contexts
.begin();
324 it
!= contexts
.end(); ++it
) {
325 if (restrict_to_render_view
&&
326 restrict_to_render_view
!= (*it
)->GetRenderView()) {
330 std::vector
<v8::Handle
<v8::Value
> > arguments
;
331 arguments
.push_back(v8::Integer::New(port_id
));
332 if (!error_message
.empty()) {
333 arguments
.push_back(v8::String::New(error_message
.c_str()));
335 arguments
.push_back(v8::Null());
337 (*it
)->CallChromeHiddenMethod("Port.dispatchOnDisconnect",
338 arguments
.size(), &arguments
[0],
343 } // namespace extensions