Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(158)

Side by Side Diff: extensions/renderer/resources/messaging.js

Issue 1966283002: Remove port lifetime management from renderer (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Use MakeUnique instead of WrapUnique Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « extensions/renderer/messaging_bindings.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 (!$Object.hasOwnProperty(ports, this.portId_))
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 (!$Object.hasOwnProperty(ports, this.portId_))
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 (!$Object.hasOwnProperty(ports, this.portId_))
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 $Object.hasOwnProperty(ports, portId);
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
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
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
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
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
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);
OLDNEW
« no previous file with comments | « extensions/renderer/messaging_bindings.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698