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