1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5"use strict";
6
7/**
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.
13 *
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
16 * match the filters.
17 *
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.
22 *
23 *
24 * A basic setup works something like this:
25 *
26 * A content script adds a message listener to its global
27 * nsIContentFrameMessageManager, with an appropriate set of filters:
28 *
29 *  {
30 *    init(messageManager, window, extensionID) {
31 *      this.window = window;
32 *
33 *      MessageChannel.addListener(
34 *        messageManager, "ContentScript:TouchContent",
35 *        this);
36 *
37 *      this.messageFilterStrict = {
38 *        innerWindowID: getInnerWindowID(window),
39 *        extensionID: extensionID,
40 *      };
41 *
42 *      this.messageFilterPermissive = {
43 *        outerWindowID: getOuterWindowID(window),
44 *      };
45 *    },
46 *
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 });
52 *          });
53 *        });
54 *      }
55 *    },
56 *  };
57 *
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:
61 *
62 *  let data = { touchWith: "pencil" };
63 *  let sender = { extensionID, contextID };
64 *  let recipient = { innerWindowID: tab.linkedBrowser.innerWindowID, extensionID };
65 *
66 *  MessageChannel.sendMessage(
67 *    tab.linkedBrowser.messageManager, "ContentScript:TouchContent",
68 *    data, {recipient, sender}
69 *  ).then(result => {
70 *    alert(result.touchResult);
71 *  });
72 *
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.
76 *
77 * For the above client, this might be done from an
78 * inner-window-destroyed observer, when its target scope is destroyed:
79 *
80 *  observe(subject, topic, data) {
81 *    if (topic == "inner-window-destroyed") {
82 *      let innerWindowID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
83 *
84 *      MessageChannel.abortResponses({ innerWindowID });
85 *    }
86 *  },
87 *
88 * From the parent, it may be done when its context is being destroyed:
89 *
90 *  onDestroy() {
91 *    MessageChannel.abortResponses({
92 *      extensionID: this.extensionID,
93 *      contextID: this.contextID,
94 *    });
95 *  },
96 *
97 */
98
99this.EXPORTED_SYMBOLS = ["MessageChannel"];
100
101/* globals MessageChannel */
102
103const Ci = Components.interfaces;
104const Cc = Components.classes;
105const Cu = Components.utils;
106const Cr = Components.results;
107
108Cu.import("resource://gre/modules/XPCOMUtils.jsm");
109Cu.import("resource://gre/modules/Services.jsm");
110
111XPCOMUtils.defineLazyModuleGetter(this, "ExtensionUtils",
112                                  "resource://gre/modules/ExtensionUtils.jsm");
113XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
114                                  "resource://gre/modules/PromiseUtils.jsm");
115XPCOMUtils.defineLazyModuleGetter(this, "Task",
116                                  "resource://gre/modules/Task.jsm");
117
118XPCOMUtils.defineLazyGetter(this, "MessageManagerProxy",
119                            () => ExtensionUtils.MessageManagerProxy);
120
121/**
122 * Handles the mapping and dispatching of messages to their registered
123 * handlers. There is one broker per message manager and class of
124 * messages. Each class of messages is mapped to one native message
125 * name, e.g., "MessageChannel:Message", and is dispatched to handlers
126 * based on an internal message name, e.g., "Extension:ExecuteScript".
127 */
128class FilteringMessageManager {
129  /**
130   * @param {string} messageName
131   *     The name of the native message this broker listens for.
132   * @param {function} callback
133   *     A function which is called for each message after it has been
134   *     mapped to its handler. The function receives two arguments:
135   *
136   *       result:
137   *         An object containing either a `handler` or an `error` property.
138   *         If no error occurs, `handler` will be a matching handler that
139   *         was registered by `addHandler`. Otherwise, the `error` property
140   *         will contain an object describing the error.
141   *
142   *        data:
143   *          An object describing the message, as defined in
144   *          `MessageChannel.addListener`.
145   * @param {nsIMessageListenerManager} messageManager
146   */
147  constructor(messageName, callback, messageManager) {
148    this.messageName = messageName;
149    this.callback = callback;
150    this.messageManager = messageManager;
151
152    this.messageManager.addMessageListener(this.messageName, this, true);
153
154    this.handlers = new Map();
155  }
156
157  /**
158   * Receives a message from our message manager, maps it to a handler, and
159   * passes the result to our message callback.
160   */
161  receiveMessage({data, target}) {
162    let handlers = Array.from(this.getHandlers(data.messageName, data.sender, data.recipient));
163
164    data.target = target;
165    this.callback(handlers, data);
166  }
167
168  /**
169   * Iterates over all handlers for the given message name. If `recipient`
170   * is provided, only iterates over handlers whose filters match it.
171   *
172   * @param {string|number} messageName
173   *     The message for which to return handlers.
174   * @param {object} sender
175   *     The sender data on which to filter handlers.
176   * @param {object} recipient
177   *     The recipient data on which to filter handlers.
178   */
179  * getHandlers(messageName, sender, recipient) {
180    let handlers = this.handlers.get(messageName) || new Set();
181    for (let handler of handlers) {
182      if (MessageChannel.matchesFilter(handler.messageFilterStrict || {}, recipient) &&
183          MessageChannel.matchesFilter(handler.messageFilterPermissive || {}, recipient, false) &&
184          (!handler.filterMessage || handler.filterMessage(sender, recipient))) {
185        yield handler;
186      }
187    }
188  }
189
190  /**
191   * Registers a handler for the given message.
192   *
193   * @param {string} messageName
194   *     The internal message name for which to register the handler.
195   * @param {object} handler
196   *     An opaque handler object. The object may have a
197   *     `messageFilterStrict` and/or a `messageFilterPermissive`
198   *     property and/or a `filterMessage` method on which to filter messages.
199   *
200   *     Final dispatching is handled by the message callback passed to
201   *     the constructor.
202   */
203  addHandler(messageName, handler) {
204    if (!this.handlers.has(messageName)) {
205      this.handlers.set(messageName, new Set());
206    }
207
208    this.handlers.get(messageName).add(handler);
209  }
210
211  /**
212   * Unregisters a handler for the given message.
213   *
214   * @param {string} messageName
215   *     The internal message name for which to unregister the handler.
216   * @param {object} handler
217   *     The handler object to unregister.
218   */
219  removeHandler(messageName, handler) {
220    this.handlers.get(messageName).delete(handler);
221  }
222}
223
224/**
225 * Manages mappings of message managers to their corresponding message
226 * brokers. Brokers are lazily created for each message manager the
227 * first time they are accessed. In the case of content frame message
228 * managers, they are also automatically destroyed when the frame
229 * unload event fires.
230 */
231class FilteringMessageManagerMap extends Map {
232  // Unfortunately, we can't use a WeakMap for this, because message
233  // managers do not support preserved wrappers.
234
235  /**
236   * @param {string} messageName
237   *     The native message name passed to `FilteringMessageManager` constructors.
238   * @param {function} callback
239   *     The message callback function passed to
240   *     `FilteringMessageManager` constructors.
241   */
242  constructor(messageName, callback) {
243    super();
244
245    this.messageName = messageName;
246    this.callback = callback;
247  }
248
249  /**
250   * Returns, and possibly creates, a message broker for the given
251   * message manager.
252   *
253   * @param {nsIMessageListenerManager} target
254   *     The message manager for which to return a broker.
255   *
256   * @returns {FilteringMessageManager}
257   */
258  get(target) {
259    if (this.has(target)) {
260      return super.get(target);
261    }
262
263    let broker = new FilteringMessageManager(this.messageName, this.callback, target);
264    this.set(target, broker);
265
266    if (target instanceof Ci.nsIDOMEventTarget) {
267      let onUnload = event => {
268        target.removeEventListener("unload", onUnload);
269        this.delete(target);
270      };
271      target.addEventListener("unload", onUnload);
272    }
273
274    return broker;
275  }
276}
277
278const MESSAGE_MESSAGE = "MessageChannel:Message";
279const MESSAGE_RESPONSE = "MessageChannel:Response";
280
281this.MessageChannel = {
282  init() {
283    Services.obs.addObserver(this, "message-manager-close", false);
284    Services.obs.addObserver(this, "message-manager-disconnect", false);
285
286    this.messageManagers = new FilteringMessageManagerMap(
287      MESSAGE_MESSAGE, this._handleMessage.bind(this));
288
289    this.responseManagers = new FilteringMessageManagerMap(
290      MESSAGE_RESPONSE, this._handleResponse.bind(this));
291
292    /**
293     * Contains a list of pending responses, either waiting to be
294     * received or waiting to be sent. @see _addPendingResponse
295     */
296    this.pendingResponses = new Set();
297  },
298
299  RESULT_SUCCESS: 0,
300  RESULT_DISCONNECTED: 1,
301  RESULT_NO_HANDLER: 2,
302  RESULT_MULTIPLE_HANDLERS: 3,
303  RESULT_ERROR: 4,
304  RESULT_NO_RESPONSE: 5,
305
306  REASON_DISCONNECTED: {
307    result: this.RESULT_DISCONNECTED,
308    message: "Message manager disconnected",
309  },
310
311  /**
312   * Specifies that only a single listener matching the specified
313   * recipient tag may be listening for the given message, at the other
314   * end of the target message manager.
315   *
316   * If no matching listeners exist, a RESULT_NO_HANDLER error will be
317   * returned. If multiple matching listeners exist, a
318   * RESULT_MULTIPLE_HANDLERS error will be returned.
319   */
320  RESPONSE_SINGLE: 0,
321
322  /**
323   * If multiple message managers matching the specified recipient tag
324   * are listening for a message, all listeners are notified, but only
325   * the first response or error is returned.
326   *
327   * Only handlers which return a value other than `undefined` are
328   * considered to have responded. Returning a Promise which evaluates
329   * to `undefined` is interpreted as an explicit response.
330   *
331   * If no matching listeners exist, a RESULT_NO_HANDLER error will be
332   * returned. If no listeners return a response, a RESULT_NO_RESPONSE
333   * error will be returned.
334   */
335  RESPONSE_FIRST: 1,
336
337  /**
338   * If multiple message managers matching the specified recipient tag
339   * are listening for a message, all listeners are notified, and all
340   * responses are returned as an array, once all listeners have
341   * replied.
342   */
343  RESPONSE_ALL: 2,
344
345  /**
346   * Fire-and-forget: The sender of this message does not expect a reply.
347   */
348  RESPONSE_NONE: 3,
349
350  /**
351   * Initializes message handlers for the given message managers if needed.
352   *
353   * @param {Array<nsIMessageListenerManager>} messageManagers
354   */
355  setupMessageManagers(messageManagers) {
356    for (let mm of messageManagers) {
357      // This call initializes a FilteringMessageManager for |mm| if needed.
358      // The FilteringMessageManager must be created to make sure that senders
359      // of messages that expect a reply, such as MessageChannel:Message, do
360      // actually receive a default reply even if there are no explicit message
361      // handlers.
362      this.messageManagers.get(mm);
363    }
364  },
365
366  /**
367   * Returns true if the properties of the `data` object match those in
368   * the `filter` object. Matching is done on a strict equality basis,
369   * and the behavior varies depending on the value of the `strict`
370   * parameter.
371   *
372   * @param {object} filter
373   *    The filter object to match against.
374   * @param {object} data
375   *    The data object being matched.
376   * @param {boolean} [strict=false]
377   *    If true, all properties in the `filter` object have a
378   *    corresponding property in `data` with the same value. If
379   *    false, properties present in both objects must have the same
380   *    value.
381   * @returns {boolean} True if the objects match.
382   */
383  matchesFilter(filter, data, strict = true) {
384    if (strict) {
385      return Object.keys(filter).every(key => {
386        return key in data && data[key] === filter[key];
387      });
388    }
389    return Object.keys(filter).every(key => {
390      return !(key in data) || data[key] === filter[key];
391    });
392  },
393
394  /**
395   * Adds a message listener to the given message manager.
396   *
397   * @param {nsIMessageListenerManager|Array<nsIMessageListenerManager>} targets
398   *    The message managers on which to listen.
399   * @param {string|number} messageName
400   *    The name of the message to listen for.
401   * @param {MessageReceiver} handler
402   *    The handler to dispatch to. Must be an object with the following
403   *    properties:
404   *
405   *      receiveMessage:
406   *        A method which is called for each message received by the
407   *        listener. The method takes one argument, an object, with the
408   *        following properties:
409   *
410   *          messageName:
411   *            The internal message name, as passed to `sendMessage`.
412   *
413   *          target:
414   *            The message manager which received this message.
415   *
416   *          channelId:
417   *            The internal ID of the transaction, used to map responses to
418   *            the original sender.
419   *
420   *          sender:
421   *            An object describing the sender, as passed to `sendMessage`.
422   *
423   *          recipient:
424   *            An object describing the recipient, as passed to
425   *            `sendMessage`.
426   *
427   *          data:
428   *            The contents of the message, as passed to `sendMessage`.
429   *
430   *        The method may return any structured-clone-compatible
431   *        object, which will be returned as a response to the message
432   *        sender. It may also instead return a `Promise`, the
433   *        resolution or rejection value of which will likewise be
434   *        returned to the message sender.
435   *
436   *      messageFilterStrict:
437   *        An object containing arbitrary properties on which to filter
438   *        received messages. Messages will only be dispatched to this
439   *        object if the `recipient` object passed to `sendMessage`
440   *        matches this filter, as determined by `matchesFilter` with
441   *        `strict=true`.
442   *
443   *      messageFilterPermissive:
444   *        An object containing arbitrary properties on which to filter
445   *        received messages. Messages will only be dispatched to this
446   *        object if the `recipient` object passed to `sendMessage`
447   *        matches this filter, as determined by `matchesFilter` with
448   *        `strict=false`.
449   *
450   *      filterMessage:
451   *        An optional function that prevents the handler from handling a
452   *        message by returning `false`. See `getHandlers` for the parameters.
453   */
454  addListener(targets, messageName, handler) {
455    for (let target of [].concat(targets)) {
456      this.messageManagers.get(target).addHandler(messageName, handler);
457    }
458  },
459
460  /**
461   * Removes a message listener from the given message manager.
462   *
463   * @param {nsIMessageListenerManager|Array<nsIMessageListenerManager>} targets
464   *    The message managers on which to stop listening.
465   * @param {string|number} messageName
466   *    The name of the message to stop listening for.
467   * @param {MessageReceiver} handler
468   *    The handler to stop dispatching to.
469   */
470  removeListener(targets, messageName, handler) {
471    for (let target of [].concat(targets)) {
472      if (this.messageManagers.has(target)) {
473        this.messageManagers.get(target).removeHandler(messageName, handler);
474      }
475    }
476  },
477
478  /**
479   * Sends a message via the given message manager. Returns a promise which
480   * resolves or rejects with the return value of the message receiver.
481   *
482   * The promise also rejects if there is no matching listener, or the other
483   * side of the message manager disconnects before the response is received.
484   *
485   * @param {nsIMessageSender} target
486   *    The message manager on which to send the message.
487   * @param {string} messageName
488   *    The name of the message to send, as passed to `addListener`.
489   * @param {object} data
490   *    A structured-clone-compatible object to send to the message
491   *    recipient.
492   * @param {object} [options]
493   *    An object containing any of the following properties:
494   * @param {object} [options.recipient]
495   *    A structured-clone-compatible object to identify the message
496   *    recipient. The object must match the `messageFilterStrict` and
497   *    `messageFilterPermissive` filters defined by recipients in order
498   *    for the message to be received.
499   * @param {object} [options.sender]
500   *    A structured-clone-compatible object to identify the message
501   *    sender. This object may also be used to avoid delivering the
502   *    message to the sender, and as a filter to prematurely
503   *    abort responses when the sender is being destroyed.
504   *    @see `abortResponses`.
505   * @param {integer} [options.responseType=RESPONSE_SINGLE]
506   *    Specifies the type of response expected. See the `RESPONSE_*`
507   *    contents for details.
508   * @returns {Promise}
509   */
510  sendMessage(target, messageName, data, options = {}) {
511    let sender = options.sender || {};
512    let recipient = options.recipient || {};
513    let responseType = options.responseType || this.RESPONSE_SINGLE;
514
515    let channelId = ExtensionUtils.getUniqueId();
516    let message = {messageName, channelId, sender, recipient, data, responseType};
517
518    if (responseType == this.RESPONSE_NONE) {
519      try {
520        target.sendAsyncMessage(MESSAGE_MESSAGE, message);
521      } catch (e) {
522        // Caller is not expecting a reply, so dump the error to the console.
523        Cu.reportError(e);
524        return Promise.reject(e);
525      }
526      return Promise.resolve();  // Not expecting any reply.
527    }
528
529    let deferred = PromiseUtils.defer();
530    deferred.sender = recipient;
531    deferred.messageManager = target;
532
533    this._addPendingResponse(deferred);
534
535    // The channel ID is used as the message name when routing responses.
536    // Add a message listener to the response broker, and remove it once
537    // we've gotten (or canceled) a response.
538    let broker = this.responseManagers.get(target);
539    broker.addHandler(channelId, deferred);
540
541    let cleanup = () => {
542      broker.removeHandler(channelId, deferred);
543    };
544    deferred.promise.then(cleanup, cleanup);
545
546    try {
547      target.sendAsyncMessage(MESSAGE_MESSAGE, message);
548    } catch (e) {
549      deferred.reject(e);
550    }
551    return deferred.promise;
552  },
553
554  _callHandlers(handlers, data) {
555    let responseType = data.responseType;
556
557    // At least one handler is required for all response types but
558    // RESPONSE_ALL.
559    if (handlers.length == 0 && responseType != this.RESPONSE_ALL) {
560      return Promise.reject({result: MessageChannel.RESULT_NO_HANDLER,
561                             message: "No matching message handler"});
562    }
563
564    if (responseType == this.RESPONSE_SINGLE) {
565      if (handlers.length > 1) {
566        return Promise.reject({result: MessageChannel.RESULT_MULTIPLE_HANDLERS,
567                               message: `Multiple matching handlers for ${data.messageName}`});
568      }
569
570      // Note: We use `new Promise` rather than `Promise.resolve` here
571      // so that errors from the handler are trapped and converted into
572      // rejected promises.
573      return new Promise(resolve => {
574        resolve(handlers[0].receiveMessage(data));
575      });
576    }
577
578    let responses = handlers.map(handler => {
579      try {
580        return handler.receiveMessage(data);
581      } catch (e) {
582        return Promise.reject(e);
583      }
584    });
585    responses = responses.filter(response => response !== undefined);
586
587    switch (responseType) {
588      case this.RESPONSE_FIRST:
589        if (responses.length == 0) {
590          return Promise.reject({result: MessageChannel.RESULT_NO_RESPONSE,
591                                 message: "No handler returned a response"});
592        }
593
594        return Promise.race(responses);
595
596      case this.RESPONSE_ALL:
597        return Promise.all(responses);
598    }
599    return Promise.reject({message: "Invalid response type"});
600  },
601
602  /**
603   * Handles dispatching message callbacks from the message brokers to their
604   * appropriate `MessageReceivers`, and routing the responses back to the
605   * original senders.
606   *
607   * Each handler object is a `MessageReceiver` object as passed to
608   * `addListener`.
609   *
610   * @param {Array<MessageHandler>} handlers
611   * @param {object} data
612   * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target
613   */
614  _handleMessage(handlers, data) {
615    if (data.responseType == this.RESPONSE_NONE) {
616      handlers.forEach(handler => {
617        // The sender expects no reply, so dump any errors to the console.
618        new Promise(resolve => {
619          resolve(handler.receiveMessage(data));
620        }).catch(e => {
621          Cu.reportError(e.stack ? `${e}\n${e.stack}` : e.message || e);
622        });
623      });
624      // Note: Unhandled messages are silently dropped.
625      return;
626    }
627
628    let target = new MessageManagerProxy(data.target);
629
630    let deferred = {
631      sender: data.sender,
632      messageManager: target,
633    };
634    deferred.promise = new Promise((resolve, reject) => {
635      deferred.reject = reject;
636
637      this._callHandlers(handlers, data).then(resolve, reject);
638    }).then(
639      value => {
640        let response = {
641          result: this.RESULT_SUCCESS,
642          messageName: data.channelId,
643          recipient: {},
644          value,
645        };
646
647        target.sendAsyncMessage(MESSAGE_RESPONSE, response);
648      },
649      error => {
650        let response = {
651          result: this.RESULT_ERROR,
652          messageName: data.channelId,
653          recipient: {},
654          error: {},
655        };
656
657        if (error && typeof(error) == "object") {
658          if (error.result) {
659            response.result = error.result;
660          }
661          // Error objects are not structured-clonable, so just copy
662          // over the important properties.
663          for (let key of ["fileName", "filename", "lineNumber",
664                           "columnNumber", "message", "stack", "result"]) {
665            if (key in error) {
666              response.error[key] = error[key];
667            }
668          }
669        }
670
671        target.sendAsyncMessage(MESSAGE_RESPONSE, response);
672      }).catch(e => {
673        Cu.reportError(e);
674      }).then(() => {
675        target.dispose();
676      });
677
678    this._addPendingResponse(deferred);
679  },
680
681  /**
682   * Handles message callbacks from the response brokers.
683   *
684   * Each handler object is a deferred object created by `sendMessage`, and
685   * should be resolved or rejected based on the contents of the response.
686   *
687   * @param {Array<MessageHandler>} handlers
688   * @param {object} data
689   * @param {nsIMessageSender|{messageManager:nsIMessageSender}} data.target
690   */
691  _handleResponse(handlers, data) {
692    // If we have an error at this point, we have handler to report it to,
693    // so just log it.
694    if (handlers.length == 0) {
695      Cu.reportError(`No matching message response handler for ${data.messageName}`);
696    } else if (handlers.length > 1) {
697      Cu.reportError(`Multiple matching response handlers for ${data.messageName}`);
698    } else if (data.result === this.RESULT_SUCCESS) {
699      handlers[0].resolve(data.value);
700    } else {
701      handlers[0].reject(data.error);
702    }
703  },
704
705  /**
706   * Adds a pending response to the the `pendingResponses` list.
707   *
708   * The response object must be a deferred promise with the following
709   * properties:
710   *
711   *  promise:
712   *    The promise object which resolves or rejects when the response
713   *    is no longer pending.
714   *
715   *  reject:
716   *    A function which, when called, causes the `promise` object to be
717   *    rejected.
718   *
719   *  sender:
720   *    A sender object, as passed to `sendMessage.
721   *
722   *  messageManager:
723   *    The message manager the response will be sent or received on.
724   *
725   * When the promise resolves or rejects, it will be removed from the
726   * list.
727   *
728   * These values are used to clear pending responses when execution
729   * contexts are destroyed.
730   *
731   * @param {Deferred} deferred
732   */
733  _addPendingResponse(deferred) {
734    let cleanup = () => {
735      this.pendingResponses.delete(deferred);
736    };
737    this.pendingResponses.add(deferred);
738    deferred.promise.then(cleanup, cleanup);
739  },
740
741  /**
742   * Aborts any pending message responses to senders matching the given
743   * filter.
744   *
745   * @param {object} sender
746   *    The object on which to filter senders, as determined by
747   *    `matchesFilter`.
748   * @param {object} [reason]
749   *    An optional object describing the reason the response was aborted.
750   *    Will be passed to the promise rejection handler of all aborted
751   *    responses.
752   */
753  abortResponses(sender, reason = this.REASON_DISCONNECTED) {
754    for (let response of this.pendingResponses) {
755      if (this.matchesFilter(sender, response.sender)) {
756        response.reject(reason);
757      }
758    }
759  },
760
761  /**
762   * Aborts any pending message responses to the broker for the given
763   * message manager.
764   *
765   * @param {nsIMessageListenerManager} target
766   *    The message manager for which to abort brokers.
767   * @param {object} reason
768   *    An object describing the reason the responses were aborted.
769   *    Will be passed to the promise rejection handler of all aborted
770   *    responses.
771   */
772  abortMessageManager(target, reason) {
773    for (let response of this.pendingResponses) {
774      if (MessageManagerProxy.matches(response.messageManager, target)) {
775        response.reject(reason);
776      }
777    }
778  },
779
780  observe(subject, topic, data) {
781    switch (topic) {
782      case "message-manager-close":
783      case "message-manager-disconnect":
784        try {
785          if (this.responseManagers.has(subject)) {
786            this.abortMessageManager(subject, this.REASON_DISCONNECTED);
787          }
788        } finally {
789          this.responseManagers.delete(subject);
790          this.messageManagers.delete(subject);
791        }
792        break;
793    }
794  },
795};
796
797MessageChannel.init();
798