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 // This contains unprivileged javascript APIs for extensions and apps. It
6 // can be loaded by any extension-related context, such as content scripts or
7 // background pages. See user_script_slave.cc for script that is loaded by
8 // content scripts only.
10 // TODO(kalman): factor requiring chrome out of here.
11 var chrome = requireNative('chrome').GetChrome();
12 var Event = require('event_bindings').Event;
13 var lastError = require('lastError');
14 var logActivity = requireNative('activityLogger');
15 var messagingNatives = requireNative('messaging_natives');
16 var processNatives = requireNative('process');
17 var unloadEvent = require('unload_event');
18 var messagingUtils = require('messaging_utils');
20 // The reserved channel name for the sendRequest/send(Native)Message APIs.
21 // Note: sendRequest is deprecated.
22 var kRequestChannel = "chrome.extension.sendRequest";
23 var kMessageChannel = "chrome.runtime.sendMessage";
24 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage";
26 // Map of port IDs to port object.
29 // Map of port IDs to unloadEvent listeners. Keep track of these to free the
30 // unloadEvent listeners when ports are closed.
31 var portReleasers = {};
33 // Change even to odd and vice versa, to get the other side of a given
35 function getOppositePortId(portId) { return portId ^ 1; }
37 // Port object. Represents a connection to another script context through
38 // which messages can be passed.
39 function Port(portId, opt_name) {
40 this.portId_ = portId;
43 var portSchema = {name: 'port', $ref: 'runtime.Port'};
44 var options = {unmanaged: true};
45 this.onDisconnect = new Event(null, [portSchema], options);
46 this.onMessage = new Event(
48 [{name: 'message', type: 'any', optional: true}, portSchema],
52 // Sends a message asynchronously to the context on the other end of this
54 Port.prototype.postMessage = function(msg) {
55 // JSON.stringify doesn't support a root object which is undefined.
56 if (msg === undefined)
58 msg = $JSON.stringify(msg);
59 if (msg === undefined) {
60 // JSON.stringify can fail with unserializable objects. Log an error and
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');
70 messagingNatives.PostMessage(this.portId_, msg);
73 // Disconnects the port from the other end.
74 Port.prototype.disconnect = function() {
75 messagingNatives.CloseChannel(this.portId_, true);
79 Port.prototype.destroy_ = function() {
80 var portId = this.portId_;
82 this.onDisconnect.destroy_();
83 this.onMessage.destroy_();
85 messagingNatives.PortRelease(portId);
86 unloadEvent.removeListener(portReleasers[portId]);
89 delete portReleasers[portId];
92 // Returns true if the specified port id is in this context. This is used by
93 // the C++ to avoid creating the javascript message for all the contexts that
94 // don't care about a particular message.
95 function hasPort(portId) {
96 return portId in ports;
99 // Hidden port creation function. We don't want to expose an API that lets
100 // people add arbitrary port IDs to the port list.
101 function createPort(portId, opt_name) {
103 throw new Error("Port '" + portId + "' already exists.");
104 var port = new Port(portId, opt_name);
105 ports[portId] = port;
106 portReleasers[portId] = $Function.bind(messagingNatives.PortRelease,
109 unloadEvent.addListener(portReleasers[portId]);
110 messagingNatives.PortAddRef(portId);
114 // Helper function for dispatchOnRequest.
115 function handleSendRequestError(isSendMessage,
116 responseCallbackPreserved,
121 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
122 if (isSendMessage && !responseCallbackPreserved) {
123 $Array.push(errorMsg,
124 "The chrome." + eventName + " listener must return true if you " +
125 "want to send a response after the listener returns");
127 $Array.push(errorMsg,
128 "Cannot send a response more than once per chrome." + eventName +
129 " listener per document");
131 $Array.push(errorMsg, "(message was sent by extension" + sourceExtensionId);
132 if (sourceExtensionId != "" && sourceExtensionId != targetExtensionId)
133 $Array.push(errorMsg, "for extension " + targetExtensionId);
135 $Array.push(errorMsg, "for URL " + sourceUrl);
136 lastError.set(eventName, errorMsg.join(" ") + ").", null, chrome);
139 // Helper function for dispatchOnConnect
140 function dispatchOnRequest(portId, channelName, sender,
141 sourceExtensionId, targetExtensionId, sourceUrl,
143 var isSendMessage = channelName == kMessageChannel;
144 var requestEvent = null;
146 if (chrome.runtime) {
147 requestEvent = isExternal ? chrome.runtime.onMessageExternal
148 : chrome.runtime.onMessage;
151 if (chrome.extension) {
152 requestEvent = isExternal ? chrome.extension.onRequestExternal
153 : chrome.extension.onRequest;
158 if (!requestEvent.hasListeners())
160 var port = createPort(portId, channelName);
161 port.onMessage.addListener(function(request) {
162 var responseCallbackPreserved = false;
163 var responseCallback = function(response) {
165 port.postMessage(response);
169 // We nulled out port when sending the response, and now the page
170 // is trying to send another response for the same request.
171 handleSendRequestError(isSendMessage, responseCallbackPreserved,
172 sourceExtensionId, targetExtensionId);
175 // In case the extension never invokes the responseCallback, and also
176 // doesn't keep a reference to it, we need to clean up the port. Do
177 // so by attaching to the garbage collection of the responseCallback
178 // using some native hackery.
179 messagingNatives.BindToGC(responseCallback, function() {
185 if (!isSendMessage) {
186 requestEvent.dispatch(request, sender, responseCallback);
188 var rv = requestEvent.dispatch(request, sender, responseCallback);
189 responseCallbackPreserved =
190 rv && rv.results && rv.results.indexOf(true) > -1;
191 if (!responseCallbackPreserved && port) {
192 // If they didn't access the response callback, they're not
193 // going to send a response, so clean up the port immediately.
199 var eventName = (isSendMessage ?
201 "runtime.onMessageExternal" : "runtime.onMessage") :
203 "extension.onRequestExternal" : "extension.onRequest"));
204 logActivity.LogEvent(targetExtensionId,
206 [sourceExtensionId, sourceUrl]);
210 // Called by native code when a channel has been opened to this context.
211 function dispatchOnConnect(portId,
218 // Only create a new Port if someone is actually listening for a connection.
219 // In addition to being an optimization, this also fixes a bug where if 2
220 // channels were opened to and from the same process, closing one would
222 var extensionId = processNatives.GetExtensionId();
223 if (targetExtensionId != extensionId)
224 return false; // not for us
226 if (ports[getOppositePortId(portId)])
227 return false; // this channel was opened by us, so ignore it
229 // Determine whether this is coming from another extension, so we can use
231 var isExternal = sourceExtensionId != extensionId;
234 if (sourceExtensionId != '')
235 sender.id = sourceExtensionId;
237 sender.url = sourceUrl;
239 sender.tab = sourceTab;
240 if (tlsChannelId !== undefined)
241 sender.tlsChannelId = tlsChannelId;
243 // Special case for sendRequest/onRequest and sendMessage/onMessage.
244 if (channelName == kRequestChannel || channelName == kMessageChannel) {
245 return dispatchOnRequest(portId, channelName, sender,
246 sourceExtensionId, targetExtensionId, sourceUrl,
250 var connectEvent = null;
251 if (chrome.runtime) {
252 connectEvent = isExternal ? chrome.runtime.onConnectExternal
253 : chrome.runtime.onConnect;
257 if (!connectEvent.hasListeners())
260 var port = createPort(portId, channelName);
261 port.sender = sender;
262 if (processNatives.manifestVersion < 2)
263 port.tab = port.sender.tab;
265 var eventName = (isExternal ?
266 "runtime.onConnectExternal" : "runtime.onConnect");
267 connectEvent.dispatch(port);
268 logActivity.LogEvent(targetExtensionId,
270 [sourceExtensionId]);
274 // Called by native code when a channel has been closed.
275 function dispatchOnDisconnect(portId, errorMessage) {
276 var port = ports[portId];
278 // Update the renderer's port bookkeeping, without notifying the browser.
279 messagingNatives.CloseChannel(portId, false);
281 lastError.set('Port', errorMessage, null, chrome);
283 port.onDisconnect.dispatch(port);
286 lastError.clear(chrome);
291 // Called by native code when a message has been sent to the given port.
292 function dispatchOnMessage(msg, portId) {
293 var port = ports[portId];
296 msg = $JSON.parse(msg);
297 port.onMessage.dispatch(msg, port);
301 // Shared implementation used by tabs.sendMessage and runtime.sendMessage.
302 function sendMessageImpl(port, request, responseCallback) {
303 if (port.name != kNativeMessageChannel)
304 port.postMessage(request);
306 if (port.name == kMessageChannel && !responseCallback) {
307 // TODO(mpcomplete): Do this for the old sendRequest API too, after
308 // verifying it doesn't break anything.
309 // Go ahead and disconnect immediately if the sender is not expecting
315 // Ensure the callback exists for the older sendRequest API.
316 if (!responseCallback)
317 responseCallback = function() {};
319 port.onDisconnect.addListener(function() {
320 // For onDisconnects, we only notify the callback if there was an error.
322 if (chrome.runtime && chrome.runtime.lastError)
328 port.onMessage.addListener(function(response) {
330 responseCallback(response);
338 function sendMessageUpdateArguments(functionName, hasOptionsArgument) {
339 // skip functionName and hasOptionsArgument
340 var args = $Array.slice(arguments, 2);
341 var alignedArgs = messagingUtils.alignSendMessageArguments(args,
344 throw new Error('Invalid arguments to ' + functionName + '.');
348 exports.kRequestChannel = kRequestChannel;
349 exports.kMessageChannel = kMessageChannel;
350 exports.kNativeMessageChannel = kNativeMessageChannel;
352 exports.createPort = createPort;
353 exports.sendMessageImpl = sendMessageImpl;
354 exports.sendMessageUpdateArguments = sendMessageUpdateArguments;
356 // For C++ code to call.
357 exports.hasPort = hasPort;
358 exports.dispatchOnConnect = dispatchOnConnect;
359 exports.dispatchOnDisconnect = dispatchOnDisconnect;
360 exports.dispatchOnMessage = dispatchOnMessage;