OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 // chrome.runtime.messaging API implementation. | 5 // chrome.runtime.messaging API implementation. |
6 // TODO(robwu): Fix this indentation. | 6 // TODO(robwu): Fix this indentation. |
7 | 7 |
8 // TODO(kalman): factor requiring chrome out of here. | 8 // TODO(kalman): factor requiring chrome out of here. |
9 var chrome = requireNative('chrome').GetChrome(); | 9 var chrome = requireNative('chrome').GetChrome(); |
10 var Event = require('event_bindings').Event; | 10 var Event = require('event_bindings').Event; |
11 var lastError = require('lastError'); | 11 var lastError = require('lastError'); |
12 var logActivity = requireNative('activityLogger'); | 12 var logActivity = requireNative('activityLogger'); |
13 var logging = requireNative('logging'); | 13 var logging = requireNative('logging'); |
14 var messagingNatives = requireNative('messaging_natives'); | 14 var messagingNatives = requireNative('messaging_natives'); |
15 var processNatives = requireNative('process'); | 15 var processNatives = requireNative('process'); |
16 var utils = require('utils'); | 16 var utils = require('utils'); |
17 var messagingUtils = require('messaging_utils'); | 17 var messagingUtils = require('messaging_utils'); |
18 | 18 |
19 // The reserved channel name for the sendRequest/send(Native)Message APIs. | 19 // The reserved channel name for the sendRequest/send(Native)Message APIs. |
20 // Note: sendRequest is deprecated. | 20 // Note: sendRequest is deprecated. |
21 var kRequestChannel = "chrome.extension.sendRequest"; | 21 var kRequestChannel = "chrome.extension.sendRequest"; |
22 var kMessageChannel = "chrome.runtime.sendMessage"; | 22 var kMessageChannel = "chrome.runtime.sendMessage"; |
23 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage"; | 23 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage"; |
24 var kPortClosedError = 'Attempting to use a disconnected port object'; | |
24 | 25 |
25 // Map of port IDs to port object. | 26 // Map of port IDs to port object. |
26 var ports = {__proto__: null}; | 27 var ports = {__proto__: null}; |
27 | 28 |
28 // Change even to odd and vice versa, to get the other side of a given | 29 // Change even to odd and vice versa, to get the other side of a given |
29 // channel. | 30 // channel. |
30 function getOppositePortId(portId) { return portId ^ 1; } | 31 function getOppositePortId(portId) { return portId ^ 1; } |
31 | 32 |
32 // Port object. Represents a connection to another script context through | 33 // Port object. Represents a connection to another script context through |
33 // which messages can be passed. | 34 // which messages can be passed. |
(...skipping 12 matching lines...) Expand all Loading... | |
46 name: 'message', | 47 name: 'message', |
47 type: 'any', | 48 type: 'any', |
48 optional: true, | 49 optional: true, |
49 }; | 50 }; |
50 var options = { | 51 var options = { |
51 __proto__: null, | 52 __proto__: null, |
52 unmanaged: true, | 53 unmanaged: true, |
53 }; | 54 }; |
54 this.onDisconnect = new Event(null, [portSchema], options); | 55 this.onDisconnect = new Event(null, [portSchema], options); |
55 this.onMessage = new Event(null, [messageSchema, portSchema], options); | 56 this.onMessage = new Event(null, [messageSchema, portSchema], options); |
56 this.onDestroy_ = null; | |
57 } | 57 } |
58 $Object.setPrototypeOf(PortImpl.prototype, null); | 58 $Object.setPrototypeOf(PortImpl.prototype, null); |
59 | 59 |
60 // Sends a message asynchronously to the context on the other end of this | 60 // Sends a message asynchronously to the context on the other end of this |
61 // port. | 61 // port. |
62 PortImpl.prototype.postMessage = function(msg) { | 62 PortImpl.prototype.postMessage = function(msg) { |
63 if (!(this.portId_ in ports)) | |
Devlin
2016/05/17 19:50:30
'in' implicitly relies on Object.prototype; use $O
robwu
2016/05/17 22:47:51
ports has a null prototype, so it doesn't matter.
| |
64 throw new Error(kPortClosedError); | |
65 | |
63 // JSON.stringify doesn't support a root object which is undefined. | 66 // JSON.stringify doesn't support a root object which is undefined. |
64 if (msg === undefined) | 67 if (msg === undefined) |
65 msg = null; | 68 msg = null; |
66 msg = $JSON.stringify(msg); | 69 msg = $JSON.stringify(msg); |
67 if (msg === undefined) { | 70 if (msg === undefined) { |
68 // JSON.stringify can fail with unserializable objects. Log an error and | 71 // JSON.stringify can fail with unserializable objects. Log an error and |
69 // drop the message. | 72 // drop the message. |
70 // | 73 // |
71 // TODO(kalman/mpcomplete): it would be better to do the same validation | 74 // TODO(kalman/mpcomplete): it would be better to do the same validation |
72 // here that we do for runtime.sendMessage (and variants), i.e. throw an | 75 // here that we do for runtime.sendMessage (and variants), i.e. throw an |
73 // schema validation Error, but just maintain the old behaviour until | 76 // schema validation Error, but just maintain the old behaviour until |
74 // there's a good reason not to (http://crbug.com/263077). | 77 // there's a good reason not to (http://crbug.com/263077). |
75 console.error('Illegal argument to Port.postMessage'); | 78 console.error('Illegal argument to Port.postMessage'); |
76 return; | 79 return; |
77 } | 80 } |
78 messagingNatives.PostMessage(this.portId_, msg); | 81 messagingNatives.PostMessage(this.portId_, msg); |
79 }; | 82 }; |
80 | 83 |
81 // Disconnects the port from the other end. | 84 // Disconnects the port from the other end. |
82 PortImpl.prototype.disconnect = function() { | 85 PortImpl.prototype.disconnect = function() { |
86 if (!(this.portId_ in ports)) | |
Devlin
2016/05/17 19:50:30
ditto
robwu
2016/05/17 22:47:51
Done.
| |
87 return; // disconnect() on an already-closed port is a no-op. | |
83 messagingNatives.CloseChannel(this.portId_, true); | 88 messagingNatives.CloseChannel(this.portId_, true); |
84 this.destroy_(); | 89 this.destroy_(); |
85 }; | 90 }; |
86 | 91 |
92 // Close this specific port without forcing the channel to close. The channel | |
93 // will close if this was the only port at this end of the channel. | |
94 PortImpl.prototype.disconnectSoftly = function() { | |
95 if (!(this.portId_ in ports)) | |
Devlin
2016/05/17 19:50:30
ditto, but can this happen?
robwu
2016/05/17 22:47:51
Done. This is not expected to happen, but I didn't
| |
96 return; | |
97 messagingNatives.CloseChannel(this.portId_, false); | |
98 this.destroy_(); | |
99 }; | |
100 | |
87 PortImpl.prototype.destroy_ = function() { | 101 PortImpl.prototype.destroy_ = function() { |
88 if (this.onDestroy_) { | |
89 this.onDestroy_(); | |
90 this.onDestroy_ = null; | |
91 } | |
92 privates(this.onDisconnect).impl.destroy_(); | 102 privates(this.onDisconnect).impl.destroy_(); |
93 privates(this.onMessage).impl.destroy_(); | 103 privates(this.onMessage).impl.destroy_(); |
94 // TODO(robwu): Remove port lifetime management because it is completely | |
95 // handled in the browser. The renderer's only roles are | |
96 // 1) rejecting ports so that the browser knows that the renderer is not | |
97 // interested in the port (this is merely an optimization) | |
98 // 2) acknowledging port creations, so that the browser knows that the port | |
99 // was successfully created (from the perspective of the extension), but | |
100 // then closed for some non-fatal reason. | |
101 // 3) notifying the browser of explicit port closure via .disconnect(). | |
102 // In other cases (navigations), the browser automatically cleans up the | |
103 // port. | |
104 messagingNatives.PortRelease(this.portId_); | |
105 delete ports[this.portId_]; | 104 delete ports[this.portId_]; |
106 }; | 105 }; |
107 | 106 |
108 // Returns true if the specified port id is in this context. This is used by | 107 // Returns true if the specified port id is in this context. This is used by |
109 // the C++ to avoid creating the javascript message for all the contexts that | 108 // the C++ to avoid creating the javascript message for all the contexts that |
110 // don't care about a particular message. | 109 // don't care about a particular message. |
111 function hasPort(portId) { | 110 function hasPort(portId) { |
112 return portId in ports; | 111 return portId in ports; |
113 }; | 112 }; |
114 | 113 |
115 // Hidden port creation function. We don't want to expose an API that lets | 114 // Hidden port creation function. We don't want to expose an API that lets |
116 // people add arbitrary port IDs to the port list. | 115 // people add arbitrary port IDs to the port list. |
117 function createPort(portId, opt_name) { | 116 function createPort(portId, opt_name) { |
118 if (ports[portId]) | 117 if (ports[portId]) |
119 throw new Error("Port '" + portId + "' already exists."); | 118 throw new Error("Port '" + portId + "' already exists."); |
120 var port = new Port(portId, opt_name); | 119 var port = new Port(portId, opt_name); |
121 ports[portId] = port; | 120 ports[portId] = port; |
122 messagingNatives.PortAddRef(portId); | |
123 return port; | 121 return port; |
124 }; | 122 }; |
125 | 123 |
126 // Helper function for dispatchOnRequest. | 124 // Helper function for dispatchOnRequest. |
127 function handleSendRequestError(isSendMessage, | 125 function handleSendRequestError(isSendMessage, |
128 responseCallbackPreserved, | 126 responseCallbackPreserved, |
129 sourceExtensionId, | 127 sourceExtensionId, |
130 targetExtensionId, | 128 targetExtensionId, |
131 sourceUrl) { | 129 sourceUrl) { |
132 var errorMsg; | 130 var errorMsg; |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
170 return false; | 168 return false; |
171 if (!requestEvent.hasListeners()) | 169 if (!requestEvent.hasListeners()) |
172 return false; | 170 return false; |
173 var port = createPort(portId, channelName); | 171 var port = createPort(portId, channelName); |
174 | 172 |
175 function messageListener(request) { | 173 function messageListener(request) { |
176 var responseCallbackPreserved = false; | 174 var responseCallbackPreserved = false; |
177 var responseCallback = function(response) { | 175 var responseCallback = function(response) { |
178 if (port) { | 176 if (port) { |
179 port.postMessage(response); | 177 port.postMessage(response); |
180 privates(port).impl.destroy_(); | 178 // TODO(robwu): This can be changed to disconnect() because there is |
Devlin
2016/05/17 19:50:30
What's the reason to not do this now?
robwu
2016/05/17 22:47:51
Because this would be a potential change in behavi
| |
179 // no point in allowing other receivers at this end of the port to | |
180 // keep the channel alive because the opener port can only receive one | |
181 // message. | |
182 privates(port).impl.disconnectSoftly(); | |
181 port = null; | 183 port = null; |
182 } else { | 184 } else { |
183 // We nulled out port when sending the response, and now the page | 185 // We nulled out port when sending the response, and now the page |
184 // is trying to send another response for the same request. | 186 // is trying to send another response for the same request. |
185 handleSendRequestError(isSendMessage, responseCallbackPreserved, | 187 handleSendRequestError(isSendMessage, responseCallbackPreserved, |
186 sourceExtensionId, targetExtensionId); | 188 sourceExtensionId, targetExtensionId); |
187 } | 189 } |
188 }; | 190 }; |
189 // In case the extension never invokes the responseCallback, and also | 191 // In case the extension never invokes the responseCallback, and also |
190 // doesn't keep a reference to it, we need to clean up the port. Do | 192 // doesn't keep a reference to it, we need to clean up the port. Do |
191 // so by attaching to the garbage collection of the responseCallback | 193 // so by attaching to the garbage collection of the responseCallback |
192 // using some native hackery. | 194 // using some native hackery. |
193 // | 195 // |
194 // If the context is destroyed before this has a chance to execute, | 196 // If the context is destroyed before this has a chance to execute, |
195 // BindToGC knows to release |portId| (important for updating C++ state | 197 // BindToGC knows to release |portId| (important for updating C++ state |
196 // both in this renderer and on the other end). We don't need to clear | 198 // both in this renderer and on the other end). We don't need to clear |
197 // any JavaScript state, as calling destroy_() would usually do - but | 199 // any JavaScript state, as calling destroy_() would usually do - but |
198 // the context has been destroyed, so there isn't any JS state to clear. | 200 // the context has been destroyed, so there isn't any JS state to clear. |
199 messagingNatives.BindToGC(responseCallback, function() { | 201 messagingNatives.BindToGC(responseCallback, function() { |
200 if (port) { | 202 if (port) { |
201 privates(port).impl.destroy_(); | 203 privates(port).impl.disconnectSoftly(); |
202 port = null; | 204 port = null; |
203 } | 205 } |
204 }, portId); | 206 }, portId); |
205 var rv = requestEvent.dispatch(request, sender, responseCallback); | 207 var rv = requestEvent.dispatch(request, sender, responseCallback); |
206 if (isSendMessage) { | 208 if (isSendMessage) { |
207 responseCallbackPreserved = | 209 responseCallbackPreserved = |
208 rv && rv.results && $Array.indexOf(rv.results, true) > -1; | 210 rv && rv.results && $Array.indexOf(rv.results, true) > -1; |
209 if (!responseCallbackPreserved && port) { | 211 if (!responseCallbackPreserved && port) { |
210 // If they didn't access the response callback, they're not | 212 // If they didn't access the response callback, they're not |
211 // going to send a response, so clean up the port immediately. | 213 // going to send a response, so clean up the port immediately. |
212 privates(port).impl.destroy_(); | 214 privates(port).impl.disconnectSoftly(); |
213 port = null; | 215 port = null; |
214 } | 216 } |
215 } | 217 } |
216 } | 218 } |
217 | 219 |
218 privates(port).impl.onDestroy_ = function() { | |
219 port.onMessage.removeListener(messageListener); | |
220 }; | |
221 port.onMessage.addListener(messageListener); | 220 port.onMessage.addListener(messageListener); |
222 | 221 |
223 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; | 222 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest"; |
224 if (isExternal) | 223 if (isExternal) |
225 eventName += "External"; | 224 eventName += "External"; |
226 logActivity.LogEvent(targetExtensionId, | 225 logActivity.LogEvent(targetExtensionId, |
227 eventName, | 226 eventName, |
228 [sourceExtensionId, sourceUrl]); | 227 [sourceExtensionId, sourceUrl]); |
229 return true; | 228 return true; |
230 } | 229 } |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
307 logActivity.LogEvent(targetExtensionId, | 306 logActivity.LogEvent(targetExtensionId, |
308 eventName, | 307 eventName, |
309 [sourceExtensionId]); | 308 [sourceExtensionId]); |
310 return true; | 309 return true; |
311 }; | 310 }; |
312 | 311 |
313 // Called by native code when a channel has been closed. | 312 // Called by native code when a channel has been closed. |
314 function dispatchOnDisconnect(portId, errorMessage) { | 313 function dispatchOnDisconnect(portId, errorMessage) { |
315 var port = ports[portId]; | 314 var port = ports[portId]; |
316 if (port) { | 315 if (port) { |
317 // Update the renderer's port bookkeeping, without notifying the browser. | 316 delete ports[portId]; |
318 messagingNatives.CloseChannel(portId, false); | |
319 if (errorMessage) | 317 if (errorMessage) |
320 lastError.set('Port', errorMessage, null, chrome); | 318 lastError.set('Port', errorMessage, null, chrome); |
321 try { | 319 try { |
322 port.onDisconnect.dispatch(port); | 320 port.onDisconnect.dispatch(port); |
323 } finally { | 321 } finally { |
324 privates(port).impl.destroy_(); | 322 privates(port).impl.destroy_(); |
325 lastError.clear(chrome); | 323 lastError.clear(chrome); |
326 } | 324 } |
327 } | 325 } |
328 }; | 326 }; |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
391 | 389 |
392 function messageListener(response) { | 390 function messageListener(response) { |
393 try { | 391 try { |
394 if (responseCallback) | 392 if (responseCallback) |
395 sendResponseAndClearCallback(response); | 393 sendResponseAndClearCallback(response); |
396 } finally { | 394 } finally { |
397 port.disconnect(); | 395 port.disconnect(); |
398 } | 396 } |
399 } | 397 } |
400 | 398 |
401 privates(port).impl.onDestroy_ = function() { | |
402 port.onDisconnect.removeListener(disconnectListener); | |
403 port.onMessage.removeListener(messageListener); | |
404 }; | |
405 port.onDisconnect.addListener(disconnectListener); | 399 port.onDisconnect.addListener(disconnectListener); |
406 port.onMessage.addListener(messageListener); | 400 port.onMessage.addListener(messageListener); |
407 }; | 401 }; |
408 | 402 |
409 function sendMessageUpdateArguments(functionName, hasOptionsArgument) { | 403 function sendMessageUpdateArguments(functionName, hasOptionsArgument) { |
410 // skip functionName and hasOptionsArgument | 404 // skip functionName and hasOptionsArgument |
411 var args = $Array.slice(arguments, 2); | 405 var args = $Array.slice(arguments, 2); |
412 var alignedArgs = messagingUtils.alignSendMessageArguments(args, | 406 var alignedArgs = messagingUtils.alignSendMessageArguments(args, |
413 hasOptionsArgument); | 407 hasOptionsArgument); |
414 if (!alignedArgs) | 408 if (!alignedArgs) |
(...skipping 22 matching lines...) Expand all Loading... | |
437 exports.$set('Port', Port); | 431 exports.$set('Port', Port); |
438 exports.$set('createPort', createPort); | 432 exports.$set('createPort', createPort); |
439 exports.$set('sendMessageImpl', sendMessageImpl); | 433 exports.$set('sendMessageImpl', sendMessageImpl); |
440 exports.$set('sendMessageUpdateArguments', sendMessageUpdateArguments); | 434 exports.$set('sendMessageUpdateArguments', sendMessageUpdateArguments); |
441 | 435 |
442 // For C++ code to call. | 436 // For C++ code to call. |
443 exports.$set('hasPort', hasPort); | 437 exports.$set('hasPort', hasPort); |
444 exports.$set('dispatchOnConnect', dispatchOnConnect); | 438 exports.$set('dispatchOnConnect', dispatchOnConnect); |
445 exports.$set('dispatchOnDisconnect', dispatchOnDisconnect); | 439 exports.$set('dispatchOnDisconnect', dispatchOnDisconnect); |
446 exports.$set('dispatchOnMessage', dispatchOnMessage); | 440 exports.$set('dispatchOnMessage', dispatchOnMessage); |
OLD | NEW |