Chromium Code Reviews| 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 dispatchOnConnect. | |
| 101 function handleSendRequestError(isSendMessage, responseCallbackAccessed, | |
| 102 sourceExtensionId, targetExtensionId) { | |
| 103 var errorMsg; | |
| 104 var eventName = (isSendMessage ? | |
| 105 "chrome.extension.onMessage" : "chrome.extension.onRequest"); | |
| 106 if (isSendMessage && !responseCallbackAccessed) { | |
| 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 | |
| 120 chrome.extension.lastError = {"message": errorMsg}; | |
| 121 console.error("Could not send response: " + errorMsg); | |
| 122 } | |
| 123 | |
| 98 // Called by native code when a channel has been opened to this context. | 124 // Called by native code when a channel has been opened to this context. |
| 99 chromeHidden.Port.dispatchOnConnect = function(portId, channelName, tab, | 125 chromeHidden.Port.dispatchOnConnect = function(portId, channelName, tab, |
| 100 sourceExtensionId, | 126 sourceExtensionId, |
| 101 targetExtensionId) { | 127 targetExtensionId) { |
| 102 // Only create a new Port if someone is actually listening for a connection. | 128 // Only create a new Port if someone is actually listening for a connection. |
| 103 // In addition to being an optimization, this also fixes a bug where if 2 | 129 // In addition to being an optimization, this also fixes a bug where if 2 |
| 104 // channels were opened to and from the same process, closing one would | 130 // channels were opened to and from the same process, closing one would |
| 105 // close both. | 131 // close both. |
| 106 if (targetExtensionId != extensionId) | 132 if (targetExtensionId != extensionId) |
| 107 return; // not for us | 133 return; // not for us |
| 108 if (ports[getOppositePortId(portId)]) | 134 if (ports[getOppositePortId(portId)]) |
| 109 return; // this channel was opened by us, so ignore it | 135 return; // this channel was opened by us, so ignore it |
| 110 | 136 |
| 111 // Determine whether this is coming from another extension, so we can use | 137 // Determine whether this is coming from another extension, so we can use |
| 112 // the right event. | 138 // the right event. |
| 113 var isExternal = sourceExtensionId != extensionId; | 139 var isExternal = sourceExtensionId != extensionId; |
| 114 | 140 |
| 115 if (tab) | 141 if (tab) |
| 116 tab = chromeHidden.JSON.parse(tab); | 142 tab = chromeHidden.JSON.parse(tab); |
| 117 var sender = {tab: tab, id: sourceExtensionId}; | 143 var sender = {tab: tab, id: sourceExtensionId}; |
| 118 | 144 |
| 119 // Special case for sendRequest/onRequest. | 145 // Special case for sendRequest/onRequest and sendMessage/onMessage. |
| 120 if (channelName == chromeHidden.kRequestChannel) { | 146 var isSendMessage = channelName == chromeHidden.kMessageChannel; |
| 121 var requestEvent = (isExternal ? | 147 if (channelName == chromeHidden.kRequestChannel || |
| 122 chrome.extension.onRequestExternal : chrome.extension.onRequest); | 148 channelName == chromeHidden.kMessageChannel) { |
| 149 var requestEvent = (isSendMessage ? | |
|
Aaron Boodman
2012/03/30 21:28:00
O_o
Matt Perry
2012/03/30 23:02:48
Yeah I went there.
| |
| 150 (isExternal ? | |
| 151 chrome.extension.onMessageExternal : chrome.extension.onMessage) : | |
| 152 (isExternal ? | |
| 153 chrome.extension.onRequestExternal : chrome.extension.onRequest)); | |
| 123 if (requestEvent.hasListeners()) { | 154 if (requestEvent.hasListeners()) { |
| 124 var port = chromeHidden.Port.createPort(portId, channelName); | 155 var port = chromeHidden.Port.createPort(portId, channelName); |
| 125 port.onMessage.addListener(function(request) { | 156 port.onMessage.addListener(function(request) { |
| 157 var responseCallbackAccessed = false; | |
| 126 var responseCallback = function(response) { | 158 var responseCallback = function(response) { |
| 127 if (port) { | 159 if (port) { |
| 128 port.postMessage(response); | 160 port.postMessage(response); |
| 129 port = null; | 161 port = null; |
| 130 } else { | 162 } else { |
| 131 // We nulled out port when sending the response, and now the page | 163 // We nulled out port when sending the response, and now the page |
| 132 // is trying to send another response for the same request. | 164 // is trying to send another response for the same request. |
| 133 var errorMsg = | 165 handleSendRequestError(isSendMessage, responseCallbackAccessed, |
| 134 "Cannot send a response more than once per " + | 166 sourceExtensionId, targetExtensionId); |
| 135 "chrome.extension.onRequest listener per document (message " + | |
| 136 "was sent by extension " + sourceExtensionId; | |
| 137 if (sourceExtensionId != targetExtensionId) { | |
| 138 errorMsg += " for extension " + targetExtensionId; | |
| 139 } | |
| 140 errorMsg += ")."; | |
| 141 chrome.extension.lastError = {"message": errorMsg}; | |
| 142 console.error("Could not send response: " + errorMsg); | |
| 143 } | 167 } |
| 144 }; | 168 }; |
| 145 // In case the extension never invokes the responseCallback, and also | 169 // In case the extension never invokes the responseCallback, and also |
| 146 // doesn't keep a reference to it, we need to clean up the port. Do | 170 // doesn't keep a reference to it, we need to clean up the port. Do |
| 147 // so by attaching to the garbage collection of the responseCallback | 171 // so by attaching to the garbage collection of the responseCallback |
| 148 // using some native hackery. | 172 // using some native hackery. |
| 149 BindToGC(responseCallback, function() { | 173 BindToGC(responseCallback, function() { |
| 150 if (port) { | 174 if (port) { |
| 151 port.disconnect(); | 175 port.disconnect(); |
| 152 port = null; | 176 port = null; |
| 153 } | 177 } |
| 154 }); | 178 }); |
| 155 requestEvent.dispatch(request, sender, responseCallback); | 179 if (!isSendMessage) { |
| 180 requestEvent.dispatch(request, sender, responseCallback); | |
| 181 } else { | |
| 182 var retvals = requestEvent.dispatch(request, sender, | |
| 183 responseCallback); | |
| 184 for (var i in retvals) { | |
| 185 if (retvals[i] === true) { | |
| 186 responseCallbackAccessed = true; | |
|
Aaron Boodman
2012/03/30 21:28:00
Were you originally trying to do some craziness wh
Aaron Boodman
2012/03/30 21:28:00
responseCallback = retvals.indexOf(true) > -1;
Matt Perry
2012/03/30 23:02:48
Yep. New name.
Matt Perry
2012/03/30 23:02:48
Sweet, done.
| |
| 187 break; | |
| 188 } | |
| 189 } | |
| 190 if (!responseCallbackAccessed) { | |
| 191 // If they didn't access the response callback, they're not | |
| 192 // going to send a response, so clean up the port immediately. | |
| 193 port.destroy_(); | |
| 194 port = null; | |
| 195 } | |
| 196 } | |
| 156 }); | 197 }); |
| 157 } | 198 } |
| 158 return; | |
| 159 } | 199 } |
| 160 | 200 |
| 161 var connectEvent = (isExternal ? | 201 var connectEvent = (isExternal ? |
| 162 chrome.extension.onConnectExternal : chrome.extension.onConnect); | 202 chrome.extension.onConnectExternal : chrome.extension.onConnect); |
| 163 if (connectEvent.hasListeners()) { | 203 if (connectEvent.hasListeners()) { |
| 164 var port = chromeHidden.Port.createPort(portId, channelName); | 204 var port = chromeHidden.Port.createPort(portId, channelName); |
| 165 port.sender = sender; | 205 port.sender = sender; |
| 166 if (manifestVersion < 2) | 206 if (manifestVersion < 2) |
| 167 port.tab = port.sender.tab; | 207 port.tab = port.sender.tab; |
| 168 | 208 |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 196 chromeHidden.Port.dispatchOnMessage = function(msg, portId) { | 236 chromeHidden.Port.dispatchOnMessage = function(msg, portId) { |
| 197 var port = ports[portId]; | 237 var port = ports[portId]; |
| 198 if (port) { | 238 if (port) { |
| 199 if (msg) { | 239 if (msg) { |
| 200 msg = chromeHidden.JSON.parse(msg); | 240 msg = chromeHidden.JSON.parse(msg); |
| 201 } | 241 } |
| 202 port.onMessage.dispatch(msg, port); | 242 port.onMessage.dispatch(msg, port); |
| 203 } | 243 } |
| 204 }; | 244 }; |
| 205 | 245 |
| 246 // Shared implementation used by tabs.sendMessage and extension.sendMessage. | |
| 247 chromeHidden.Port.sendMessageImpl = function(port, request, | |
| 248 responseCallback) { | |
| 249 port.postMessage(request); | |
| 250 | |
| 251 if (port.name == chromeHidden.kMessageChannel && !responseCallback) { | |
| 252 // Go ahead and disconnect immediately if the sender is not expecting | |
| 253 // a response. | |
| 254 port.disconnect(); | |
| 255 return; | |
| 256 } | |
| 257 | |
| 258 if (!responseCallback) | |
|
Aaron Boodman
2012/03/30 21:28:00
Maybe add a comment that this is used by the older
Matt Perry
2012/03/30 23:02:48
Done.
| |
| 259 responseCallback = function() {}; | |
| 260 | |
| 261 port.onDisconnect.addListener(function() { | |
| 262 // For onDisconnects, we only notify the callback if there was an error | |
| 263 try { | |
| 264 if (chrome.extension.lastError) | |
| 265 responseCallback(); | |
| 266 } finally { | |
| 267 port = null; | |
| 268 } | |
| 269 }); | |
| 270 port.onMessage.addListener(function(response) { | |
| 271 try { | |
| 272 responseCallback(response); | |
| 273 } finally { | |
| 274 port.disconnect(); | |
| 275 port = null; | |
| 276 } | |
| 277 }); | |
| 278 } | |
| 279 | |
| 206 // This function is called on context initialization for both content scripts | 280 // This function is called on context initialization for both content scripts |
| 207 // and extension contexts. | 281 // and extension contexts. |
| 208 chromeHidden.onLoad.addListener(function(tempExtensionId, | 282 chromeHidden.onLoad.addListener(function(tempExtensionId, |
| 209 isExtensionProcess, | 283 isExtensionProcess, |
| 210 inIncognitoContext, | 284 inIncognitoContext, |
| 211 tempManifestVersion) { | 285 tempManifestVersion) { |
| 212 extensionId = tempExtensionId; | 286 extensionId = tempExtensionId; |
| 213 manifestVersion = tempManifestVersion; | 287 manifestVersion = tempManifestVersion; |
| 214 | 288 |
| 215 chrome.extension = chrome.extension || {}; | 289 chrome.extension = chrome.extension || {}; |
| 216 | 290 |
| 217 if (manifestVersion < 2) { | 291 if (manifestVersion < 2) { |
| 218 chrome.self = chrome.extension; | 292 chrome.self = chrome.extension; |
| 219 chrome.extension.inIncognitoTab = inIncognitoContext; | 293 chrome.extension.inIncognitoTab = inIncognitoContext; |
| 220 } | 294 } |
| 221 | 295 |
| 222 chrome.extension.inIncognitoContext = inIncognitoContext; | 296 chrome.extension.inIncognitoContext = inIncognitoContext; |
| 223 }); | 297 }); |
| OLD | NEW |