Index: trunk/src/extensions/renderer/resources/messaging.js |
=================================================================== |
--- trunk/src/extensions/renderer/resources/messaging.js (revision 274563) |
+++ trunk/src/extensions/renderer/resources/messaging.js (working copy) |
@@ -1,386 +0,0 @@ |
-// Copyright 2014 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-// This contains unprivileged javascript APIs for extensions and apps. It |
-// can be loaded by any extension-related context, such as content scripts or |
-// background pages. See user_script_slave.cc for script that is loaded by |
-// content scripts only. |
- |
- // TODO(kalman): factor requiring chrome out of here. |
- var chrome = requireNative('chrome').GetChrome(); |
- var Event = require('event_bindings').Event; |
- var lastError = require('lastError'); |
- var logActivity = requireNative('activityLogger'); |
- var messagingNatives = requireNative('messaging_natives'); |
- var processNatives = requireNative('process'); |
- var unloadEvent = require('unload_event'); |
- var utils = require('utils'); |
- var messagingUtils = require('messaging_utils'); |
- |
- // The reserved channel name for the sendRequest/send(Native)Message APIs. |
- // Note: sendRequest is deprecated. |
- var kRequestChannel = "chrome.extension.sendRequest"; |
- var kMessageChannel = "chrome.runtime.sendMessage"; |
- var kNativeMessageChannel = "chrome.runtime.sendNativeMessage"; |
- |
- // Map of port IDs to port object. |
- var ports = {}; |
- |
- // Map of port IDs to unloadEvent listeners. Keep track of these to free the |
- // unloadEvent listeners when ports are closed. |
- var portReleasers = {}; |
- |
- // Change even to odd and vice versa, to get the other side of a given |
- // channel. |
- function getOppositePortId(portId) { return portId ^ 1; } |
- |
- // Port object. Represents a connection to another script context through |
- // which messages can be passed. |
- function PortImpl(portId, opt_name) { |
- this.portId_ = portId; |
- this.name = opt_name; |
- |
- var portSchema = {name: 'port', $ref: 'runtime.Port'}; |
- var options = {unmanaged: true}; |
- this.onDisconnect = new Event(null, [portSchema], options); |
- this.onMessage = new Event( |
- null, |
- [{name: 'message', type: 'any', optional: true}, portSchema], |
- options); |
- this.onDestroy_ = null; |
- } |
- |
- // Sends a message asynchronously to the context on the other end of this |
- // port. |
- PortImpl.prototype.postMessage = function(msg) { |
- // JSON.stringify doesn't support a root object which is undefined. |
- if (msg === undefined) |
- msg = null; |
- msg = $JSON.stringify(msg); |
- if (msg === undefined) { |
- // JSON.stringify can fail with unserializable objects. Log an error and |
- // drop the message. |
- // |
- // TODO(kalman/mpcomplete): it would be better to do the same validation |
- // here that we do for runtime.sendMessage (and variants), i.e. throw an |
- // schema validation Error, but just maintain the old behaviour until |
- // there's a good reason not to (http://crbug.com/263077). |
- console.error('Illegal argument to Port.postMessage'); |
- return; |
- } |
- messagingNatives.PostMessage(this.portId_, msg); |
- }; |
- |
- // Disconnects the port from the other end. |
- PortImpl.prototype.disconnect = function() { |
- messagingNatives.CloseChannel(this.portId_, true); |
- this.destroy_(); |
- }; |
- |
- PortImpl.prototype.destroy_ = function() { |
- var portId = this.portId_; |
- |
- if (this.onDestroy_) |
- this.onDestroy_(); |
- privates(this.onDisconnect).impl.destroy_(); |
- privates(this.onMessage).impl.destroy_(); |
- |
- messagingNatives.PortRelease(portId); |
- unloadEvent.removeListener(portReleasers[portId]); |
- |
- delete ports[portId]; |
- delete portReleasers[portId]; |
- }; |
- |
- // Returns true if the specified port id is in this context. This is used by |
- // the C++ to avoid creating the javascript message for all the contexts that |
- // don't care about a particular message. |
- function hasPort(portId) { |
- return portId in ports; |
- }; |
- |
- // Hidden port creation function. We don't want to expose an API that lets |
- // people add arbitrary port IDs to the port list. |
- function createPort(portId, opt_name) { |
- if (ports[portId]) |
- throw new Error("Port '" + portId + "' already exists."); |
- var port = new Port(portId, opt_name); |
- ports[portId] = port; |
- portReleasers[portId] = $Function.bind(messagingNatives.PortRelease, |
- this, |
- portId); |
- unloadEvent.addListener(portReleasers[portId]); |
- messagingNatives.PortAddRef(portId); |
- return port; |
- }; |
- |
- // Helper function for dispatchOnRequest. |
- function handleSendRequestError(isSendMessage, |
- responseCallbackPreserved, |
- sourceExtensionId, |
- targetExtensionId, |
- sourceUrl) { |
- var errorMsg = []; |
- var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; |
- if (isSendMessage && !responseCallbackPreserved) { |
- $Array.push(errorMsg, |
- "The chrome." + eventName + " listener must return true if you " + |
- "want to send a response after the listener returns"); |
- } else { |
- $Array.push(errorMsg, |
- "Cannot send a response more than once per chrome." + eventName + |
- " listener per document"); |
- } |
- $Array.push(errorMsg, "(message was sent by extension" + sourceExtensionId); |
- if (sourceExtensionId != "" && sourceExtensionId != targetExtensionId) |
- $Array.push(errorMsg, "for extension " + targetExtensionId); |
- if (sourceUrl != "") |
- $Array.push(errorMsg, "for URL " + sourceUrl); |
- lastError.set(eventName, errorMsg.join(" ") + ").", null, chrome); |
- } |
- |
- // Helper function for dispatchOnConnect |
- function dispatchOnRequest(portId, channelName, sender, |
- sourceExtensionId, targetExtensionId, sourceUrl, |
- isExternal) { |
- var isSendMessage = channelName == kMessageChannel; |
- var requestEvent = null; |
- if (isSendMessage) { |
- if (chrome.runtime) { |
- requestEvent = isExternal ? chrome.runtime.onMessageExternal |
- : chrome.runtime.onMessage; |
- } |
- } else { |
- if (chrome.extension) { |
- requestEvent = isExternal ? chrome.extension.onRequestExternal |
- : chrome.extension.onRequest; |
- } |
- } |
- if (!requestEvent) |
- return false; |
- if (!requestEvent.hasListeners()) |
- return false; |
- var port = createPort(portId, channelName); |
- |
- function messageListener(request) { |
- var responseCallbackPreserved = false; |
- var responseCallback = function(response) { |
- if (port) { |
- port.postMessage(response); |
- privates(port).impl.destroy_(); |
- port = null; |
- } else { |
- // We nulled out port when sending the response, and now the page |
- // is trying to send another response for the same request. |
- handleSendRequestError(isSendMessage, responseCallbackPreserved, |
- sourceExtensionId, targetExtensionId); |
- } |
- }; |
- // In case the extension never invokes the responseCallback, and also |
- // doesn't keep a reference to it, we need to clean up the port. Do |
- // so by attaching to the garbage collection of the responseCallback |
- // using some native hackery. |
- messagingNatives.BindToGC(responseCallback, function() { |
- if (port) { |
- privates(port).impl.destroy_(); |
- port = null; |
- } |
- }); |
- var rv = requestEvent.dispatch(request, sender, responseCallback); |
- if (isSendMessage) { |
- responseCallbackPreserved = |
- rv && rv.results && $Array.indexOf(rv.results, true) > -1; |
- if (!responseCallbackPreserved && port) { |
- // If they didn't access the response callback, they're not |
- // going to send a response, so clean up the port immediately. |
- privates(port).impl.destroy_(); |
- port = null; |
- } |
- } |
- } |
- |
- privates(port).impl.onDestroy_ = function() { |
- port.onMessage.removeListener(messageListener); |
- }; |
- port.onMessage.addListener(messageListener); |
- |
- var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; |
- if (isExternal) |
- eventName += "External"; |
- logActivity.LogEvent(targetExtensionId, |
- eventName, |
- [sourceExtensionId, sourceUrl]); |
- return true; |
- } |
- |
- // Called by native code when a channel has been opened to this context. |
- function dispatchOnConnect(portId, |
- channelName, |
- sourceTab, |
- sourceExtensionId, |
- targetExtensionId, |
- sourceUrl, |
- tlsChannelId) { |
- // Only create a new Port if someone is actually listening for a connection. |
- // In addition to being an optimization, this also fixes a bug where if 2 |
- // channels were opened to and from the same process, closing one would |
- // close both. |
- var extensionId = processNatives.GetExtensionId(); |
- if (targetExtensionId != extensionId) |
- return false; // not for us |
- |
- if (ports[getOppositePortId(portId)]) |
- return false; // this channel was opened by us, so ignore it |
- |
- // Determine whether this is coming from another extension, so we can use |
- // the right event. |
- var isExternal = sourceExtensionId != extensionId; |
- |
- var sender = {}; |
- if (sourceExtensionId != '') |
- sender.id = sourceExtensionId; |
- if (sourceUrl) |
- sender.url = sourceUrl; |
- if (sourceTab) |
- sender.tab = sourceTab; |
- if (tlsChannelId !== undefined) |
- sender.tlsChannelId = tlsChannelId; |
- |
- // Special case for sendRequest/onRequest and sendMessage/onMessage. |
- if (channelName == kRequestChannel || channelName == kMessageChannel) { |
- return dispatchOnRequest(portId, channelName, sender, |
- sourceExtensionId, targetExtensionId, sourceUrl, |
- isExternal); |
- } |
- |
- var connectEvent = null; |
- if (chrome.runtime) { |
- connectEvent = isExternal ? chrome.runtime.onConnectExternal |
- : chrome.runtime.onConnect; |
- } |
- if (!connectEvent) |
- return false; |
- if (!connectEvent.hasListeners()) |
- return false; |
- |
- var port = createPort(portId, channelName); |
- port.sender = sender; |
- if (processNatives.manifestVersion < 2) |
- port.tab = port.sender.tab; |
- |
- var eventName = (isExternal ? |
- "runtime.onConnectExternal" : "runtime.onConnect"); |
- connectEvent.dispatch(port); |
- logActivity.LogEvent(targetExtensionId, |
- eventName, |
- [sourceExtensionId]); |
- return true; |
- }; |
- |
- // Called by native code when a channel has been closed. |
- function dispatchOnDisconnect(portId, errorMessage) { |
- var port = ports[portId]; |
- if (port) { |
- // Update the renderer's port bookkeeping, without notifying the browser. |
- messagingNatives.CloseChannel(portId, false); |
- if (errorMessage) |
- lastError.set('Port', errorMessage, null, chrome); |
- try { |
- port.onDisconnect.dispatch(port); |
- } finally { |
- privates(port).impl.destroy_(); |
- lastError.clear(chrome); |
- } |
- } |
- }; |
- |
- // Called by native code when a message has been sent to the given port. |
- function dispatchOnMessage(msg, portId) { |
- var port = ports[portId]; |
- if (port) { |
- if (msg) |
- msg = $JSON.parse(msg); |
- port.onMessage.dispatch(msg, port); |
- } |
- }; |
- |
- // Shared implementation used by tabs.sendMessage and runtime.sendMessage. |
- function sendMessageImpl(port, request, responseCallback) { |
- if (port.name != kNativeMessageChannel) |
- port.postMessage(request); |
- |
- if (port.name == kMessageChannel && !responseCallback) { |
- // TODO(mpcomplete): Do this for the old sendRequest API too, after |
- // verifying it doesn't break anything. |
- // Go ahead and disconnect immediately if the sender is not expecting |
- // a response. |
- port.disconnect(); |
- return; |
- } |
- |
- // Ensure the callback exists for the older sendRequest API. |
- if (!responseCallback) |
- responseCallback = function() {}; |
- |
- // Note: make sure to manually remove the onMessage/onDisconnect listeners |
- // that we added before destroying the Port, a workaround to a bug in Port |
- // where any onMessage/onDisconnect listeners added but not removed will |
- // be leaked when the Port is destroyed. |
- // http://crbug.com/320723 tracks a sustainable fix. |
- |
- function disconnectListener() { |
- // For onDisconnects, we only notify the callback if there was an error. |
- if (chrome.runtime && chrome.runtime.lastError) |
- responseCallback(); |
- } |
- |
- function messageListener(response) { |
- try { |
- responseCallback(response); |
- } finally { |
- port.disconnect(); |
- } |
- } |
- |
- privates(port).impl.onDestroy_ = function() { |
- port.onDisconnect.removeListener(disconnectListener); |
- port.onMessage.removeListener(messageListener); |
- }; |
- port.onDisconnect.addListener(disconnectListener); |
- port.onMessage.addListener(messageListener); |
- }; |
- |
- function sendMessageUpdateArguments(functionName, hasOptionsArgument) { |
- // skip functionName and hasOptionsArgument |
- var args = $Array.slice(arguments, 2); |
- var alignedArgs = messagingUtils.alignSendMessageArguments(args, |
- hasOptionsArgument); |
- if (!alignedArgs) |
- throw new Error('Invalid arguments to ' + functionName + '.'); |
- return alignedArgs; |
- } |
- |
-var Port = utils.expose('Port', PortImpl, { functions: [ |
- 'disconnect', |
- 'postMessage' |
- ], |
- properties: [ |
- 'name', |
- 'onDisconnect', |
- 'onMessage' |
- ] }); |
- |
-exports.kRequestChannel = kRequestChannel; |
-exports.kMessageChannel = kMessageChannel; |
-exports.kNativeMessageChannel = kNativeMessageChannel; |
-exports.Port = Port; |
-exports.createPort = createPort; |
-exports.sendMessageImpl = sendMessageImpl; |
-exports.sendMessageUpdateArguments = sendMessageUpdateArguments; |
- |
-// For C++ code to call. |
-exports.hasPort = hasPort; |
-exports.dispatchOnConnect = dispatchOnConnect; |
-exports.dispatchOnDisconnect = dispatchOnDisconnect; |
-exports.dispatchOnMessage = dispatchOnMessage; |