Chromium Code Reviews| 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 |