Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / extensions / renderer / resources / messaging.js
blobe39795bdc0b1f00c4c8ec640ed7f2ed60a1d6dcf
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 // chrome.runtime.messaging API implementation.
7 // TODO(kalman): factor requiring chrome out of here.
8 var chrome = requireNative('chrome').GetChrome();
9 var Event = require('event_bindings').Event;
10 var lastError = require('lastError');
11 var logActivity = requireNative('activityLogger');
12 var logging = requireNative('logging');
13 var messagingNatives = requireNative('messaging_natives');
14 var processNatives = requireNative('process');
15 var unloadEvent = require('unload_event');
16 var utils = require('utils');
17 var messagingUtils = require('messaging_utils');
19 // The reserved channel name for the sendRequest/send(Native)Message APIs.
20 // Note: sendRequest is deprecated.
21 var kRequestChannel = "chrome.extension.sendRequest";
22 var kMessageChannel = "chrome.runtime.sendMessage";
23 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage";
25 // Map of port IDs to port object.
26 var ports = {};
28 // Map of port IDs to unloadEvent listeners. Keep track of these to free the
29 // unloadEvent listeners when ports are closed.
30 var portReleasers = {};
32 // Change even to odd and vice versa, to get the other side of a given
33 // channel.
34 function getOppositePortId(portId) { return portId ^ 1; }
36 // Port object. Represents a connection to another script context through
37 // which messages can be passed.
38 function PortImpl(portId, opt_name) {
39 this.portId_ = portId;
40 this.name = opt_name;
42 var portSchema = {name: 'port', $ref: 'runtime.Port'};
43 var options = {unmanaged: true};
44 this.onDisconnect = new Event(null, [portSchema], options);
45 this.onMessage = new Event(
46 null,
47 [{name: 'message', type: 'any', optional: true}, portSchema],
48 options);
49 this.onDestroy_ = null;
52 // Sends a message asynchronously to the context on the other end of this
53 // port.
54 PortImpl.prototype.postMessage = function(msg) {
55 // JSON.stringify doesn't support a root object which is undefined.
56 if (msg === undefined)
57 msg = null;
58 msg = $JSON.stringify(msg);
59 if (msg === undefined) {
60 // JSON.stringify can fail with unserializable objects. Log an error and
61 // drop the message.
63 // TODO(kalman/mpcomplete): it would be better to do the same validation
64 // here that we do for runtime.sendMessage (and variants), i.e. throw an
65 // schema validation Error, but just maintain the old behaviour until
66 // there's a good reason not to (http://crbug.com/263077).
67 console.error('Illegal argument to Port.postMessage');
68 return;
70 messagingNatives.PostMessage(this.portId_, msg);
73 // Disconnects the port from the other end.
74 PortImpl.prototype.disconnect = function() {
75 messagingNatives.CloseChannel(this.portId_, true);
76 this.destroy_();
79 PortImpl.prototype.destroy_ = function() {
80 var portId = this.portId_;
82 if (this.onDestroy_)
83 this.onDestroy_();
84 privates(this.onDisconnect).impl.destroy_();
85 privates(this.onMessage).impl.destroy_();
87 messagingNatives.PortRelease(portId);
88 unloadEvent.removeListener(portReleasers[portId]);
90 delete ports[portId];
91 delete portReleasers[portId];
94 // Returns true if the specified port id is in this context. This is used by
95 // the C++ to avoid creating the javascript message for all the contexts that
96 // don't care about a particular message.
97 function hasPort(portId) {
98 return portId in ports;
101 // Hidden port creation function. We don't want to expose an API that lets
102 // people add arbitrary port IDs to the port list.
103 function createPort(portId, opt_name) {
104 if (ports[portId])
105 throw new Error("Port '" + portId + "' already exists.");
106 var port = new Port(portId, opt_name);
107 ports[portId] = port;
108 portReleasers[portId] = $Function.bind(messagingNatives.PortRelease,
109 this,
110 portId);
111 unloadEvent.addListener(portReleasers[portId]);
112 messagingNatives.PortAddRef(portId);
113 return port;
116 // Helper function for dispatchOnRequest.
117 function handleSendRequestError(isSendMessage,
118 responseCallbackPreserved,
119 sourceExtensionId,
120 targetExtensionId,
121 sourceUrl) {
122 var errorMsg = [];
123 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
124 if (isSendMessage && !responseCallbackPreserved) {
125 $Array.push(errorMsg,
126 "The chrome." + eventName + " listener must return true if you " +
127 "want to send a response after the listener returns");
128 } else {
129 $Array.push(errorMsg,
130 "Cannot send a response more than once per chrome." + eventName +
131 " listener per document");
133 $Array.push(errorMsg, "(message was sent by extension" + sourceExtensionId);
134 if (sourceExtensionId != "" && sourceExtensionId != targetExtensionId)
135 $Array.push(errorMsg, "for extension " + targetExtensionId);
136 if (sourceUrl != "")
137 $Array.push(errorMsg, "for URL " + sourceUrl);
138 lastError.set(eventName, errorMsg.join(" ") + ").", null, chrome);
141 // Helper function for dispatchOnConnect
142 function dispatchOnRequest(portId, channelName, sender,
143 sourceExtensionId, targetExtensionId, sourceUrl,
144 isExternal) {
145 var isSendMessage = channelName == kMessageChannel;
146 var requestEvent = null;
147 if (isSendMessage) {
148 if (chrome.runtime) {
149 requestEvent = isExternal ? chrome.runtime.onMessageExternal
150 : chrome.runtime.onMessage;
152 } else {
153 if (chrome.extension) {
154 requestEvent = isExternal ? chrome.extension.onRequestExternal
155 : chrome.extension.onRequest;
158 if (!requestEvent)
159 return false;
160 if (!requestEvent.hasListeners())
161 return false;
162 var port = createPort(portId, channelName);
164 function messageListener(request) {
165 var responseCallbackPreserved = false;
166 var responseCallback = function(response) {
167 if (port) {
168 port.postMessage(response);
169 privates(port).impl.destroy_();
170 port = null;
171 } else {
172 // We nulled out port when sending the response, and now the page
173 // is trying to send another response for the same request.
174 handleSendRequestError(isSendMessage, responseCallbackPreserved,
175 sourceExtensionId, targetExtensionId);
178 // In case the extension never invokes the responseCallback, and also
179 // doesn't keep a reference to it, we need to clean up the port. Do
180 // so by attaching to the garbage collection of the responseCallback
181 // using some native hackery.
183 // If the context is destroyed before this has a chance to execute,
184 // BindToGC knows to release |portId| (important for updating C++ state
185 // both in this renderer and on the other end). We don't need to clear
186 // any JavaScript state, as calling destroy_() would usually do - but
187 // the context has been destroyed, so there isn't any JS state to clear.
188 messagingNatives.BindToGC(responseCallback, function() {
189 if (port) {
190 privates(port).impl.destroy_();
191 port = null;
193 }, portId);
194 var rv = requestEvent.dispatch(request, sender, responseCallback);
195 if (isSendMessage) {
196 responseCallbackPreserved =
197 rv && rv.results && $Array.indexOf(rv.results, true) > -1;
198 if (!responseCallbackPreserved && port) {
199 // If they didn't access the response callback, they're not
200 // going to send a response, so clean up the port immediately.
201 privates(port).impl.destroy_();
202 port = null;
207 privates(port).impl.onDestroy_ = function() {
208 port.onMessage.removeListener(messageListener);
210 port.onMessage.addListener(messageListener);
212 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
213 if (isExternal)
214 eventName += "External";
215 logActivity.LogEvent(targetExtensionId,
216 eventName,
217 [sourceExtensionId, sourceUrl]);
218 return true;
221 // Called by native code when a channel has been opened to this context.
222 function dispatchOnConnect(portId,
223 channelName,
224 sourceTab,
225 sourceFrameId,
226 guestProcessId,
227 sourceExtensionId,
228 targetExtensionId,
229 sourceUrl,
230 tlsChannelId) {
231 // Only create a new Port if someone is actually listening for a connection.
232 // In addition to being an optimization, this also fixes a bug where if 2
233 // channels were opened to and from the same process, closing one would
234 // close both.
235 var extensionId = processNatives.GetExtensionId();
237 // messaging_bindings.cc should ensure that this method only gets called for
238 // the right extension.
239 logging.CHECK(targetExtensionId == extensionId);
241 if (ports[getOppositePortId(portId)])
242 return false; // this channel was opened by us, so ignore it
244 // Determine whether this is coming from another extension, so we can use
245 // the right event.
246 var isExternal = sourceExtensionId != extensionId;
248 var sender = {};
249 if (sourceExtensionId != '')
250 sender.id = sourceExtensionId;
251 if (sourceUrl)
252 sender.url = sourceUrl;
253 if (sourceTab)
254 sender.tab = sourceTab;
255 if (sourceFrameId >= 0)
256 sender.frameId = sourceFrameId;
257 if (typeof guestProcessId != 'undefined') {
258 // Note that |guestProcessId| is not a standard field on MessageSender and
259 // should not be exposed to drive-by extensions; it is only exposed to
260 // component extensions.
261 logging.CHECK(processNatives.IsComponentExtension(),
262 "GuestProcessId can only be exposed to component extensions.");
263 sender.guestProcessId = guestProcessId;
265 if (typeof tlsChannelId != 'undefined')
266 sender.tlsChannelId = tlsChannelId;
268 // Special case for sendRequest/onRequest and sendMessage/onMessage.
269 if (channelName == kRequestChannel || channelName == kMessageChannel) {
270 return dispatchOnRequest(portId, channelName, sender,
271 sourceExtensionId, targetExtensionId, sourceUrl,
272 isExternal);
275 var connectEvent = null;
276 if (chrome.runtime) {
277 connectEvent = isExternal ? chrome.runtime.onConnectExternal
278 : chrome.runtime.onConnect;
280 if (!connectEvent)
281 return false;
282 if (!connectEvent.hasListeners())
283 return false;
285 var port = createPort(portId, channelName);
286 port.sender = sender;
287 if (processNatives.manifestVersion < 2)
288 port.tab = port.sender.tab;
290 var eventName = (isExternal ?
291 "runtime.onConnectExternal" : "runtime.onConnect");
292 connectEvent.dispatch(port);
293 logActivity.LogEvent(targetExtensionId,
294 eventName,
295 [sourceExtensionId]);
296 return true;
299 // Called by native code when a channel has been closed.
300 function dispatchOnDisconnect(portId, errorMessage) {
301 var port = ports[portId];
302 if (port) {
303 // Update the renderer's port bookkeeping, without notifying the browser.
304 messagingNatives.CloseChannel(portId, false);
305 if (errorMessage)
306 lastError.set('Port', errorMessage, null, chrome);
307 try {
308 port.onDisconnect.dispatch(port);
309 } finally {
310 privates(port).impl.destroy_();
311 lastError.clear(chrome);
316 // Called by native code when a message has been sent to the given port.
317 function dispatchOnMessage(msg, portId) {
318 var port = ports[portId];
319 if (port) {
320 if (msg)
321 msg = $JSON.parse(msg);
322 port.onMessage.dispatch(msg, port);
326 // Shared implementation used by tabs.sendMessage and runtime.sendMessage.
327 function sendMessageImpl(port, request, responseCallback) {
328 if (port.name != kNativeMessageChannel)
329 port.postMessage(request);
331 if (port.name == kMessageChannel && !responseCallback) {
332 // TODO(mpcomplete): Do this for the old sendRequest API too, after
333 // verifying it doesn't break anything.
334 // Go ahead and disconnect immediately if the sender is not expecting
335 // a response.
336 port.disconnect();
337 return;
340 // Ensure the callback exists for the older sendRequest API.
341 if (!responseCallback)
342 responseCallback = function() {};
344 // Note: make sure to manually remove the onMessage/onDisconnect listeners
345 // that we added before destroying the Port, a workaround to a bug in Port
346 // where any onMessage/onDisconnect listeners added but not removed will
347 // be leaked when the Port is destroyed.
348 // http://crbug.com/320723 tracks a sustainable fix.
350 function disconnectListener() {
351 // For onDisconnects, we only notify the callback if there was an error.
352 if (chrome.runtime && chrome.runtime.lastError)
353 responseCallback();
356 function messageListener(response) {
357 try {
358 responseCallback(response);
359 } finally {
360 port.disconnect();
364 privates(port).impl.onDestroy_ = function() {
365 port.onDisconnect.removeListener(disconnectListener);
366 port.onMessage.removeListener(messageListener);
368 port.onDisconnect.addListener(disconnectListener);
369 port.onMessage.addListener(messageListener);
372 function sendMessageUpdateArguments(functionName, hasOptionsArgument) {
373 // skip functionName and hasOptionsArgument
374 var args = $Array.slice(arguments, 2);
375 var alignedArgs = messagingUtils.alignSendMessageArguments(args,
376 hasOptionsArgument);
377 if (!alignedArgs)
378 throw new Error('Invalid arguments to ' + functionName + '.');
379 return alignedArgs;
382 var Port = utils.expose('Port', PortImpl, { functions: [
383 'disconnect',
384 'postMessage'
386 properties: [
387 'name',
388 'onDisconnect',
389 'onMessage'
390 ] });
392 exports.kRequestChannel = kRequestChannel;
393 exports.kMessageChannel = kMessageChannel;
394 exports.kNativeMessageChannel = kNativeMessageChannel;
395 exports.Port = Port;
396 exports.createPort = createPort;
397 exports.sendMessageImpl = sendMessageImpl;
398 exports.sendMessageUpdateArguments = sendMessageUpdateArguments;
400 // For C++ code to call.
401 exports.hasPort = hasPort;
402 exports.dispatchOnConnect = dispatchOnConnect;
403 exports.dispatchOnDisconnect = dispatchOnDisconnect;
404 exports.dispatchOnMessage = dispatchOnMessage;