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