| 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 script contains unprivileged javascript APIs related to chrome | 5 // This script contains unprivileged javascript APIs related to chrome |
| 6 // extensions. It is loaded by any extension-related context, such as content | 6 // extensions. It is loaded by any extension-related context, such as content |
| 7 // scripts or background pages. | 7 // scripts or background pages. |
| 8 // See user_script_slave.cc for script that is loaded by content scripts only. | 8 // See user_script_slave.cc for script that is loaded by content scripts only. |
| 9 | 9 |
| 10 require('json_schema'); | 10 require('json_schema'); |
| 11 require('event_bindings'); | 11 require('event_bindings'); |
| 12 var miscNatives = requireNative('miscellaneous_bindings'); | 12 var miscNatives = requireNative('miscellaneous_bindings'); |
| 13 var CloseChannel = miscNatives.CloseChannel; | 13 var CloseChannel = miscNatives.CloseChannel; |
| 14 var PortAddRef = miscNatives.PortAddRef; | 14 var PortAddRef = miscNatives.PortAddRef; |
| 15 var PortRelease = miscNatives.PortRelease; | 15 var PortRelease = miscNatives.PortRelease; |
| 16 var PostMessage = miscNatives.PostMessage; | 16 var PostMessage = miscNatives.PostMessage; |
| 17 var BindToGC = miscNatives.BindToGC; | 17 var BindToGC = miscNatives.BindToGC; |
| 18 | 18 |
| 19 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); | 19 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); |
| 20 var manifestVersion; | 20 var manifestVersion; |
| 21 var extensionId; | 21 var extensionId; |
| 22 | 22 |
| 23 // The reserved channel name for the sendRequest API. | 23 // The reserved channel name for the sendRequest/sendMessage APIs. |
| 24 // Note: sendRequest is deprecated. |
| 24 chromeHidden.kRequestChannel = "chrome.extension.sendRequest"; | 25 chromeHidden.kRequestChannel = "chrome.extension.sendRequest"; |
| 26 chromeHidden.kMessageChannel = "chrome.extension.sendMessage"; |
| 25 | 27 |
| 26 // Map of port IDs to port object. | 28 // Map of port IDs to port object. |
| 27 var ports = {}; | 29 var ports = {}; |
| 28 | 30 |
| 29 // Map of port IDs to chromeHidden.onUnload listeners. Keep track of these | 31 // Map of port IDs to chromeHidden.onUnload listeners. Keep track of these |
| 30 // to free the onUnload listeners when ports are closed. | 32 // to free the onUnload listeners when ports are closed. |
| 31 var portReleasers = {}; | 33 var portReleasers = {}; |
| 32 | 34 |
| 33 // Change even to odd and vice versa, to get the other side of a given | 35 // Change even to odd and vice versa, to get the other side of a given |
| 34 // channel. | 36 // channel. |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 } | 90 } |
| 89 var port = new PortImpl(portId, opt_name); | 91 var port = new PortImpl(portId, opt_name); |
| 90 ports[portId] = port; | 92 ports[portId] = port; |
| 91 portReleasers[portId] = PortRelease.bind(this, portId); | 93 portReleasers[portId] = PortRelease.bind(this, portId); |
| 92 chromeHidden.onUnload.addListener(portReleasers[portId]); | 94 chromeHidden.onUnload.addListener(portReleasers[portId]); |
| 93 | 95 |
| 94 PortAddRef(portId); | 96 PortAddRef(portId); |
| 95 return port; | 97 return port; |
| 96 }; | 98 }; |
| 97 | 99 |
| 100 // Helper function for dispatchOnRequest. |
| 101 function handleSendRequestError(isSendMessage, responseCallbackPreserved, |
| 102 sourceExtensionId, targetExtensionId) { |
| 103 var errorMsg; |
| 104 var eventName = (isSendMessage ? |
| 105 "chrome.extension.onMessage" : "chrome.extension.onRequest"); |
| 106 if (isSendMessage && !responseCallbackPreserved) { |
| 107 errorMsg = |
| 108 "The " + eventName + " listener must return true if you want to" + |
| 109 " send a response after the listener returns "; |
| 110 } else { |
| 111 errorMsg = |
| 112 "Cannot send a response more than once per " + eventName + |
| 113 " listener per document"; |
| 114 } |
| 115 errorMsg += " (message was sent by extension " + sourceExtensionId; |
| 116 if (sourceExtensionId != targetExtensionId) |
| 117 errorMsg += " for extension " + targetExtensionId; |
| 118 errorMsg += ")."; |
| 119 chrome.extension.lastError = {"message": errorMsg}; |
| 120 console.error("Could not send response: " + errorMsg); |
| 121 } |
| 122 |
| 123 // Helper function for dispatchOnConnect |
| 98 function dispatchOnRequest(portId, channelName, sender, | 124 function dispatchOnRequest(portId, channelName, sender, |
| 99 sourceExtensionId, targetExtensionId, | 125 sourceExtensionId, targetExtensionId, |
| 100 isExternal) { | 126 isExternal) { |
| 101 var requestEvent = (isExternal ? | 127 var isSendMessage = channelName == chromeHidden.kMessageChannel; |
| 102 chrome.extension.onRequestExternal : chrome.extension.onRequest); | 128 var requestEvent = (isSendMessage ? |
| 129 (isExternal ? |
| 130 chrome.extension.onMessageExternal : chrome.extension.onMessage) : |
| 131 (isExternal ? |
| 132 chrome.extension.onRequestExternal : chrome.extension.onRequest)); |
| 103 if (requestEvent.hasListeners()) { | 133 if (requestEvent.hasListeners()) { |
| 104 var port = chromeHidden.Port.createPort(portId, channelName); | 134 var port = chromeHidden.Port.createPort(portId, channelName); |
| 105 port.onMessage.addListener(function(request) { | 135 port.onMessage.addListener(function(request) { |
| 136 var responseCallbackPreserved = false; |
| 106 var responseCallback = function(response) { | 137 var responseCallback = function(response) { |
| 107 if (port) { | 138 if (port) { |
| 108 port.postMessage(response); | 139 port.postMessage(response); |
| 109 port.destroy_(); | 140 port.destroy_(); |
| 110 port = null; | 141 port = null; |
| 111 } else { | 142 } else { |
| 112 // We nulled out port when sending the response, and now the page | 143 // We nulled out port when sending the response, and now the page |
| 113 // is trying to send another response for the same request. | 144 // is trying to send another response for the same request. |
| 114 var errorMsg = | 145 handleSendRequestError(isSendMessage, responseCallbackPreserved, |
| 115 "Cannot send a response more than once per " + | 146 sourceExtensionId, targetExtensionId); |
| 116 "chrome.extension.onRequest listener per document (message " + | |
| 117 "was sent by extension " + sourceExtensionId; | |
| 118 if (sourceExtensionId != targetExtensionId) { | |
| 119 errorMsg += " for extension " + targetExtensionId; | |
| 120 } | |
| 121 errorMsg += ")."; | |
| 122 chrome.extension.lastError = {"message": errorMsg}; | |
| 123 console.error("Could not send response: " + errorMsg); | |
| 124 } | 147 } |
| 125 }; | 148 }; |
| 126 // In case the extension never invokes the responseCallback, and also | 149 // In case the extension never invokes the responseCallback, and also |
| 127 // doesn't keep a reference to it, we need to clean up the port. Do | 150 // doesn't keep a reference to it, we need to clean up the port. Do |
| 128 // so by attaching to the garbage collection of the responseCallback | 151 // so by attaching to the garbage collection of the responseCallback |
| 129 // using some native hackery. | 152 // using some native hackery. |
| 130 BindToGC(responseCallback, function() { | 153 BindToGC(responseCallback, function() { |
| 131 if (port) { | 154 if (port) { |
| 132 port.destroy_(); | 155 port.destroy_(); |
| 133 port = null; | 156 port = null; |
| 134 } | 157 } |
| 135 }); | 158 }); |
| 136 requestEvent.dispatch(request, sender, responseCallback); | 159 if (!isSendMessage) { |
| 160 requestEvent.dispatch(request, sender, responseCallback); |
| 161 } else { |
| 162 var rv = requestEvent.dispatch(request, sender, responseCallback); |
| 163 responseCallbackPreserved = |
| 164 rv && rv.results && rv.results.indexOf(true) > -1; |
| 165 if (!responseCallbackPreserved) { |
| 166 // If they didn't access the response callback, they're not |
| 167 // going to send a response, so clean up the port immediately. |
| 168 port.destroy_(); |
| 169 port = null; |
| 170 } |
| 171 } |
| 137 }); | 172 }); |
| 138 return true; | 173 return true; |
| 139 } | 174 } |
| 140 return false; | 175 return false; |
| 141 } | 176 } |
| 142 | 177 |
| 143 // Called by native code when a channel has been opened to this context. | 178 // Called by native code when a channel has been opened to this context. |
| 144 chromeHidden.Port.dispatchOnConnect = function(portId, channelName, tab, | 179 chromeHidden.Port.dispatchOnConnect = function(portId, channelName, tab, |
| 145 sourceExtensionId, | 180 sourceExtensionId, |
| 146 targetExtensionId) { | 181 targetExtensionId) { |
| 147 // Only create a new Port if someone is actually listening for a connection. | 182 // Only create a new Port if someone is actually listening for a connection. |
| 148 // In addition to being an optimization, this also fixes a bug where if 2 | 183 // In addition to being an optimization, this also fixes a bug where if 2 |
| 149 // channels were opened to and from the same process, closing one would | 184 // channels were opened to and from the same process, closing one would |
| 150 // close both. | 185 // close both. |
| 151 if (targetExtensionId != extensionId) | 186 if (targetExtensionId != extensionId) |
| 152 return false; // not for us | 187 return false; // not for us |
| 153 if (ports[getOppositePortId(portId)]) | 188 if (ports[getOppositePortId(portId)]) |
| 154 return false; // this channel was opened by us, so ignore it | 189 return false; // this channel was opened by us, so ignore it |
| 155 | 190 |
| 156 // Determine whether this is coming from another extension, so we can use | 191 // Determine whether this is coming from another extension, so we can use |
| 157 // the right event. | 192 // the right event. |
| 158 var isExternal = sourceExtensionId != extensionId; | 193 var isExternal = sourceExtensionId != extensionId; |
| 159 | 194 |
| 160 if (tab) | 195 if (tab) |
| 161 tab = chromeHidden.JSON.parse(tab); | 196 tab = chromeHidden.JSON.parse(tab); |
| 162 var sender = {tab: tab, id: sourceExtensionId}; | 197 var sender = {tab: tab, id: sourceExtensionId}; |
| 163 | 198 |
| 164 // Special case for sendRequest/onRequest. | 199 // Special case for sendRequest/onRequest and sendMessage/onMessage. |
| 165 if (channelName == chromeHidden.kRequestChannel) { | 200 if (channelName == chromeHidden.kRequestChannel || |
| 201 channelName == chromeHidden.kMessageChannel) { |
| 166 return dispatchOnRequest(portId, channelName, sender, | 202 return dispatchOnRequest(portId, channelName, sender, |
| 167 sourceExtensionId, targetExtensionId, | 203 sourceExtensionId, targetExtensionId, |
| 168 isExternal); | 204 isExternal); |
| 169 } | 205 } |
| 170 | 206 |
| 171 var connectEvent = (isExternal ? | 207 var connectEvent = (isExternal ? |
| 172 chrome.extension.onConnectExternal : chrome.extension.onConnect); | 208 chrome.extension.onConnectExternal : chrome.extension.onConnect); |
| 173 if (connectEvent.hasListeners()) { | 209 if (connectEvent.hasListeners()) { |
| 174 var port = chromeHidden.Port.createPort(portId, channelName); | 210 var port = chromeHidden.Port.createPort(portId, channelName); |
| 175 port.sender = sender; | 211 port.sender = sender; |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 208 chromeHidden.Port.dispatchOnMessage = function(msg, portId) { | 244 chromeHidden.Port.dispatchOnMessage = function(msg, portId) { |
| 209 var port = ports[portId]; | 245 var port = ports[portId]; |
| 210 if (port) { | 246 if (port) { |
| 211 if (msg) { | 247 if (msg) { |
| 212 msg = chromeHidden.JSON.parse(msg); | 248 msg = chromeHidden.JSON.parse(msg); |
| 213 } | 249 } |
| 214 port.onMessage.dispatch(msg, port); | 250 port.onMessage.dispatch(msg, port); |
| 215 } | 251 } |
| 216 }; | 252 }; |
| 217 | 253 |
| 254 // Shared implementation used by tabs.sendMessage and extension.sendMessage. |
| 255 chromeHidden.Port.sendMessageImpl = function(port, request, |
| 256 responseCallback) { |
| 257 port.postMessage(request); |
| 258 |
| 259 if (port.name == chromeHidden.kMessageChannel && !responseCallback) { |
| 260 // TODO(mpcomplete): Do this for the old sendRequest API too, after |
| 261 // verifying it doesn't break anything. |
| 262 // Go ahead and disconnect immediately if the sender is not expecting |
| 263 // a response. |
| 264 port.disconnect(); |
| 265 return; |
| 266 } |
| 267 |
| 268 // Ensure the callback exists for the older sendRequest API. |
| 269 if (!responseCallback) |
| 270 responseCallback = function() {}; |
| 271 |
| 272 port.onDisconnect.addListener(function() { |
| 273 // For onDisconnects, we only notify the callback if there was an error |
| 274 try { |
| 275 if (chrome.extension.lastError) |
| 276 responseCallback(); |
| 277 } finally { |
| 278 port = null; |
| 279 } |
| 280 }); |
| 281 port.onMessage.addListener(function(response) { |
| 282 try { |
| 283 responseCallback(response); |
| 284 } finally { |
| 285 port.disconnect(); |
| 286 port = null; |
| 287 } |
| 288 }); |
| 289 } |
| 290 |
| 218 // This function is called on context initialization for both content scripts | 291 // This function is called on context initialization for both content scripts |
| 219 // and extension contexts. | 292 // and extension contexts. |
| 220 chromeHidden.onLoad.addListener(function(tempExtensionId, | 293 chromeHidden.onLoad.addListener(function(tempExtensionId, |
| 221 isExtensionProcess, | 294 isExtensionProcess, |
| 222 inIncognitoContext, | 295 inIncognitoContext, |
| 223 tempManifestVersion) { | 296 tempManifestVersion) { |
| 224 extensionId = tempExtensionId; | 297 extensionId = tempExtensionId; |
| 225 manifestVersion = tempManifestVersion; | 298 manifestVersion = tempManifestVersion; |
| 226 | 299 |
| 227 chrome.extension = chrome.extension || {}; | 300 chrome.extension = chrome.extension || {}; |
| 228 | 301 |
| 229 if (manifestVersion < 2) { | 302 if (manifestVersion < 2) { |
| 230 chrome.self = chrome.extension; | 303 chrome.self = chrome.extension; |
| 231 chrome.extension.inIncognitoTab = inIncognitoContext; | 304 chrome.extension.inIncognitoTab = inIncognitoContext; |
| 232 } | 305 } |
| 233 | 306 |
| 234 chrome.extension.inIncognitoContext = inIncognitoContext; | 307 chrome.extension.inIncognitoContext = inIncognitoContext; |
| 235 }); | 308 }); |
| OLD | NEW |