EveryWindow.sys.mjs (3561B)
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 /* 6 * This module enables consumers to register callbacks on every 7 * current and future browser window. 8 * 9 * Usage: EveryWindow.registerCallback(id, init, uninit); 10 * EveryWindow.unregisterCallback(id); 11 * 12 * id is expected to be a unique value that identifies the 13 * consumer, to be used for unregistration. If the id is already 14 * in use, registerCallback returns false without doing anything. 15 * 16 * Each callback will receive the window for which it is presently 17 * being called as the first argument. 18 * 19 * init is called on every existing window at the time of registration, 20 * and on all future windows at browser-delayed-startup-finished. 21 * 22 * uninit is called on every existing window if requested at the time 23 * of unregistration, and at the time of domwindowclosed. 24 * If the window is closing, a second argument is passed with value `true`. 25 */ 26 27 var initialized = false; 28 var callbacks = new Map(); 29 30 function callForEveryWindow(callback) { 31 let windowList = Services.wm.getEnumerator("navigator:browser"); 32 for (let win of windowList) { 33 win.delayedStartupPromise.then(() => { 34 callback(win); 35 }); 36 } 37 } 38 39 export const EveryWindow = { 40 /** 41 * The current list of all browser windows whose delayedStartupPromise has resolved 42 */ 43 get readyWindows() { 44 return Array.from(Services.wm.getEnumerator("navigator:browser")).filter( 45 win => win.gBrowserInit?.delayedStartupFinished 46 ); 47 }, 48 49 /** 50 * Registers init and uninit functions to be called on every window. 51 * 52 * @param {string} id A unique identifier for the consumer, to be 53 * used for unregistration. 54 * @param {function} init The function to be called on every currently 55 * existing window and every future window after delayed startup. 56 * @param {function} uninit The function to be called on every window 57 * at the time of callback unregistration or after domwindowclosed. 58 * @returns {boolean} Returns false if the id was taken, else true. 59 */ 60 registerCallback: function EW_registerCallback(id, init, uninit) { 61 if (callbacks.has(id)) { 62 return false; 63 } 64 65 if (!initialized) { 66 let addUnloadListener = win => { 67 function observer(subject, topic) { 68 if (topic == "domwindowclosed" && subject === win) { 69 Services.ww.unregisterNotification(observer); 70 for (let c of callbacks.values()) { 71 c.uninit(win, true); 72 } 73 } 74 } 75 Services.ww.registerNotification(observer); 76 }; 77 78 Services.obs.addObserver(win => { 79 for (let c of callbacks.values()) { 80 c.init(win); 81 } 82 addUnloadListener(win); 83 }, "browser-delayed-startup-finished"); 84 85 callForEveryWindow(addUnloadListener); 86 87 initialized = true; 88 } 89 90 callForEveryWindow(init); 91 callbacks.set(id, { id, init, uninit }); 92 93 return true; 94 }, 95 96 /** 97 * Unregisters a previously registered consumer. 98 * 99 * @param {string} id The id to unregister. 100 * @param {boolean} [callUninit=true] Whether to call the registered uninit 101 * function on every window. 102 */ 103 unregisterCallback: function EW_unregisterCallback(id, callUninit = true) { 104 if (!callbacks.has(id)) { 105 return; 106 } 107 108 if (callUninit) { 109 callForEveryWindow(callbacks.get(id).uninit); 110 } 111 112 callbacks.delete(id); 113 }, 114 };