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

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

Issue 309413002: Revert 274558 "Move some extensions renderer resources to extens..." (Closed) Base URL: svn://svn.chromium.org/chrome/
Patch Set: Created 6 years, 6 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 | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 // This contains unprivileged javascript APIs for extensions and apps. It
6 // can be loaded by any extension-related context, such as content scripts or
7 // background pages. See user_script_slave.cc for script that is loaded by
8 // content scripts only.
9
10 // TODO(kalman): factor requiring chrome out of here.
11 var chrome = requireNative('chrome').GetChrome();
12 var Event = require('event_bindings').Event;
13 var lastError = require('lastError');
14 var logActivity = requireNative('activityLogger');
15 var messagingNatives = requireNative('messaging_natives');
16 var processNatives = requireNative('process');
17 var unloadEvent = require('unload_event');
18 var utils = require('utils');
19 var messagingUtils = require('messaging_utils');
20
21 // The reserved channel name for the sendRequest/send(Native)Message APIs.
22 // Note: sendRequest is deprecated.
23 var kRequestChannel = "chrome.extension.sendRequest";
24 var kMessageChannel = "chrome.runtime.sendMessage";
25 var kNativeMessageChannel = "chrome.runtime.sendNativeMessage";
26
27 // Map of port IDs to port object.
28 var ports = {};
29
30 // Map of port IDs to unloadEvent listeners. Keep track of these to free the
31 // unloadEvent listeners when ports are closed.
32 var portReleasers = {};
33
34 // Change even to odd and vice versa, to get the other side of a given
35 // channel.
36 function getOppositePortId(portId) { return portId ^ 1; }
37
38 // Port object. Represents a connection to another script context through
39 // which messages can be passed.
40 function PortImpl(portId, opt_name) {
41 this.portId_ = portId;
42 this.name = opt_name;
43
44 var portSchema = {name: 'port', $ref: 'runtime.Port'};
45 var options = {unmanaged: true};
46 this.onDisconnect = new Event(null, [portSchema], options);
47 this.onMessage = new Event(
48 null,
49 [{name: 'message', type: 'any', optional: true}, portSchema],
50 options);
51 this.onDestroy_ = null;
52 }
53
54 // Sends a message asynchronously to the context on the other end of this
55 // port.
56 PortImpl.prototype.postMessage = function(msg) {
57 // JSON.stringify doesn't support a root object which is undefined.
58 if (msg === undefined)
59 msg = null;
60 msg = $JSON.stringify(msg);
61 if (msg === undefined) {
62 // JSON.stringify can fail with unserializable objects. Log an error and
63 // drop the message.
64 //
65 // TODO(kalman/mpcomplete): it would be better to do the same validation
66 // here that we do for runtime.sendMessage (and variants), i.e. throw an
67 // schema validation Error, but just maintain the old behaviour until
68 // there's a good reason not to (http://crbug.com/263077).
69 console.error('Illegal argument to Port.postMessage');
70 return;
71 }
72 messagingNatives.PostMessage(this.portId_, msg);
73 };
74
75 // Disconnects the port from the other end.
76 PortImpl.prototype.disconnect = function() {
77 messagingNatives.CloseChannel(this.portId_, true);
78 this.destroy_();
79 };
80
81 PortImpl.prototype.destroy_ = function() {
82 var portId = this.portId_;
83
84 if (this.onDestroy_)
85 this.onDestroy_();
86 privates(this.onDisconnect).impl.destroy_();
87 privates(this.onMessage).impl.destroy_();
88
89 messagingNatives.PortRelease(portId);
90 unloadEvent.removeListener(portReleasers[portId]);
91
92 delete ports[portId];
93 delete portReleasers[portId];
94 };
95
96 // Returns true if the specified port id is in this context. This is used by
97 // the C++ to avoid creating the javascript message for all the contexts that
98 // don't care about a particular message.
99 function hasPort(portId) {
100 return portId in ports;
101 };
102
103 // Hidden port creation function. We don't want to expose an API that lets
104 // people add arbitrary port IDs to the port list.
105 function createPort(portId, opt_name) {
106 if (ports[portId])
107 throw new Error("Port '" + portId + "' already exists.");
108 var port = new Port(portId, opt_name);
109 ports[portId] = port;
110 portReleasers[portId] = $Function.bind(messagingNatives.PortRelease,
111 this,
112 portId);
113 unloadEvent.addListener(portReleasers[portId]);
114 messagingNatives.PortAddRef(portId);
115 return port;
116 };
117
118 // Helper function for dispatchOnRequest.
119 function handleSendRequestError(isSendMessage,
120 responseCallbackPreserved,
121 sourceExtensionId,
122 targetExtensionId,
123 sourceUrl) {
124 var errorMsg = [];
125 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
126 if (isSendMessage && !responseCallbackPreserved) {
127 $Array.push(errorMsg,
128 "The chrome." + eventName + " listener must return true if you " +
129 "want to send a response after the listener returns");
130 } else {
131 $Array.push(errorMsg,
132 "Cannot send a response more than once per chrome." + eventName +
133 " listener per document");
134 }
135 $Array.push(errorMsg, "(message was sent by extension" + sourceExtensionId);
136 if (sourceExtensionId != "" && sourceExtensionId != targetExtensionId)
137 $Array.push(errorMsg, "for extension " + targetExtensionId);
138 if (sourceUrl != "")
139 $Array.push(errorMsg, "for URL " + sourceUrl);
140 lastError.set(eventName, errorMsg.join(" ") + ").", null, chrome);
141 }
142
143 // Helper function for dispatchOnConnect
144 function dispatchOnRequest(portId, channelName, sender,
145 sourceExtensionId, targetExtensionId, sourceUrl,
146 isExternal) {
147 var isSendMessage = channelName == kMessageChannel;
148 var requestEvent = null;
149 if (isSendMessage) {
150 if (chrome.runtime) {
151 requestEvent = isExternal ? chrome.runtime.onMessageExternal
152 : chrome.runtime.onMessage;
153 }
154 } else {
155 if (chrome.extension) {
156 requestEvent = isExternal ? chrome.extension.onRequestExternal
157 : chrome.extension.onRequest;
158 }
159 }
160 if (!requestEvent)
161 return false;
162 if (!requestEvent.hasListeners())
163 return false;
164 var port = createPort(portId, channelName);
165
166 function messageListener(request) {
167 var responseCallbackPreserved = false;
168 var responseCallback = function(response) {
169 if (port) {
170 port.postMessage(response);
171 privates(port).impl.destroy_();
172 port = null;
173 } else {
174 // We nulled out port when sending the response, and now the page
175 // is trying to send another response for the same request.
176 handleSendRequestError(isSendMessage, responseCallbackPreserved,
177 sourceExtensionId, targetExtensionId);
178 }
179 };
180 // In case the extension never invokes the responseCallback, and also
181 // doesn't keep a reference to it, we need to clean up the port. Do
182 // so by attaching to the garbage collection of the responseCallback
183 // using some native hackery.
184 messagingNatives.BindToGC(responseCallback, function() {
185 if (port) {
186 privates(port).impl.destroy_();
187 port = null;
188 }
189 });
190 var rv = requestEvent.dispatch(request, sender, responseCallback);
191 if (isSendMessage) {
192 responseCallbackPreserved =
193 rv && rv.results && $Array.indexOf(rv.results, true) > -1;
194 if (!responseCallbackPreserved && port) {
195 // If they didn't access the response callback, they're not
196 // going to send a response, so clean up the port immediately.
197 privates(port).impl.destroy_();
198 port = null;
199 }
200 }
201 }
202
203 privates(port).impl.onDestroy_ = function() {
204 port.onMessage.removeListener(messageListener);
205 };
206 port.onMessage.addListener(messageListener);
207
208 var eventName = isSendMessage ? "runtime.onMessage" : "extension.onRequest";
209 if (isExternal)
210 eventName += "External";
211 logActivity.LogEvent(targetExtensionId,
212 eventName,
213 [sourceExtensionId, sourceUrl]);
214 return true;
215 }
216
217 // Called by native code when a channel has been opened to this context.
218 function dispatchOnConnect(portId,
219 channelName,
220 sourceTab,
221 sourceExtensionId,
222 targetExtensionId,
223 sourceUrl,
224 tlsChannelId) {
225 // Only create a new Port if someone is actually listening for a connection.
226 // In addition to being an optimization, this also fixes a bug where if 2
227 // channels were opened to and from the same process, closing one would
228 // close both.
229 var extensionId = processNatives.GetExtensionId();
230 if (targetExtensionId != extensionId)
231 return false; // not for us
232
233 if (ports[getOppositePortId(portId)])
234 return false; // this channel was opened by us, so ignore it
235
236 // Determine whether this is coming from another extension, so we can use
237 // the right event.
238 var isExternal = sourceExtensionId != extensionId;
239
240 var sender = {};
241 if (sourceExtensionId != '')
242 sender.id = sourceExtensionId;
243 if (sourceUrl)
244 sender.url = sourceUrl;
245 if (sourceTab)
246 sender.tab = sourceTab;
247 if (tlsChannelId !== undefined)
248 sender.tlsChannelId = tlsChannelId;
249
250 // Special case for sendRequest/onRequest and sendMessage/onMessage.
251 if (channelName == kRequestChannel || channelName == kMessageChannel) {
252 return dispatchOnRequest(portId, channelName, sender,
253 sourceExtensionId, targetExtensionId, sourceUrl,
254 isExternal);
255 }
256
257 var connectEvent = null;
258 if (chrome.runtime) {
259 connectEvent = isExternal ? chrome.runtime.onConnectExternal
260 : chrome.runtime.onConnect;
261 }
262 if (!connectEvent)
263 return false;
264 if (!connectEvent.hasListeners())
265 return false;
266
267 var port = createPort(portId, channelName);
268 port.sender = sender;
269 if (processNatives.manifestVersion < 2)
270 port.tab = port.sender.tab;
271
272 var eventName = (isExternal ?
273 "runtime.onConnectExternal" : "runtime.onConnect");
274 connectEvent.dispatch(port);
275 logActivity.LogEvent(targetExtensionId,
276 eventName,
277 [sourceExtensionId]);
278 return true;
279 };
280
281 // Called by native code when a channel has been closed.
282 function dispatchOnDisconnect(portId, errorMessage) {
283 var port = ports[portId];
284 if (port) {
285 // Update the renderer's port bookkeeping, without notifying the browser.
286 messagingNatives.CloseChannel(portId, false);
287 if (errorMessage)
288 lastError.set('Port', errorMessage, null, chrome);
289 try {
290 port.onDisconnect.dispatch(port);
291 } finally {
292 privates(port).impl.destroy_();
293 lastError.clear(chrome);
294 }
295 }
296 };
297
298 // Called by native code when a message has been sent to the given port.
299 function dispatchOnMessage(msg, portId) {
300 var port = ports[portId];
301 if (port) {
302 if (msg)
303 msg = $JSON.parse(msg);
304 port.onMessage.dispatch(msg, port);
305 }
306 };
307
308 // Shared implementation used by tabs.sendMessage and runtime.sendMessage.
309 function sendMessageImpl(port, request, responseCallback) {
310 if (port.name != kNativeMessageChannel)
311 port.postMessage(request);
312
313 if (port.name == kMessageChannel && !responseCallback) {
314 // TODO(mpcomplete): Do this for the old sendRequest API too, after
315 // verifying it doesn't break anything.
316 // Go ahead and disconnect immediately if the sender is not expecting
317 // a response.
318 port.disconnect();
319 return;
320 }
321
322 // Ensure the callback exists for the older sendRequest API.
323 if (!responseCallback)
324 responseCallback = function() {};
325
326 // Note: make sure to manually remove the onMessage/onDisconnect listeners
327 // that we added before destroying the Port, a workaround to a bug in Port
328 // where any onMessage/onDisconnect listeners added but not removed will
329 // be leaked when the Port is destroyed.
330 // http://crbug.com/320723 tracks a sustainable fix.
331
332 function disconnectListener() {
333 // For onDisconnects, we only notify the callback if there was an error.
334 if (chrome.runtime && chrome.runtime.lastError)
335 responseCallback();
336 }
337
338 function messageListener(response) {
339 try {
340 responseCallback(response);
341 } finally {
342 port.disconnect();
343 }
344 }
345
346 privates(port).impl.onDestroy_ = function() {
347 port.onDisconnect.removeListener(disconnectListener);
348 port.onMessage.removeListener(messageListener);
349 };
350 port.onDisconnect.addListener(disconnectListener);
351 port.onMessage.addListener(messageListener);
352 };
353
354 function sendMessageUpdateArguments(functionName, hasOptionsArgument) {
355 // skip functionName and hasOptionsArgument
356 var args = $Array.slice(arguments, 2);
357 var alignedArgs = messagingUtils.alignSendMessageArguments(args,
358 hasOptionsArgument);
359 if (!alignedArgs)
360 throw new Error('Invalid arguments to ' + functionName + '.');
361 return alignedArgs;
362 }
363
364 var Port = utils.expose('Port', PortImpl, { functions: [
365 'disconnect',
366 'postMessage'
367 ],
368 properties: [
369 'name',
370 'onDisconnect',
371 'onMessage'
372 ] });
373
374 exports.kRequestChannel = kRequestChannel;
375 exports.kMessageChannel = kMessageChannel;
376 exports.kNativeMessageChannel = kNativeMessageChannel;
377 exports.Port = Port;
378 exports.createPort = createPort;
379 exports.sendMessageImpl = sendMessageImpl;
380 exports.sendMessageUpdateArguments = sendMessageUpdateArguments;
381
382 // For C++ code to call.
383 exports.hasPort = hasPort;
384 exports.dispatchOnConnect = dispatchOnConnect;
385 exports.dispatchOnDisconnect = dispatchOnDisconnect;
386 exports.dispatchOnMessage = dispatchOnMessage;
OLDNEW
« no previous file with comments | « trunk/src/extensions/renderer/resources/last_error.js ('k') | trunk/src/extensions/renderer/resources/messaging_utils.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698