Suppression for crbug/241044.
[chromium-blink-merge.git] / chrome / renderer / extensions / miscellaneous_bindings.cc
blobcf538c7ad315d108a1bed9538f8b47238b19cb13
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"
7 #include <map>
8 #include <string>
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):
31 // var extension =
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');
38 // });
40 using content::RenderThread;
41 using content::V8ValueConverter;
43 namespace {
45 struct ExtensionData {
46 struct PortData {
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 {
74 public:
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();
95 if (!renderview)
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();
120 if (notify_browser)
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;
162 private:
163 DISALLOW_COPY_AND_ASSIGN(GCCallbackArgs);
166 static void GCCallback(v8::Isolate* isolate,
167 v8::Persistent<v8::Value> object,
168 void* parameter) {
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);
178 delete args;
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();
192 } // namespace
194 namespace extensions {
196 ChromeV8Extension* MiscellaneousBindings::Get(
197 Dispatcher* dispatcher,
198 v8::Handle<v8::Context> context) {
199 return new ExtensionImpl(dispatcher, context);
202 // static
203 void MiscellaneousBindings::DispatchOnConnect(
204 const ChromeV8ContextSet::ContextSet& contexts,
205 int target_port_id,
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()) {
223 continue;
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()),
233 tab,
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,
243 &retval)) {
244 continue;
247 if (try_catch.HasCaught()) {
248 LOG(ERROR) << "Exception caught when calling Port.dispatchOnConnect.";
249 continue;
252 if (retval.IsEmpty()) {
253 LOG(ERROR) << "Empty return value from Port.dispatchOnConnect.";
254 continue;
257 CHECK(retval->IsBoolean());
258 if (retval->BooleanValue())
259 port_created = true;
262 // If we didn't create a port, notify the other end of the channel (treat it
263 // as a disconnect).
264 if (!port_created) {
265 content::RenderThread::Get()->Send(
266 new ExtensionHostMsg_CloseChannel(
267 target_port_id, kReceivingEndDoesntExistError));
271 // static
272 void MiscellaneousBindings::DeliverMessage(
273 const ChromeV8ContextSet::ContextSet& contexts,
274 int target_port_id,
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()) {
283 continue;
286 // Check to see whether the context has this port before bothering to create
287 // the message.
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,
292 &has_port)) {
293 continue;
296 if (try_catch.HasCaught()) {
297 LOG(ERROR) << "Exception caught when calling Port.hasPort.";
298 continue;
301 CHECK(!has_port.IsEmpty());
302 if (!has_port->BooleanValue())
303 continue;
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",
309 arguments.size(),
310 &arguments[0],
311 NULL));
315 // static
316 void MiscellaneousBindings::DispatchOnDisconnect(
317 const ChromeV8ContextSet::ContextSet& contexts,
318 int port_id,
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()) {
327 continue;
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()));
334 } else {
335 arguments.push_back(v8::Null());
337 (*it)->CallChromeHiddenMethod("Port.dispatchOnDisconnect",
338 arguments.size(), &arguments[0],
339 NULL);
343 } // namespace extensions