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 |