15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// This contains unprivileged javascript APIs for extensions and apps. It 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// can be loaded by any extension-related context, such as content scripts or 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// background pages. See user_script_slave.cc for script that is loaded by 82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// content scripts only. 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // TODO(kalman): factor requiring chrome out of here. 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) var chrome = requireNative('chrome').GetChrome(); 12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var Event = require('event_bindings').Event; 13868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var lastError = require('lastError'); 14c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) var logActivity = requireNative('activityLogger'); 15fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch var messagingNatives = requireNative('messaging_natives'); 16868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var processNatives = requireNative('process'); 17868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var unloadEvent = require('unload_event'); 18c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 19868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // The reserved channel name for the sendRequest/send(Native)Message APIs. 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Note: sendRequest is deprecated. 21868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var kRequestChannel = "chrome.extension.sendRequest"; 22868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var kMessageChannel = "chrome.runtime.sendMessage"; 23868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var kNativeMessageChannel = "chrome.runtime.sendNativeMessage"; 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Map of port IDs to port object. 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var ports = {}; 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 28868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // Map of port IDs to unloadEvent listeners. Keep track of these to free the 29868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // unloadEvent listeners when ports are closed. 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var portReleasers = {}; 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Change even to odd and vice versa, to get the other side of a given 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // channel. 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function getOppositePortId(portId) { return portId ^ 1; } 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Port object. Represents a connection to another script context through 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // which messages can be passed. 38868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function Port(portId, opt_name) { 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.portId_ = portId; 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.name = opt_name; 41ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch 42ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch var portSchema = {name: 'port', $ref: 'runtime.Port'}; 43ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch var options = {unmanaged: true}; 44ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch this.onDisconnect = new Event(null, [portSchema], options); 45ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch this.onMessage = new Event( 46ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch null, 47ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch [{name: 'message', type: 'any', optional: true}, portSchema], 48ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch options); 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Sends a message asynchronously to the context on the other end of this 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // port. 53868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) Port.prototype.postMessage = function(msg) { 54eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // JSON.stringify doesn't support a root object which is undefined. 55eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (msg === undefined) 56eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch msg = null; 57ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch msg = $JSON.stringify(msg); 58ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch if (msg === undefined) { 59ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // JSON.stringify can fail with unserializable objects. Log an error and 60ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // drop the message. 61ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // 62ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // TODO(kalman/mpcomplete): it would be better to do the same validation 63ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // here that we do for runtime.sendMessage (and variants), i.e. throw an 64ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // schema validation Error, but just maintain the old behaviour until 65ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // there's a good reason not to (http://crbug.com/263077). 66ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch console.error('Illegal argument to Port.postMessage'); 67ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch return; 68ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch } 69fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch messagingNatives.PostMessage(this.portId_, msg); 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Disconnects the port from the other end. 73868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) Port.prototype.disconnect = function() { 74fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch messagingNatives.CloseChannel(this.portId_, true); 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.destroy_(); 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 78868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) Port.prototype.destroy_ = function() { 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var portId = this.portId_; 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.onDisconnect.destroy_(); 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.onMessage.destroy_(); 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 84fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch messagingNatives.PortRelease(portId); 85868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) unloadEvent.removeListener(portReleasers[portId]); 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delete ports[portId]; 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delete portReleasers[portId]; 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Returns true if the specified port id is in this context. This is used by 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the C++ to avoid creating the javascript message for all the contexts that 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // don't care about a particular message. 94868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function hasPort(portId) { 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return portId in ports; 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Hidden port creation function. We don't want to expose an API that lets 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // people add arbitrary port IDs to the port list. 100868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function createPort(portId, opt_name) { 101868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (ports[portId]) 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error("Port '" + portId + "' already exists."); 103868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var port = new Port(portId, opt_name); 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ports[portId] = port; 105fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch portReleasers[portId] = $Function.bind(messagingNatives.PortRelease, 106fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch this, 107fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch portId); 108868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) unloadEvent.addListener(portReleasers[portId]); 109fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch messagingNatives.PortAddRef(portId); 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return port; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Helper function for dispatchOnRequest. 114c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) function handleSendRequestError(isSendMessage, 115c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) responseCallbackPreserved, 116c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sourceExtensionId, 117c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) targetExtensionId, 118c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sourceUrl) { 119c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) var errorMsg = []; 120c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (isSendMessage && !responseCallbackPreserved) { 122eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(errorMsg, 123c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) "The chrome." + eventName + " listener must return true if you " + 124c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) "want to send a response after the listener returns"); 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(errorMsg, 127c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) "Cannot send a response more than once per chrome." + eventName + 128c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) " listener per document"); 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 130eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(errorMsg, "(message was sent by extension" + sourceExtensionId); 131c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (sourceExtensionId != "" && sourceExtensionId != targetExtensionId) 132eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(errorMsg, "for extension " + targetExtensionId); 133c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (sourceUrl != "") 134eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch $Array.push(errorMsg, "for URL " + sourceUrl); 135c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) lastError.set(eventName, errorMsg.join(" ") + ").", null, chrome); 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Helper function for dispatchOnConnect 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function dispatchOnRequest(portId, channelName, sender, 140c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sourceExtensionId, targetExtensionId, sourceUrl, 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) isExternal) { 142868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var isSendMessage = channelName == kMessageChannel; 143868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var requestEvent = null; 144868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (isSendMessage) { 145868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (chrome.runtime) { 146868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) requestEvent = isExternal ? chrome.runtime.onMessageExternal 147868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) : chrome.runtime.onMessage; 148868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 149868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } else { 150868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (chrome.extension) { 151868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) requestEvent = isExternal ? chrome.extension.onRequestExternal 152868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) : chrome.extension.onRequest; 153868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 154868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 155868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!requestEvent) 156868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return false; 157868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!requestEvent.hasListeners()) 158868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return false; 159868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var port = createPort(portId, channelName); 160868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.onMessage.addListener(function(request) { 161868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var responseCallbackPreserved = false; 162868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var responseCallback = function(response) { 163868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (port) { 164868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.postMessage(response); 165868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.destroy_(); 166868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port = null; 1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } else { 168868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // We nulled out port when sending the response, and now the page 169868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // is trying to send another response for the same request. 170868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) handleSendRequestError(isSendMessage, responseCallbackPreserved, 171868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) sourceExtensionId, targetExtensionId); 172868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 173868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) }; 174868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // In case the extension never invokes the responseCallback, and also 175868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // doesn't keep a reference to it, we need to clean up the port. Do 176868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // so by attaching to the garbage collection of the responseCallback 177868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // using some native hackery. 178fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch messagingNatives.BindToGC(responseCallback, function() { 179868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (port) { 180868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.destroy_(); 181868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port = null; 1822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) } 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }); 184868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!isSendMessage) { 185868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) requestEvent.dispatch(request, sender, responseCallback); 186868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } else { 187868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var rv = requestEvent.dispatch(request, sender, responseCallback); 188868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) responseCallbackPreserved = 189868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) rv && rv.results && rv.results.indexOf(true) > -1; 190868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!responseCallbackPreserved && port) { 191868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // If they didn't access the response callback, they're not 192868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // going to send a response, so clean up the port immediately. 193868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.destroy_(); 194868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port = null; 195868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 196868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 197868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) }); 198868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var eventName = (isSendMessage ? 199868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) (isExternal ? 200868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) "runtime.onMessageExternal" : "runtime.onMessage") : 201868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) (isExternal ? 202868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) "extension.onRequestExternal" : "extension.onRequest")); 203868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) logActivity.LogEvent(targetExtensionId, 204868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) eventName, 205868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) [sourceExtensionId, sourceUrl]); 206868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return true; 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Called by native code when a channel has been opened to this context. 210868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function dispatchOnConnect(portId, 211868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) channelName, 212868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) sourceTab, 213868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) sourceExtensionId, 214868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) targetExtensionId, 215868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) sourceUrl) { 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Only create a new Port if someone is actually listening for a connection. 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // In addition to being an optimization, this also fixes a bug where if 2 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // channels were opened to and from the same process, closing one would 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // close both. 220868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var extensionId = processNatives.GetExtensionId(); 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (targetExtensionId != extensionId) 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; // not for us 223868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (ports[getOppositePortId(portId)]) 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; // this channel was opened by us, so ignore it 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Determine whether this is coming from another extension, so we can use 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the right event. 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var isExternal = sourceExtensionId != extensionId; 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 231868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var sender = {}; 232868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (sourceExtensionId != '') 233868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) sender.id = sourceExtensionId; 234c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (sourceUrl) 235c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sender.url = sourceUrl; 236c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (sourceTab) 237c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sender.tab = sourceTab; 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Special case for sendRequest/onRequest and sendMessage/onMessage. 240868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (channelName == kRequestChannel || channelName == kMessageChannel) { 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return dispatchOnRequest(portId, channelName, sender, 242c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) sourceExtensionId, targetExtensionId, sourceUrl, 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) isExternal); 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 246868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var connectEvent = null; 247868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (chrome.runtime) { 248868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) connectEvent = isExternal ? chrome.runtime.onConnectExternal 249868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) : chrome.runtime.onConnect; 2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 251868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!connectEvent) 252868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return false; 253868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (!connectEvent.hasListeners()) 254868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return false; 255868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 256868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var port = createPort(portId, channelName); 257868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.sender = sender; 258868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (processNatives.manifestVersion < 2) 259868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) port.tab = port.sender.tab; 260868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 261868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) var eventName = (isExternal ? 262868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) "runtime.onConnectExternal" : "runtime.onConnect"); 263868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) connectEvent.dispatch(port); 264868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) logActivity.LogEvent(targetExtensionId, 265868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) eventName, 266868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) [sourceExtensionId]); 267868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return true; 2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Called by native code when a channel has been closed. 271868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function dispatchOnDisconnect(portId, errorMessage) { 2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var port = ports[portId]; 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (port) { 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Update the renderer's port bookkeeping, without notifying the browser. 275fb250657ef40d7500f20882d5c9909c1013367d3Ben Murdoch messagingNatives.CloseChannel(portId, false); 276c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (errorMessage) 277c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) lastError.set('Port', errorMessage, null, chrome); 2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.onDisconnect.dispatch(port); 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } finally { 2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.destroy_(); 2822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) lastError.clear(chrome); 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Called by native code when a message has been sent to the given port. 288868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function dispatchOnMessage(msg, portId) { 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var port = ports[portId]; 290eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (port) { 291eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (msg) 292eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch msg = $JSON.parse(msg); 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.onMessage.dispatch(msg, port); 294eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Shared implementation used by tabs.sendMessage and runtime.sendMessage. 298868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) function sendMessageImpl(port, request, responseCallback) { 299868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (port.name != kNativeMessageChannel) 3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.postMessage(request); 3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 302868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (port.name == kMessageChannel && !responseCallback) { 3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(mpcomplete): Do this for the old sendRequest API too, after 3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // verifying it doesn't break anything. 3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Go ahead and disconnect immediately if the sender is not expecting 3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // a response. 3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.disconnect(); 3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Ensure the callback exists for the older sendRequest API. 3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!responseCallback) 3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) responseCallback = function() {}; 3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.onDisconnect.addListener(function() { 316868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // For onDisconnects, we only notify the callback if there was an error. 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 318868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (chrome.runtime && chrome.runtime.lastError) 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) responseCallback(); 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } finally { 3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port = null; 3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }); 3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.onMessage.addListener(function(response) { 3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try { 3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) responseCallback(response); 3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } finally { 3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port.disconnect(); 3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) port = null; 3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }); 3322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) }; 3332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) function sendMessageUpdateArguments(functionName) { 3352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Align missing (optional) function arguments with the arguments that 3362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // schema validation is expecting, e.g. 3372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // extension.sendRequest(req) -> extension.sendRequest(null, req) 3382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // extension.sendRequest(req, cb) -> extension.sendRequest(null, req, cb) 339eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch var args = $Array.splice(arguments, 1); // skip functionName 3402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) var lastArg = args.length - 1; 3412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // responseCallback (last argument) is optional. 3432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) var responseCallback = null; 3442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (typeof(args[lastArg]) == 'function') 3452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) responseCallback = args[lastArg--]; 3462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // request (second argument) is required. 3482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) var request = args[lastArg--]; 3492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 350ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch // targetId (first argument, extensionId in the manifest) is optional. 3512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) var targetId = null; 3522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (lastArg >= 0) 3532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) targetId = args[lastArg--]; 3542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 3552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (lastArg != -1) 3562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) throw new Error('Invalid arguments to ' + functionName + '.'); 3572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) return [targetId, request, responseCallback]; 3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 360868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.kRequestChannel = kRequestChannel; 361868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.kMessageChannel = kMessageChannel; 362868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.kNativeMessageChannel = kNativeMessageChannel; 363868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.Port = Port; 364868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.createPort = createPort; 365868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.sendMessageImpl = sendMessageImpl; 3662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)exports.sendMessageUpdateArguments = sendMessageUpdateArguments; 367868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 368868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// For C++ code to call. 369868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.hasPort = hasPort; 370868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.dispatchOnConnect = dispatchOnConnect; 371868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.dispatchOnDisconnect = dispatchOnDisconnect; 372868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)exports.dispatchOnMessage = dispatchOnMessage; 373