Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / extensions / renderer / messaging_bindings.cc
blob595bee1b600e7229871c148feab9b0d8c90d1aaa
1 // Copyright 2014 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 "extensions/renderer/messaging_bindings.h"
7 #include <map>
8 #include <string>
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/bind_helpers.h"
13 #include "base/callback.h"
14 #include "base/lazy_instance.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/values.h"
18 #include "components/guest_view/common/guest_view_constants.h"
19 #include "content/public/child/v8_value_converter.h"
20 #include "content/public/common/child_process_host.h"
21 #include "content/public/renderer/render_frame.h"
22 #include "content/public/renderer/render_thread.h"
23 #include "extensions/common/api/messaging/message.h"
24 #include "extensions/common/extension_messages.h"
25 #include "extensions/common/manifest_handlers/externally_connectable.h"
26 #include "extensions/renderer/dispatcher.h"
27 #include "extensions/renderer/event_bindings.h"
28 #include "extensions/renderer/object_backed_native_handler.h"
29 #include "extensions/renderer/script_context.h"
30 #include "extensions/renderer/script_context_set.h"
31 #include "third_party/WebKit/public/web/WebDocument.h"
32 #include "third_party/WebKit/public/web/WebLocalFrame.h"
33 #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
34 #include "third_party/WebKit/public/web/WebScopedUserGesture.h"
35 #include "third_party/WebKit/public/web/WebScopedWindowFocusAllowedIndicator.h"
36 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
37 #include "v8/include/v8.h"
39 // Message passing API example (in a content script):
40 // var extension =
41 // new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
42 // var port = runtime.connect();
43 // port.postMessage('Can you hear me now?');
44 // port.onmessage.addListener(function(msg, port) {
45 // alert('response=' + msg);
46 // port.postMessage('I got your reponse');
47 // });
49 using content::RenderThread;
50 using content::V8ValueConverter;
52 namespace extensions {
54 namespace {
56 // Binds |callback| to run when |object| is garbage collected. So as to not
57 // re-entrantly call into v8, we execute this function asynchronously, at
58 // which point |context| may have been invalidated. If so, |callback| is not
59 // run, and |fallback| will be called instead.
61 // Deletes itself when the object args[0] is garbage collected or when the
62 // context is invalidated.
63 class GCCallback : public base::SupportsWeakPtr<GCCallback> {
64 public:
65 GCCallback(ScriptContext* context,
66 const v8::Local<v8::Object>& object,
67 const v8::Local<v8::Function>& callback,
68 const base::Closure& fallback)
69 : context_(context),
70 object_(context->isolate(), object),
71 callback_(context->isolate(), callback),
72 fallback_(fallback) {
73 object_.SetWeak(this, FirstWeakCallback, v8::WeakCallbackType::kParameter);
74 context->AddInvalidationObserver(
75 base::Bind(&GCCallback::OnContextInvalidated, AsWeakPtr()));
78 private:
79 static void FirstWeakCallback(const v8::WeakCallbackInfo<GCCallback>& data) {
80 // v8 says we need to explicitly reset weak handles from their callbacks.
81 // It's not implicit as one might expect.
82 data.GetParameter()->object_.Reset();
83 data.SetSecondPassCallback(SecondWeakCallback);
86 static void SecondWeakCallback(const v8::WeakCallbackInfo<GCCallback>& data) {
87 base::MessageLoop::current()->PostTask(
88 FROM_HERE,
89 base::Bind(&GCCallback::RunCallback, data.GetParameter()->AsWeakPtr()));
92 void RunCallback() {
93 CHECK(context_);
94 v8::Isolate* isolate = context_->isolate();
95 v8::HandleScope handle_scope(isolate);
96 context_->CallFunction(v8::Local<v8::Function>::New(isolate, callback_));
97 delete this;
100 void OnContextInvalidated() {
101 fallback_.Run();
102 context_ = NULL;
103 delete this;
106 // ScriptContext which owns this GCCallback.
107 ScriptContext* context_;
109 // Holds a global handle to the object this GCCallback is bound to.
110 v8::Global<v8::Object> object_;
112 // Function to run when |object_| bound to this GCCallback is GC'd.
113 v8::Global<v8::Function> callback_;
115 // Function to run if context is invalidated before we have a chance
116 // to execute |callback_|.
117 base::Closure fallback_;
119 DISALLOW_COPY_AND_ASSIGN(GCCallback);
122 struct ExtensionData {
123 struct PortData {
124 int ref_count; // how many contexts have a handle to this port
125 PortData() : ref_count(0) {}
127 std::map<int, PortData> ports; // port ID -> data
130 base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER;
132 bool HasPortData(int port_id) {
133 return g_extension_data.Get().ports.find(port_id) !=
134 g_extension_data.Get().ports.end();
137 ExtensionData::PortData& GetPortData(int port_id) {
138 return g_extension_data.Get().ports[port_id];
141 void ClearPortData(int port_id) {
142 g_extension_data.Get().ports.erase(port_id);
145 const char kPortClosedError[] = "Attempting to use a disconnected port object";
146 const char kReceivingEndDoesntExistError[] =
147 "Could not establish connection. Receiving end does not exist.";
149 class ExtensionImpl : public ObjectBackedNativeHandler {
150 public:
151 ExtensionImpl(Dispatcher* dispatcher, ScriptContext* context)
152 : ObjectBackedNativeHandler(context),
153 dispatcher_(dispatcher),
154 weak_ptr_factory_(this) {
155 RouteFunction(
156 "CloseChannel",
157 base::Bind(&ExtensionImpl::CloseChannel, base::Unretained(this)));
158 RouteFunction(
159 "PortAddRef",
160 base::Bind(&ExtensionImpl::PortAddRef, base::Unretained(this)));
161 RouteFunction(
162 "PortRelease",
163 base::Bind(&ExtensionImpl::PortRelease, base::Unretained(this)));
164 RouteFunction(
165 "PostMessage",
166 base::Bind(&ExtensionImpl::PostMessage, base::Unretained(this)));
167 // TODO(fsamuel, kalman): Move BindToGC out of messaging natives.
168 RouteFunction("BindToGC",
169 base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this)));
172 ~ExtensionImpl() override {}
174 private:
175 void ClearPortDataAndNotifyDispatcher(int port_id) {
176 ClearPortData(port_id);
177 dispatcher_->ClearPortData(port_id);
180 // Sends a message along the given channel.
181 void PostMessage(const v8::FunctionCallbackInfo<v8::Value>& args) {
182 content::RenderFrame* renderframe = context()->GetRenderFrame();
183 if (!renderframe)
184 return;
186 // Arguments are (int32 port_id, string message).
187 CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString());
189 int port_id = args[0]->Int32Value();
190 if (!HasPortData(port_id)) {
191 args.GetIsolate()->ThrowException(v8::Exception::Error(
192 v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError)));
193 return;
196 renderframe->Send(new ExtensionHostMsg_PostMessage(
197 renderframe->GetRoutingID(), port_id,
198 Message(*v8::String::Utf8Value(args[1]),
199 blink::WebUserGestureIndicator::isProcessingUserGesture())));
202 // Forcefully disconnects a port.
203 void CloseChannel(const v8::FunctionCallbackInfo<v8::Value>& args) {
204 // Arguments are (int32 port_id, boolean notify_browser).
205 CHECK_EQ(2, args.Length());
206 CHECK(args[0]->IsInt32());
207 CHECK(args[1]->IsBoolean());
209 int port_id = args[0]->Int32Value();
210 if (!HasPortData(port_id))
211 return;
213 // Send via the RenderThread because the RenderFrame might be closing.
214 bool notify_browser = args[1]->BooleanValue();
215 if (notify_browser) {
216 content::RenderThread::Get()->Send(
217 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
220 ClearPortDataAndNotifyDispatcher(port_id);
223 // A new port has been created for a context. This occurs both when script
224 // opens a connection, and when a connection is opened to this script.
225 void PortAddRef(const v8::FunctionCallbackInfo<v8::Value>& args) {
226 // Arguments are (int32 port_id).
227 CHECK_EQ(1, args.Length());
228 CHECK(args[0]->IsInt32());
230 int port_id = args[0]->Int32Value();
231 ++GetPortData(port_id).ref_count;
234 // The frame a port lived in has been destroyed. When there are no more
235 // frames with a reference to a given port, we will disconnect it and notify
236 // the other end of the channel.
237 void PortRelease(const v8::FunctionCallbackInfo<v8::Value>& args) {
238 // Arguments are (int32 port_id).
239 CHECK(args.Length() == 1 && args[0]->IsInt32());
240 ReleasePort(args[0]->Int32Value());
243 // Implementation of both the PortRelease native handler call, and callback
244 // when contexts are invalidated to release their ports.
245 void ReleasePort(int port_id) {
246 if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) {
247 // Send via the RenderThread because the RenderFrame might be closing.
248 content::RenderThread::Get()->Send(
249 new ExtensionHostMsg_CloseChannel(port_id, std::string()));
250 ClearPortDataAndNotifyDispatcher(port_id);
254 // void BindToGC(object, callback, port_id)
256 // Binds |callback| to be invoked *sometime after* |object| is garbage
257 // collected. We don't call the method re-entrantly so as to avoid executing
258 // JS in some bizarro undefined mid-GC state, nor do we then call into the
259 // script context if it's been invalidated.
261 // If the script context *is* invalidated in the meantime, as a slight hack,
262 // release the port with ID |port_id| if it's >= 0.
263 void BindToGC(const v8::FunctionCallbackInfo<v8::Value>& args) {
264 CHECK(args.Length() == 3 && args[0]->IsObject() && args[1]->IsFunction() &&
265 args[2]->IsInt32());
266 int port_id = args[2]->Int32Value();
267 base::Closure fallback = base::Bind(&base::DoNothing);
268 if (port_id >= 0) {
269 fallback = base::Bind(&ExtensionImpl::ReleasePort,
270 weak_ptr_factory_.GetWeakPtr(), port_id);
272 // Destroys itself when the object is GC'd or context is invalidated.
273 new GCCallback(context(), args[0].As<v8::Object>(),
274 args[1].As<v8::Function>(), fallback);
277 // Dispatcher handle. Not owned.
278 Dispatcher* dispatcher_;
280 base::WeakPtrFactory<ExtensionImpl> weak_ptr_factory_;
283 void DispatchOnConnectToScriptContext(
284 int target_port_id,
285 const std::string& channel_name,
286 const ExtensionMsg_TabConnectionInfo* source,
287 const ExtensionMsg_ExternalConnectionInfo& info,
288 const std::string& tls_channel_id,
289 bool* port_created,
290 ScriptContext* script_context) {
291 // Only dispatch the events if this is the requested target frame (0 = main
292 // frame; positive = child frame).
293 content::RenderFrame* renderframe = script_context->GetRenderFrame();
294 if (info.target_frame_id == 0 && renderframe->GetWebFrame()->parent() != NULL)
295 return;
296 if (info.target_frame_id > 0 &&
297 renderframe->GetRoutingID() != info.target_frame_id)
298 return;
299 v8::Isolate* isolate = script_context->isolate();
300 v8::HandleScope handle_scope(isolate);
302 scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
304 const std::string& source_url_spec = info.source_url.spec();
305 std::string target_extension_id = script_context->GetExtensionID();
306 const Extension* extension = script_context->extension();
308 v8::Local<v8::Value> tab = v8::Null(isolate);
309 v8::Local<v8::Value> tls_channel_id_value = v8::Undefined(isolate);
310 v8::Local<v8::Value> guest_process_id = v8::Undefined(isolate);
312 if (extension) {
313 if (!source->tab.empty() && !extension->is_platform_app())
314 tab = converter->ToV8Value(&source->tab, script_context->v8_context());
316 ExternallyConnectableInfo* externally_connectable =
317 ExternallyConnectableInfo::Get(extension);
318 if (externally_connectable &&
319 externally_connectable->accepts_tls_channel_id) {
320 tls_channel_id_value = v8::String::NewFromUtf8(isolate,
321 tls_channel_id.c_str(),
322 v8::String::kNormalString,
323 tls_channel_id.size());
326 if (info.guest_process_id != content::ChildProcessHost::kInvalidUniqueID)
327 guest_process_id = v8::Integer::New(isolate, info.guest_process_id);
330 v8::Local<v8::Value> arguments[] = {
331 // portId
332 v8::Integer::New(isolate, target_port_id),
333 // channelName
334 v8::String::NewFromUtf8(isolate, channel_name.c_str(),
335 v8::String::kNormalString, channel_name.size()),
336 // sourceTab
337 tab,
338 // source_frame_id
339 v8::Integer::New(isolate, source->frame_id),
340 // guestProcessId
341 guest_process_id,
342 // sourceExtensionId
343 v8::String::NewFromUtf8(isolate, info.source_id.c_str(),
344 v8::String::kNormalString, info.source_id.size()),
345 // targetExtensionId
346 v8::String::NewFromUtf8(isolate, target_extension_id.c_str(),
347 v8::String::kNormalString,
348 target_extension_id.size()),
349 // sourceUrl
350 v8::String::NewFromUtf8(isolate, source_url_spec.c_str(),
351 v8::String::kNormalString,
352 source_url_spec.size()),
353 // tlsChannelId
354 tls_channel_id_value,
357 v8::Local<v8::Value> retval =
358 script_context->module_system()->CallModuleMethod(
359 "messaging", "dispatchOnConnect", arraysize(arguments), arguments);
361 if (!retval.IsEmpty()) {
362 CHECK(retval->IsBoolean());
363 *port_created |= retval->BooleanValue();
364 } else {
365 LOG(ERROR) << "Empty return value from dispatchOnConnect.";
369 void DeliverMessageToScriptContext(const Message& message,
370 int target_port_id,
371 ScriptContext* script_context) {
372 v8::Isolate* isolate = script_context->isolate();
373 v8::HandleScope handle_scope(isolate);
375 // Check to see whether the context has this port before bothering to create
376 // the message.
377 v8::Local<v8::Value> port_id_handle =
378 v8::Integer::New(isolate, target_port_id);
379 v8::Local<v8::Value> has_port =
380 script_context->module_system()->CallModuleMethod("messaging", "hasPort",
381 1, &port_id_handle);
383 CHECK(!has_port.IsEmpty());
384 if (!has_port->BooleanValue())
385 return;
387 std::vector<v8::Local<v8::Value>> arguments;
388 arguments.push_back(v8::String::NewFromUtf8(isolate,
389 message.data.c_str(),
390 v8::String::kNormalString,
391 message.data.size()));
392 arguments.push_back(port_id_handle);
394 scoped_ptr<blink::WebScopedUserGesture> web_user_gesture;
395 scoped_ptr<blink::WebScopedWindowFocusAllowedIndicator> allow_window_focus;
396 if (message.user_gesture) {
397 web_user_gesture.reset(new blink::WebScopedUserGesture);
399 if (script_context->web_frame()) {
400 blink::WebDocument document = script_context->web_frame()->document();
401 allow_window_focus.reset(new blink::WebScopedWindowFocusAllowedIndicator(
402 &document));
406 script_context->module_system()->CallModuleMethod(
407 "messaging", "dispatchOnMessage", &arguments);
410 void DispatchOnDisconnectToScriptContext(int port_id,
411 const std::string& error_message,
412 ScriptContext* script_context) {
413 v8::Isolate* isolate = script_context->isolate();
414 v8::HandleScope handle_scope(isolate);
416 std::vector<v8::Local<v8::Value>> arguments;
417 arguments.push_back(v8::Integer::New(isolate, port_id));
418 if (!error_message.empty()) {
419 arguments.push_back(
420 v8::String::NewFromUtf8(isolate, error_message.c_str()));
421 } else {
422 arguments.push_back(v8::Null(isolate));
425 script_context->module_system()->CallModuleMethod(
426 "messaging", "dispatchOnDisconnect", &arguments);
429 } // namespace
431 ObjectBackedNativeHandler* MessagingBindings::Get(Dispatcher* dispatcher,
432 ScriptContext* context) {
433 return new ExtensionImpl(dispatcher, context);
436 // static
437 void MessagingBindings::DispatchOnConnect(
438 const ScriptContextSet& context_set,
439 int target_port_id,
440 const std::string& channel_name,
441 const ExtensionMsg_TabConnectionInfo& source,
442 const ExtensionMsg_ExternalConnectionInfo& info,
443 const std::string& tls_channel_id,
444 content::RenderFrame* restrict_to_render_frame) {
445 // TODO(robwu): ScriptContextSet.ForEach should accept RenderFrame*.
446 content::RenderView* restrict_to_render_view =
447 restrict_to_render_frame ? restrict_to_render_frame->GetRenderView()
448 : NULL;
449 bool port_created = false;
450 context_set.ForEach(
451 info.target_id, restrict_to_render_view,
452 base::Bind(&DispatchOnConnectToScriptContext, target_port_id,
453 channel_name, &source, info, tls_channel_id, &port_created));
455 // If we didn't create a port, notify the other end of the channel (treat it
456 // as a disconnect).
457 if (!port_created) {
458 content::RenderThread::Get()->Send(new ExtensionHostMsg_CloseChannel(
459 target_port_id, kReceivingEndDoesntExistError));
463 // static
464 void MessagingBindings::DeliverMessage(
465 const ScriptContextSet& context_set,
466 int target_port_id,
467 const Message& message,
468 content::RenderFrame* restrict_to_render_frame) {
469 // TODO(robwu): ScriptContextSet.ForEach should accept RenderFrame*.
470 content::RenderView* restrict_to_render_view =
471 restrict_to_render_frame ? restrict_to_render_frame->GetRenderView()
472 : NULL;
473 context_set.ForEach(
474 restrict_to_render_view,
475 base::Bind(&DeliverMessageToScriptContext, message, target_port_id));
478 // static
479 void MessagingBindings::DispatchOnDisconnect(
480 const ScriptContextSet& context_set,
481 int port_id,
482 const std::string& error_message,
483 content::RenderFrame* restrict_to_render_frame) {
484 // TODO(robwu): ScriptContextSet.ForEach should accept RenderFrame*.
485 content::RenderView* restrict_to_render_view =
486 restrict_to_render_frame ? restrict_to_render_frame->GetRenderView()
487 : NULL;
488 context_set.ForEach(
489 restrict_to_render_view,
490 base::Bind(&DispatchOnDisconnectToScriptContext, port_id, error_message));
493 } // namespace extensions