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 |