| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This contains unprivileged javascript APIs for extensions and apps. It | 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 | 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 | 7 // background pages. See user_script_slave.cc for script that is loaded by |
| 8 // content scripts only. | 8 // content scripts only. |
| 9 | 9 |
| 10 // TODO(kalman): factor requiring chrome out of here. | 10 // TODO(kalman): factor requiring chrome out of here. |
| 11 var chrome = requireNative('chrome').GetChrome(); | 11 var chrome = requireNative('chrome').GetChrome(); |
| 12 var Event = require('event_bindings').Event; | 12 var Event = require('event_bindings').Event; |
| 13 var lastError = require('lastError'); | 13 var lastError = require('lastError'); |
| 14 var logActivity = requireNative('activityLogger'); | 14 var logActivity = requireNative('activityLogger'); |
| 15 var messagingNatives = requireNative('messaging_natives'); | 15 var messagingNatives = requireNative('messaging_natives'); |
| 16 var processNatives = requireNative('process'); | 16 var processNatives = requireNative('process'); |
| 17 var unloadEvent = require('unload_event'); | 17 var unloadEvent = require('unload_event'); |
| 18 var utils = require('utils'); |
| 18 var messagingUtils = require('messaging_utils'); | 19 var messagingUtils = require('messaging_utils'); |
| 19 | 20 |
| 20 // The reserved channel name for the sendRequest/send(Native)Message APIs. | 21 // The reserved channel name for the sendRequest/send(Native)Message APIs. |
| 21 // Note: sendRequest is deprecated. | 22 // Note: sendRequest is deprecated. |
| 22 var kRequestChannel = "chrome.extension.sendRequest"; | 23 var kRequestChannel = "chrome.extension.sendRequest"; |
| 23 var kMessageChannel = "chrome.runtime.sendMessage"; | 24 var kMessageChannel = "chrome.runtime.sendMessage"; |
| 24 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage"; | 25 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage"; |
| 25 | 26 |
| 26 // Map of port IDs to port object. | 27 // Map of port IDs to port object. |
| 27 var ports = {}; | 28 var ports = {}; |
| 28 | 29 |
| 29 // Map of port IDs to unloadEvent listeners. Keep track of these to free the | 30 // Map of port IDs to unloadEvent listeners. Keep track of these to free the |
| 30 // unloadEvent listeners when ports are closed. | 31 // unloadEvent listeners when ports are closed. |
| 31 var portReleasers = {}; | 32 var portReleasers = {}; |
| 32 | 33 |
| 33 // Change even to odd and vice versa, to get the other side of a given | 34 // Change even to odd and vice versa, to get the other side of a given |
| 34 // channel. | 35 // channel. |
| 35 function getOppositePortId(portId) { return portId ^ 1; } | 36 function getOppositePortId(portId) { return portId ^ 1; } |
| 36 | 37 |
| 37 // Port object. Represents a connection to another script context through | 38 // Port object. Represents a connection to another script context through |
| 38 // which messages can be passed. | 39 // which messages can be passed. |
| 39 function Port(portId, opt_name) { | 40 function PortImpl(portId, opt_name) { |
| 40 this.portId_ = portId; | 41 this.portId_ = portId; |
| 41 this.name = opt_name; | 42 this.name = opt_name; |
| 42 | 43 |
| 43 var portSchema = {name: 'port', $ref: 'runtime.Port'}; | 44 var portSchema = {name: 'port', $ref: 'runtime.Port'}; |
| 44 var options = {unmanaged: true}; | 45 var options = {unmanaged: true}; |
| 45 this.onDisconnect = new Event(null, [portSchema], options); | 46 this.onDisconnect = new Event(null, [portSchema], options); |
| 46 this.onMessage = new Event( | 47 this.onMessage = new Event( |
| 47 null, | 48 null, |
| 48 [{name: 'message', type: 'any', optional: true}, portSchema], | 49 [{name: 'message', type: 'any', optional: true}, portSchema], |
| 49 options); | 50 options); |
| 50 this.onDestroy_ = null; | 51 this.onDestroy_ = null; |
| 51 } | 52 } |
| 52 | 53 |
| 53 // Sends a message asynchronously to the context on the other end of this | 54 // Sends a message asynchronously to the context on the other end of this |
| 54 // port. | 55 // port. |
| 55 Port.prototype.postMessage = function(msg) { | 56 PortImpl.prototype.postMessage = function(msg) { |
| 56 // JSON.stringify doesn't support a root object which is undefined. | 57 // JSON.stringify doesn't support a root object which is undefined. |
| 57 if (msg === undefined) | 58 if (msg === undefined) |
| 58 msg = null; | 59 msg = null; |
| 59 msg = $JSON.stringify(msg); | 60 msg = $JSON.stringify(msg); |
| 60 if (msg === undefined) { | 61 if (msg === undefined) { |
| 61 // JSON.stringify can fail with unserializable objects. Log an error and | 62 // JSON.stringify can fail with unserializable objects. Log an error and |
| 62 // drop the message. | 63 // drop the message. |
| 63 // | 64 // |
| 64 // TODO(kalman/mpcomplete): it would be better to do the same validation | 65 // TODO(kalman/mpcomplete): it would be better to do the same validation |
| 65 // here that we do for runtime.sendMessage (and variants), i.e. throw an | 66 // here that we do for runtime.sendMessage (and variants), i.e. throw an |
| 66 // schema validation Error, but just maintain the old behaviour until | 67 // schema validation Error, but just maintain the old behaviour until |
| 67 // there's a good reason not to (http://crbug.com/263077). | 68 // there's a good reason not to (http://crbug.com/263077). |
| 68 console.error('Illegal argument to Port.postMessage'); | 69 console.error('Illegal argument to Port.postMessage'); |
| 69 return; | 70 return; |
| 70 } | 71 } |
| 71 messagingNatives.PostMessage(this.portId_, msg); | 72 messagingNatives.PostMessage(this.portId_, msg); |
| 72 }; | 73 }; |
| 73 | 74 |
| 74 // Disconnects the port from the other end. | 75 // Disconnects the port from the other end. |
| 75 Port.prototype.disconnect = function() { | 76 PortImpl.prototype.disconnect = function() { |
| 76 messagingNatives.CloseChannel(this.portId_, true); | 77 messagingNatives.CloseChannel(this.portId_, true); |
| 77 this.destroy_(); | 78 this.destroy_(); |
| 78 }; | 79 }; |
| 79 | 80 |
| 80 Port.prototype.destroy_ = function() { | 81 PortImpl.prototype.destroy_ = function() { |
| 81 var portId = this.portId_; | 82 var portId = this.portId_; |
| 82 | 83 |
| 83 if (this.onDestroy_) | 84 if (this.onDestroy_) |
| 84 this.onDestroy_(); | 85 this.onDestroy_(); |
| 85 privates(this.onDisconnect).impl.destroy_(); | 86 privates(this.onDisconnect).impl.destroy_(); |
| 86 privates(this.onMessage).impl.destroy_(); | 87 privates(this.onMessage).impl.destroy_(); |
| 87 | 88 |
| 88 messagingNatives.PortRelease(portId); | 89 messagingNatives.PortRelease(portId); |
| 89 unloadEvent.removeListener(portReleasers[portId]); | 90 unloadEvent.removeListener(portReleasers[portId]); |
| 90 | 91 |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 160 return false; | 161 return false; |
| 161 if (!requestEvent.hasListeners()) | 162 if (!requestEvent.hasListeners()) |
| 162 return false; | 163 return false; |
| 163 var port = createPort(portId, channelName); | 164 var port = createPort(portId, channelName); |
| 164 | 165 |
| 165 function messageListener(request) { | 166 function messageListener(request) { |
| 166 var responseCallbackPreserved = false; | 167 var responseCallbackPreserved = false; |
| 167 var responseCallback = function(response) { | 168 var responseCallback = function(response) { |
| 168 if (port) { | 169 if (port) { |
| 169 port.postMessage(response); | 170 port.postMessage(response); |
| 170 port.destroy_(); | 171 privates(port).impl.destroy_(); |
| 171 port = null; | 172 port = null; |
| 172 } else { | 173 } else { |
| 173 // We nulled out port when sending the response, and now the page | 174 // We nulled out port when sending the response, and now the page |
| 174 // is trying to send another response for the same request. | 175 // is trying to send another response for the same request. |
| 175 handleSendRequestError(isSendMessage, responseCallbackPreserved, | 176 handleSendRequestError(isSendMessage, responseCallbackPreserved, |
| 176 sourceExtensionId, targetExtensionId); | 177 sourceExtensionId, targetExtensionId); |
| 177 } | 178 } |
| 178 }; | 179 }; |
| 179 // In case the extension never invokes the responseCallback, and also | 180 // In case the extension never invokes the responseCallback, and also |
| 180 // doesn't keep a reference to it, we need to clean up the port. Do | 181 // doesn't keep a reference to it, we need to clean up the port. Do |
| 181 // so by attaching to the garbage collection of the responseCallback | 182 // so by attaching to the garbage collection of the responseCallback |
| 182 // using some native hackery. | 183 // using some native hackery. |
| 183 messagingNatives.BindToGC(responseCallback, function() { | 184 messagingNatives.BindToGC(responseCallback, function() { |
| 184 if (port) { | 185 if (port) { |
| 185 port.destroy_(); | 186 privates(port).impl.destroy_(); |
| 186 port = null; | 187 port = null; |
| 187 } | 188 } |
| 188 }); | 189 }); |
| 189 var rv = requestEvent.dispatch(request, sender, responseCallback); | 190 var rv = requestEvent.dispatch(request, sender, responseCallback); |
| 190 if (isSendMessage) { | 191 if (isSendMessage) { |
| 191 responseCallbackPreserved = | 192 responseCallbackPreserved = |
| 192 rv && rv.results && $Array.indexOf(rv.results, true) > -1; | 193 rv && rv.results && $Array.indexOf(rv.results, true) > -1; |
| 193 if (!responseCallbackPreserved && port) { | 194 if (!responseCallbackPreserved && port) { |
| 194 // If they didn't access the response callback, they're not | 195 // If they didn't access the response callback, they're not |
| 195 // going to send a response, so clean up the port immediately. | 196 // going to send a response, so clean up the port immediately. |
| 196 port.destroy_(); | 197 privates(port).impl.destroy_(); |
| 197 port = null; | 198 port = null; |
| 198 } | 199 } |
| 199 } | 200 } |
| 200 } | 201 } |
| 201 | 202 |
| 202 port.onDestroy_ = function() { | 203 privates(port).impl.onDestroy_ = function() { |
| 203 port.onMessage.removeListener(messageListener); | 204 port.onMessage.removeListener(messageListener); |
| 204 }; | 205 }; |
| 205 port.onMessage.addListener(messageListener); | 206 port.onMessage.addListener(messageListener); |
| 206 | 207 |
| 207 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; | 208 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; |
| 208 if (isExternal) | 209 if (isExternal) |
| 209 eventName += "External"; | 210 eventName += "External"; |
| 210 logActivity.LogEvent(targetExtensionId, | 211 logActivity.LogEvent(targetExtensionId, |
| 211 eventName, | 212 eventName, |
| 212 [sourceExtensionId, sourceUrl]); | 213 [sourceExtensionId, sourceUrl]); |
| (...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 281 function dispatchOnDisconnect(portId, errorMessage) { | 282 function dispatchOnDisconnect(portId, errorMessage) { |
| 282 var port = ports[portId]; | 283 var port = ports[portId]; |
| 283 if (port) { | 284 if (port) { |
| 284 // Update the renderer's port bookkeeping, without notifying the browser. | 285 // Update the renderer's port bookkeeping, without notifying the browser. |
| 285 messagingNatives.CloseChannel(portId, false); | 286 messagingNatives.CloseChannel(portId, false); |
| 286 if (errorMessage) | 287 if (errorMessage) |
| 287 lastError.set('Port', errorMessage, null, chrome); | 288 lastError.set('Port', errorMessage, null, chrome); |
| 288 try { | 289 try { |
| 289 port.onDisconnect.dispatch(port); | 290 port.onDisconnect.dispatch(port); |
| 290 } finally { | 291 } finally { |
| 291 port.destroy_(); | 292 privates(port).impl.destroy_(); |
| 292 lastError.clear(chrome); | 293 lastError.clear(chrome); |
| 293 } | 294 } |
| 294 } | 295 } |
| 295 }; | 296 }; |
| 296 | 297 |
| 297 // Called by native code when a message has been sent to the given port. | 298 // Called by native code when a message has been sent to the given port. |
| 298 function dispatchOnMessage(msg, portId) { | 299 function dispatchOnMessage(msg, portId) { |
| 299 var port = ports[portId]; | 300 var port = ports[portId]; |
| 300 if (port) { | 301 if (port) { |
| 301 if (msg) | 302 if (msg) |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 335 } | 336 } |
| 336 | 337 |
| 337 function messageListener(response) { | 338 function messageListener(response) { |
| 338 try { | 339 try { |
| 339 responseCallback(response); | 340 responseCallback(response); |
| 340 } finally { | 341 } finally { |
| 341 port.disconnect(); | 342 port.disconnect(); |
| 342 } | 343 } |
| 343 } | 344 } |
| 344 | 345 |
| 345 port.onDestroy_ = function() { | 346 privates(port).impl.onDestroy_ = function() { |
| 346 port.onDisconnect.removeListener(disconnectListener); | 347 port.onDisconnect.removeListener(disconnectListener); |
| 347 port.onMessage.removeListener(messageListener); | 348 port.onMessage.removeListener(messageListener); |
| 348 }; | 349 }; |
| 349 port.onDisconnect.addListener(disconnectListener); | 350 port.onDisconnect.addListener(disconnectListener); |
| 350 port.onMessage.addListener(messageListener); | 351 port.onMessage.addListener(messageListener); |
| 351 }; | 352 }; |
| 352 | 353 |
| 353 function sendMessageUpdateArguments(functionName, hasOptionsArgument) { | 354 function sendMessageUpdateArguments(functionName, hasOptionsArgument) { |
| 354 // skip functionName and hasOptionsArgument | 355 // skip functionName and hasOptionsArgument |
| 355 var args = $Array.slice(arguments, 2); | 356 var args = $Array.slice(arguments, 2); |
| 356 var alignedArgs = messagingUtils.alignSendMessageArguments(args, | 357 var alignedArgs = messagingUtils.alignSendMessageArguments(args, |
| 357 hasOptionsArgument); | 358 hasOptionsArgument); |
| 358 if (!alignedArgs) | 359 if (!alignedArgs) |
| 359 throw new Error('Invalid arguments to ' + functionName + '.'); | 360 throw new Error('Invalid arguments to ' + functionName + '.'); |
| 360 return alignedArgs; | 361 return alignedArgs; |
| 361 } | 362 } |
| 362 | 363 |
| 364 var Port = utils.expose(PortImpl, [ |
| 365 'disconnect', |
| 366 'postMessage' |
| 367 ], |
| 368 [ |
| 369 'name', |
| 370 'onDisconnect', |
| 371 'onMessage' |
| 372 ]); |
| 373 |
| 363 exports.kRequestChannel = kRequestChannel; | 374 exports.kRequestChannel = kRequestChannel; |
| 364 exports.kMessageChannel = kMessageChannel; | 375 exports.kMessageChannel = kMessageChannel; |
| 365 exports.kNativeMessageChannel = kNativeMessageChannel; | 376 exports.kNativeMessageChannel = kNativeMessageChannel; |
| 366 exports.Port = Port; | 377 exports.Port = Port; |
| 367 exports.createPort = createPort; | 378 exports.createPort = createPort; |
| 368 exports.sendMessageImpl = sendMessageImpl; | 379 exports.sendMessageImpl = sendMessageImpl; |
| 369 exports.sendMessageUpdateArguments = sendMessageUpdateArguments; | 380 exports.sendMessageUpdateArguments = sendMessageUpdateArguments; |
| 370 | 381 |
| 371 // For C++ code to call. | 382 // For C++ code to call. |
| 372 exports.hasPort = hasPort; | 383 exports.hasPort = hasPort; |
| 373 exports.dispatchOnConnect = dispatchOnConnect; | 384 exports.dispatchOnConnect = dispatchOnConnect; |
| 374 exports.dispatchOnDisconnect = dispatchOnDisconnect; | 385 exports.dispatchOnDisconnect = dispatchOnDisconnect; |
| 375 exports.dispatchOnMessage = dispatchOnMessage; | 386 exports.dispatchOnMessage = dispatchOnMessage; |
| OLD | NEW |