1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim: set sts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 * This module provides wrappers around standard message managers to
9 * simplify bidirectional communication. It currently allows a caller to
10 * send a message to a single listener, and receive a reply. If there
11 * are no matching listeners, or the message manager disconnects before
12 * a reply is received, the caller is returned an error.
14 * The listener end may specify filters for the messages it wishes to
15 * receive, and the sender end likewise may specify recipient tags to
18 * The message handler on the listener side may return its response
19 * value directly, or may return a promise, the resolution or rejection
20 * of which will be returned instead. The sender end likewise receives a
21 * promise which resolves or rejects to the listener's response.
24 * A basic setup works something like this:
26 * A content script adds a message listener to its global
27 * ContentFrameMessageManager, with an appropriate set of filters:
30 * init(messageManager, window, extensionID) {
31 * this.window = window;
33 * MessageChannel.addListener(
34 * messageManager, "ContentScript:TouchContent",
37 * this.messageFilterStrict = {
38 * innerWindowID: getInnerWindowID(window),
39 * extensionID: extensionID,
42 * this.messageFilterPermissive = {
43 * outerWindowID: getOuterWindowID(window),
47 * receiveMessage({ target, messageName, sender, recipient, data }) {
48 * if (messageName == "ContentScript:TouchContent") {
49 * return new Promise(resolve => {
50 * this.touchWindow(data.touchWith, result => {
51 * resolve({ touchResult: result });
58 * A script in the parent process sends a message to the content process
59 * via a tab message manager, including recipient tags to match its
60 * filter, and an optional sender tag to identify itself:
62 * let data = { touchWith: "pencil" };
63 * let sender = { extensionID, contextID };
64 * let recipient = { innerWindowID: tab.linkedBrowser.innerWindowID, extensionID };
66 * MessageChannel.sendMessage(
67 * tab.linkedBrowser.messageManager, "ContentScript:TouchContent",
68 * data, {recipient, sender}
70 * alert(result.touchResult);
73 * Since the lifetimes of message senders and receivers may not always
74 * match, either side of the message channel may cancel pending
75 * responses which match its sender or recipient tags.
77 * For the above client, this might be done from an
78 * inner-window-destroyed observer, when its target scope is destroyed:
80 * observe(subject, topic, data) {
81 * if (topic == "inner-window-destroyed") {
82 * let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
84 * MessageChannel.abortResponses({ innerWindowID });
88 * From the parent, it may be done when its context is being destroyed:
91 * MessageChannel.abortResponses({
92 * extensionID: this.extensionID,
93 * contextID: this.contextID,
99 export let MessageChannel;
101 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
103 import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
107 ChromeUtils.defineESModuleGetters(lazy, {
108 MessageManagerProxy: "resource://gre/modules/MessageManagerProxy.sys.mjs",
111 function getMessageManager(target) {
112 if (typeof target.sendAsyncMessage === "function") {
115 return new lazy.MessageManagerProxy(target);
118 function matches(target, messageManager) {
119 return target === messageManager || target.messageManager === messageManager;
122 const { DEBUG } = AppConstants;
124 // Idle callback timeout for low-priority message dispatch.
125 const LOW_PRIORITY_TIMEOUT_MS = 250;
127 const MESSAGE_MESSAGES = "MessageChannel:Messages";
128 const MESSAGE_RESPONSE = "MessageChannel:Response";
131 var _makeDeferred = (resolve, reject) => {
132 // We use arrow functions here and refer to the outer variables via
133 // `this`, to avoid a lexical name lookup. Yes, it makes a difference.
134 // No, I don't like it any more than you do.
135 _deferredResult.resolve = resolve;
136 _deferredResult.reject = reject;
140 * Helper to create a new Promise without allocating any closures to
141 * receive its resolution functions.
143 * I know what you're thinking: "This is crazy. There is no possible way
144 * this can be necessary. Just use the ordinary Promise constructor the
145 * way it was meant to be used, you lunatic."
147 * And, against all odds, it turns out that you're wrong. Creating
148 * lambdas to receive promise resolution functions consistently turns
149 * out to be one of the most expensive parts of message dispatch in this
152 * So we do the stupid micro-optimization, and try to live with
155 * (See also bug 1404950.)
159 let Deferred = () => {
161 _deferredResult = res;
162 res.promise = new Promise(_makeDeferred);
163 _deferredResult = null;
168 * Handles the mapping and dispatching of messages to their registered
169 * handlers. There is one broker per message manager and class of
170 * messages. Each class of messages is mapped to one native message
171 * name, e.g., "MessageChannel:Message", and is dispatched to handlers
172 * based on an internal message name, e.g., "Extension:ExecuteScript".
174 class FilteringMessageManager {
176 * @param {string} messageName
177 * The name of the native message this broker listens for.
178 * @param {Function} callback
179 * A function which is called for each message after it has been
180 * mapped to its handler. The function receives two arguments:
183 * An object containing either a `handler` or an `error` property.
184 * If no error occurs, `handler` will be a matching handler that
185 * was registered by `addHandler`. Otherwise, the `error` property
186 * will contain an object describing the error.
189 * An object describing the message, as defined in
190 * `MessageChannel.addListener`.
191 * @param {nsIMessageListenerManager} messageManager
193 constructor(messageName, callback, messageManager) {
194 this.messageName = messageName;
195 this.callback = callback;
196 this.messageManager = messageManager;
198 this.messageManager.addMessageListener(this.messageName, this, true);
200 this.handlers = new Map();
204 * Receives a set of messages from our message manager, maps each to a
205 * handler, and passes the results to our message callbacks.
207 receiveMessage({ data, target }) {
208 data.forEach(msg => {
210 let handlers = Array.from(
211 this.getHandlers(msg.messageName, msg.sender || null, msg.recipient)
215 this.callback(handlers, msg);
221 * Iterates over all handlers for the given message name. If `recipient`
222 * is provided, only iterates over handlers whose filters match it.
224 * @param {string|number} messageName
225 * The message for which to return handlers.
226 * @param {object} sender
227 * The sender data on which to filter handlers.
228 * @param {object} recipient
229 * The recipient data on which to filter handlers.
231 *getHandlers(messageName, sender, recipient) {
232 let handlers = this.handlers.get(messageName) || new Set();
233 for (let handler of handlers) {
235 MessageChannel.matchesFilter(
236 handler.messageFilterStrict || null,
239 MessageChannel.matchesFilter(
240 handler.messageFilterPermissive || null,
244 (!handler.filterMessage || handler.filterMessage(sender, recipient))
252 * Registers a handler for the given message.
254 * @param {string} messageName
255 * The internal message name for which to register the handler.
256 * @param {object} handler
257 * An opaque handler object. The object may have a
258 * `messageFilterStrict` and/or a `messageFilterPermissive`
259 * property and/or a `filterMessage` method on which to filter messages.
261 * Final dispatching is handled by the message callback passed to
264 addHandler(messageName, handler) {
265 if (!this.handlers.has(messageName)) {
266 this.handlers.set(messageName, new Set());
269 this.handlers.get(messageName).add(handler);
273 * Unregisters a handler for the given message.
275 * @param {string} messageName
276 * The internal message name for which to unregister the handler.
277 * @param {object} handler
278 * The handler object to unregister.
280 removeHandler(messageName, handler) {
281 if (this.handlers.has(messageName)) {
282 this.handlers.get(messageName).delete(handler);
288 * A message dispatch and response manager that wrapse a single native
289 * message manager. Handles dispatching messages through the manager
290 * (optionally coalescing several low-priority messages and dispatching
291 * them during an idle slice), and mapping their responses to the
292 * appropriate response callbacks.
294 * Note that this is a simplified subclass of FilteringMessageManager
295 * that only supports one handler per message, and does not support
298 class ResponseManager extends FilteringMessageManager {
299 constructor(messageName, callback, messageManager) {
300 super(messageName, callback, messageManager);
302 this.idleMessages = [];
303 this.idleScheduled = false;
304 this.onIdle = this.onIdle.bind(this);
308 * Schedules a new idle callback to dispatch pending low-priority
309 * messages, if one is not already scheduled.
311 scheduleIdleCallback() {
312 if (!this.idleScheduled) {
313 ChromeUtils.idleDispatch(this.onIdle, {
314 timeout: LOW_PRIORITY_TIMEOUT_MS,
316 this.idleScheduled = true;
321 * Called when the event queue is idle, and dispatches any pending
322 * low-priority messages in a single chunk.
324 * @param {IdleDeadline} deadline
327 this.idleScheduled = false;
329 let messages = this.idleMessages;
330 this.idleMessages = [];
332 let msgs = messages.map(msg => msg.getMessage());
334 this.messageManager.sendAsyncMessage(MESSAGE_MESSAGES, msgs);
336 for (let msg of messages) {
343 * Sends a message through our wrapped message manager, or schedules
344 * it for low-priority dispatch during an idle callback.
346 * @param {any} message
347 * The message to send.
348 * @param {object} [options]
349 * Message dispatch options.
350 * @param {boolean} [options.lowPriority = false]
351 * If true, dispatches the message in a single chunk with other
352 * low-priority messages the next time the event queue is idle.
354 sendMessage(message, options = {}) {
355 if (options.lowPriority) {
356 this.idleMessages.push(message);
357 this.scheduleIdleCallback();
359 this.messageManager.sendAsyncMessage(MESSAGE_MESSAGES, [
360 message.getMessage(),
365 receiveMessage({ data, target }) {
366 data.target = target;
368 this.callback(this.handlers.get(data.messageName), data);
371 *getHandlers(messageName, sender, recipient) {
372 let handler = this.handlers.get(messageName);
378 addHandler(messageName, handler) {
379 if (DEBUG && this.handlers.has(messageName)) {
381 `Handler already registered for response ID ${messageName}`
384 this.handlers.set(messageName, handler);
388 * Unregisters a handler for the given message.
390 * @param {string} messageName
391 * The internal message name for which to unregister the handler.
392 * @param {object} handler
393 * The handler object to unregister.
395 removeHandler(messageName, handler) {
396 if (DEBUG && this.handlers.get(messageName) !== handler) {
398 `Attempting to remove unexpected response handler for ${messageName}`
401 this.handlers.delete(messageName);
406 * Manages mappings of message managers to their corresponding message
407 * brokers. Brokers are lazily created for each message manager the
408 * first time they are accessed. In the case of content frame message
409 * managers, they are also automatically destroyed when the frame
410 * unload event fires.
412 class FilteringMessageManagerMap extends Map {
413 // Unfortunately, we can't use a WeakMap for this, because message
414 // managers do not support preserved wrappers.
417 * @param {string} messageName
418 * The native message name passed to `FilteringMessageManager` constructors.
419 * @param {Function} callback
420 * The message callback function passed to
421 * `FilteringMessageManager` constructors.
422 * @param {Function} [constructor = FilteringMessageManager]
423 * The constructor for the message manager class that we're
426 constructor(messageName, callback, constructor = FilteringMessageManager) {
429 this.messageName = messageName;
430 this.callback = callback;
431 this._constructor = constructor;
435 * Returns, and possibly creates, a message broker for the given
438 * @param {nsIMessageListenerManager} target
439 * The message manager for which to return a broker.
441 * @returns {FilteringMessageManager}
444 let broker = super.get(target);
449 broker = new this._constructor(this.messageName, this.callback, target);
450 this.set(target, broker);
452 // XXXbz if target is really known to be a MessageListenerManager,
453 // do we need this isInstance check?
454 if (EventTarget.isInstance(target)) {
455 let onUnload = event => {
456 target.removeEventListener("unload", onUnload);
459 target.addEventListener("unload", onUnload);
467 * Represents a message being sent through a MessageChannel, which may
468 * or may not have been dispatched yet, and is pending a response.
470 * When a response has been received, or the message has been canceled,
471 * this class is responsible for settling the response promise as
474 * @param {number} channelId
475 * The unique ID for this message.
476 * @param {any} message
477 * The message contents.
478 * @param {object} sender
479 * An object describing the sender of the message, used by
480 * `abortResponses` to determine whether the message should be
482 * @param {ResponseManager} broker
483 * The response broker on which we're expected to receive a
486 class PendingMessage {
487 constructor(channelId, message, sender, broker) {
488 this.channelId = channelId;
489 this.message = message;
490 this.sender = sender;
491 this.broker = broker;
492 this.deferred = Deferred();
494 MessageChannel.pendingResponses.add(this);
498 * Cleans up after this message once we've received or aborted a
503 this.broker.removeHandler(this.channelId, this);
504 MessageChannel.pendingResponses.delete(this);
512 * Returns the promise which will resolve when we've received or
513 * aborted a response to this message.
516 return this.deferred.promise;
520 * Resolves the message's response promise, and cleans up.
526 this.deferred.resolve(value);
530 * Rejects the message's response promise, and cleans up.
536 this.deferred.reject(value);
539 get messageManager() {
540 return this.broker.messageManager;
544 * Returns the contents of the message to be sent over a message
545 * manager, and registers the response with our response broker.
547 * Returns null if the response has already been canceled, and the
548 * message should not be sent.
555 this.broker.addHandler(this.channelId, this);
563 // Web workers has MessageChannel API, which is unrelated to this.
564 // eslint-disable-next-line no-global-assign
567 Services.obs.addObserver(this, "message-manager-close");
568 Services.obs.addObserver(this, "message-manager-disconnect");
570 this.messageManagers = new FilteringMessageManagerMap(
572 this._handleMessage.bind(this)
575 this.responseManagers = new FilteringMessageManagerMap(
577 this._handleResponse.bind(this),
582 * @property {Set<Deferred>} pendingResponses
583 * Contains a set of pending responses, either waiting to be
584 * received or waiting to be sent.
586 * The response object must be a deferred promise with the following
590 * The promise object which resolves or rejects when the response
591 * is no longer pending.
594 * A function which, when called, causes the `promise` object to be
598 * A sender object, as passed to `sendMessage.
601 * The message manager the response will be sent or received on.
603 * When the promise resolves or rejects, it must be removed from the
606 * These values are used to clear pending responses when execution
607 * contexts are destroyed.
609 this.pendingResponses = new Set();
612 * @property {LimitedSet<string>} abortedResponses
613 * Contains the message name of a limited number of aborted response
614 * handlers, the responses for which will be ignored.
616 this.abortedResponses = new ExtensionUtils.LimitedSet(30);
620 RESULT_DISCONNECTED: 1,
621 RESULT_NO_HANDLER: 2,
622 RESULT_MULTIPLE_HANDLERS: 3,
624 RESULT_NO_RESPONSE: 5,
626 REASON_DISCONNECTED: {
627 result: 1, // this.RESULT_DISCONNECTED
628 message: "Message manager disconnected",
632 * Specifies that only a single listener matching the specified
633 * recipient tag may be listening for the given message, at the other
634 * end of the target message manager.
636 * If no matching listeners exist, a RESULT_NO_HANDLER error will be
637 * returned. If multiple matching listeners exist, a
638 * RESULT_MULTIPLE_HANDLERS error will be returned.
643 * If multiple message managers matching the specified recipient tag
644 * are listening for a message, all listeners are notified, but only
645 * the first response or error is returned.
647 * Only handlers which return a value other than `undefined` are
648 * considered to have responded. Returning a Promise which evaluates
649 * to `undefined` is interpreted as an explicit response.
651 * If no matching listeners exist, a RESULT_NO_HANDLER error will be
652 * returned. If no listeners return a response, a RESULT_NO_RESPONSE
653 * error will be returned.
658 * If multiple message managers matching the specified recipient tag
659 * are listening for a message, all listeners are notified, and all
660 * responses are returned as an array, once all listeners have
666 * Fire-and-forget: The sender of this message does not expect a reply.
671 * Initializes message handlers for the given message managers if needed.
673 * @param {Array<nsIMessageListenerManager>} messageManagers
675 setupMessageManagers(messageManagers) {
676 for (let mm of messageManagers) {
677 // This call initializes a FilteringMessageManager for |mm| if needed.
678 // The FilteringMessageManager must be created to make sure that senders
679 // of messages that expect a reply, such as MessageChannel:Message, do
680 // actually receive a default reply even if there are no explicit message
682 this.messageManagers.get(mm);
687 * Returns true if the properties of the `data` object match those in
688 * the `filter` object. Matching is done on a strict equality basis,
689 * and the behavior varies depending on the value of the `strict`
692 * @param {object?} filter
693 * The filter object to match against.
694 * @param {object} data
695 * The data object being matched.
696 * @param {boolean} [strict=true]
697 * If true, all properties in the `filter` object have a
698 * corresponding property in `data` with the same value. If
699 * false, properties present in both objects must have the same
701 * @returns {boolean} True if the objects match.
703 matchesFilter(filter, data, strict = true) {
708 return Object.keys(filter).every(key => {
709 return key in data && data[key] === filter[key];
712 return Object.keys(filter).every(key => {
713 return !(key in data) || data[key] === filter[key];
718 * Adds a message listener to the given message manager.
720 * @param {nsIMessageListenerManager|Array<nsIMessageListenerManager>} targets
721 * The message managers on which to listen.
722 * @param {string|number} messageName
723 * The name of the message to listen for.
724 * @param {MessageReceiver} handler
725 * The handler to dispatch to. Must be an object with the following
729 * A method which is called for each message received by the
730 * listener. The method takes one argument, an object, with the
731 * following properties:
734 * The internal message name, as passed to `sendMessage`.
737 * The message manager which received this message.
740 * The internal ID of the transaction, used to map responses to
741 * the original sender.
744 * An object describing the sender, as passed to `sendMessage`.
747 * An object describing the recipient, as passed to
751 * The contents of the message, as passed to `sendMessage`.
753 * The method may return any structured-clone-compatible
754 * object, which will be returned as a response to the message
755 * sender. It may also instead return a `Promise`, the
756 * resolution or rejection value of which will likewise be
757 * returned to the message sender.
759 * messageFilterStrict:
760 * An object containing arbitrary properties on which to filter
761 * received messages. Messages will only be dispatched to this
762 * object if the `recipient` object passed to `sendMessage`
763 * matches this filter, as determined by `matchesFilter` with
766 * messageFilterPermissive:
767 * An object containing arbitrary properties on which to filter
768 * received messages. Messages will only be dispatched to this
769 * object if the `recipient` object passed to `sendMessage`
770 * matches this filter, as determined by `matchesFilter` with
774 * An optional function that prevents the handler from handling a
775 * message by returning `false`. See `getHandlers` for the parameters.
777 addListener(targets, messageName, handler) {
778 if (!Array.isArray(targets)) {
781 for (let target of targets) {
782 this.messageManagers.get(target).addHandler(messageName, handler);
787 * Removes a message listener from the given message manager.
789 * @param {nsIMessageListenerManager|Array<nsIMessageListenerManager>} targets
790 * The message managers on which to stop listening.
791 * @param {string|number} messageName
792 * The name of the message to stop listening for.
793 * @param {MessageReceiver} handler
794 * The handler to stop dispatching to.
796 removeListener(targets, messageName, handler) {
797 if (!Array.isArray(targets)) {
800 for (let target of targets) {
801 if (this.messageManagers.has(target)) {
802 this.messageManagers.get(target).removeHandler(messageName, handler);
808 * Sends a message via the given message manager. Returns a promise which
809 * resolves or rejects with the return value of the message receiver.
811 * The promise also rejects if there is no matching listener, or the other
812 * side of the message manager disconnects before the response is received.
814 * @param {nsIMessageSender} target
815 * The message manager on which to send the message.
816 * @param {string} messageName
817 * The name of the message to send, as passed to `addListener`.
818 * @param {object} data
819 * A structured-clone-compatible object to send to the message
821 * @param {object} [options]
822 * An object containing any of the following properties:
823 * @param {object} [options.recipient]
824 * A structured-clone-compatible object to identify the message
825 * recipient. The object must match the `messageFilterStrict` and
826 * `messageFilterPermissive` filters defined by recipients in order
827 * for the message to be received.
828 * @param {object} [options.sender]
829 * A structured-clone-compatible object to identify the message
830 * sender. This object may also be used to avoid delivering the
831 * message to the sender, and as a filter to prematurely
832 * abort responses when the sender is being destroyed.
833 * @see `abortResponses`.
834 * @param {boolean} [options.lowPriority = false]
835 * If true, treat this as a low-priority message, and attempt to
836 * send it in the same chunk as other messages to the same target
837 * the next time the event queue is idle. This option reduces
838 * messaging overhead at the expense of adding some latency.
839 * @param {integer} [options.responseType = RESPONSE_SINGLE]
840 * Specifies the type of response expected. See the `RESPONSE_*`
841 * contents for details.
844 sendMessage(target, messageName, data, options = {}) {
845 let sender = options.sender || {};
846 let recipient = options.recipient || {};
847 let responseType = options.responseType || this.RESPONSE_SINGLE;
849 let channelId = ExtensionUtils.getUniqueId();
860 if (responseType == this.RESPONSE_NONE) {
862 target.sendAsyncMessage(MESSAGE_MESSAGES, [message]);
864 // Caller is not expecting a reply, so dump the error to the console.
866 return Promise.reject(e);
868 return Promise.resolve(); // Not expecting any reply.
871 let broker = this.responseManagers.get(target);
872 let pending = new PendingMessage(channelId, message, recipient, broker);
875 broker.sendMessage(pending, options);
879 return pending.promise;
882 _callHandlers(handlers, data) {
883 let responseType = data.responseType;
885 // At least one handler is required for all response types but
887 if (!handlers.length && responseType != this.RESPONSE_ALL) {
888 return Promise.reject({
889 result: MessageChannel.RESULT_NO_HANDLER,
890 message: "No matching message handler",
894 if (responseType == this.RESPONSE_SINGLE) {
895 if (handlers.length > 1) {
896 return Promise.reject({
897 result: MessageChannel.RESULT_MULTIPLE_HANDLERS,
898 message: `Multiple matching handlers for ${data.messageName}`,
902 // Note: We use `new Promise` rather than `Promise.resolve` here
903 // so that errors from the handler are trapped and converted into
904 // rejected promises.
905 return new Promise(resolve => {
906 resolve(handlers[0].receiveMessage(data));
910 let responses = handlers.map((handler, i) => {
912 return handler.receiveMessage(data, i + 1 == handlers.length);
914 return Promise.reject(e);
918 responses = responses.filter(response => response !== undefined);
920 switch (responseType) {
921 case this.RESPONSE_FIRST:
922 if (!responses.length) {
923 return Promise.reject({
924 result: MessageChannel.RESULT_NO_RESPONSE,
925 message: "No handler returned a response",
929 return Promise.race(responses);
931 case this.RESPONSE_ALL:
932 return Promise.all(responses);
934 return Promise.reject({ message: "Invalid response type" });
938 * Handles dispatching message callbacks from the message brokers to their
939 * appropriate `MessageReceivers`, and routing the responses back to the
942 * Each handler object is a `MessageReceiver` object as passed to
945 * @param {Array<MessageHandler>} handlers
946 * @param {object} data
947 * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target
949 _handleMessage(handlers, data) {
950 if (data.responseType == this.RESPONSE_NONE) {
951 handlers.forEach(handler => {
952 // The sender expects no reply, so dump any errors to the console.
953 new Promise(resolve => {
954 resolve(handler.receiveMessage(data));
956 Cu.reportError(e.stack ? `${e}\n${e.stack}` : e.message || e);
960 // Note: Unhandled messages are silently dropped.
964 let target = getMessageManager(data.target);
968 messageManager: target,
969 channelId: data.channelId,
970 respondingSide: true,
973 let cleanup = () => {
974 this.pendingResponses.delete(deferred);
975 if (target.dispose) {
979 this.pendingResponses.add(deferred);
981 deferred.promise = new Promise((resolve, reject) => {
982 deferred.reject = reject;
984 this._callHandlers(handlers, data).then(resolve, reject);
990 result: this.RESULT_SUCCESS,
991 messageName: deferred.channelId,
996 if (target.isDisconnected) {
997 // Target is disconnected. We can't send an error response, so
1001 target.sendAsyncMessage(MESSAGE_RESPONSE, response);
1004 if (target.isDisconnected) {
1005 // Target is disconnected. We can't send an error response, so
1008 error.result !== this.RESULT_DISCONNECTED &&
1009 error.result !== this.RESULT_NO_RESPONSE
1012 Cu.getClassName(error, false) === "Object"
1021 result: this.RESULT_ERROR,
1022 messageName: deferred.channelId,
1027 if (error && typeof error == "object") {
1029 response.result = error.result;
1031 // Error objects are not structured-clonable, so just copy
1032 // over the important properties.
1041 "mozWebExtLocation",
1044 response.error[key] = error[key];
1049 target.sendAsyncMessage(MESSAGE_RESPONSE, response);
1052 .then(cleanup, e => {
1059 * Handles message callbacks from the response brokers.
1061 * @param {MessageHandler?} handler
1062 * A deferred object created by `sendMessage`, to be resolved
1063 * or rejected based on the contents of the response.
1064 * @param {object} data
1065 * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target
1067 _handleResponse(handler, data) {
1068 // If we have an error at this point, we have handler to report it to,
1071 if (this.abortedResponses.has(data.messageName)) {
1072 this.abortedResponses.delete(data.messageName);
1073 Services.console.logStringMessage(
1074 `Ignoring response to aborted listener for ${data.messageName}`
1078 `No matching message response handler for ${data.messageName}`
1081 } else if (data.result === this.RESULT_SUCCESS) {
1082 handler.resolve(data.value);
1084 handler.reject(data.error);
1089 * Aborts pending message response for the specific channel.
1091 * @param {string} channelId
1092 * A string for channelId of the response.
1093 * @param {object} reason
1094 * An object describing the reason the response was aborted.
1095 * Will be passed to the promise rejection handler of the aborted
1098 abortChannel(channelId, reason) {
1099 for (let response of this.pendingResponses) {
1100 if (channelId === response.channelId && response.respondingSide) {
1101 this.pendingResponses.delete(response);
1102 response.reject(reason);
1108 * Aborts any pending message responses to senders matching the given
1111 * @param {object} sender
1112 * The object on which to filter senders, as determined by
1114 * @param {object} [reason]
1115 * An optional object describing the reason the response was aborted.
1116 * Will be passed to the promise rejection handler of all aborted
1119 abortResponses(sender, reason = this.REASON_DISCONNECTED) {
1120 for (let response of this.pendingResponses) {
1121 if (this.matchesFilter(sender, response.sender)) {
1122 this.pendingResponses.delete(response);
1123 this.abortedResponses.add(response.channelId);
1124 response.reject(reason);
1130 * Aborts any pending message responses to the broker for the given
1133 * @param {nsIMessageListenerManager} target
1134 * The message manager for which to abort brokers.
1135 * @param {object} reason
1136 * An object describing the reason the responses were aborted.
1137 * Will be passed to the promise rejection handler of all aborted
1140 abortMessageManager(target, reason) {
1141 for (let response of this.pendingResponses) {
1142 if (matches(response.messageManager, target)) {
1143 this.abortedResponses.add(response.channelId);
1144 response.reject(reason);
1149 observe(subject, topic, data) {
1151 case "message-manager-close":
1152 case "message-manager-disconnect":
1154 if (this.responseManagers.has(subject)) {
1155 this.abortMessageManager(subject, this.REASON_DISCONNECTED);
1158 this.responseManagers.delete(subject);
1159 this.messageManagers.delete(subject);
1166 MessageChannel.init();