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 ? |
| 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; |
| 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) |
| 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 |