OpenSearchManager.sys.mjs (7012B)
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 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", 9 }); 10 11 /** 12 * Manages the set of available opensearch engines per browser. 13 */ 14 class _OpenSearchManager { 15 /** 16 * @typedef {object} OpenSearchData 17 * @property {string} uri 18 * The uri of the opensearch XML. 19 * @property {string} title 20 * The name of the engine. 21 * @property {string} icon 22 * Data URI containing the engine's icon. 23 */ 24 25 /** 26 * @type {WeakMap<MozBrowser, OpenSearchData[]>} 27 */ 28 #offeredEngines = new WeakMap(); 29 30 /** 31 * @type {WeakMap<MozBrowser, OpenSearchData[]>} 32 */ 33 #hiddenEngines = new WeakMap(); 34 35 constructor() { 36 Services.obs.addObserver(this, "browser-search-engine-modified"); 37 } 38 39 /** 40 * Observer for browser-search-engine-modified. 41 * 42 * @param {nsISearchEngine} engine 43 * The modified engine. 44 * @param {string} _topic 45 * Always browser-search-engine-modified. 46 * @param {string} data 47 * The type of modification. 48 */ 49 observe(engine, _topic, data) { 50 // There are two kinds of search engine objects: nsISearchEngine objects 51 // and plain OpenSearchData objects. `engine` in this observer is the 52 // former and the arrays in #offeredEngines and #hiddenEngines contain the 53 // latter. They are related by their names. 54 switch (data) { 55 case "engine-added": 56 // An engine was added to the search service. If a page is offering the 57 // engine, then the engine needs to be removed from the corresponding 58 // browser's offered engines. 59 this.#removeMaybeOfferedEngine(engine.name); 60 break; 61 case "engine-removed": 62 // An engine was removed from the search service. If a page is offering 63 // the engine, then the engine needs to be added back to the corresponding 64 // browser's offered engines. 65 this.#addMaybeOfferedEngine(engine.name); 66 break; 67 } 68 } 69 70 /** 71 * Adds an open search engine to the list of available engines for a browser. 72 * If an engine with that name is already installed, adds it to the list 73 * of hidden engines instead. 74 * 75 * @param {MozBrowser} browser 76 * The browser offering the engine. 77 * @param {{title: string, href: string}} engine 78 * The title of the engine and the url to the opensearch XML. 79 */ 80 addEngine(browser, engine) { 81 if (!Services.search.hasSuccessfullyInitialized) { 82 // We haven't finished initializing search yet. This means we can't 83 // call getEngineByName here. Since this is only on start-up and unlikely 84 // to happen in the normal case, we'll just return early rather than 85 // trying to handle it asynchronously. 86 return; 87 } 88 // Check to see whether we've already added an engine with this title 89 if (this.#offeredEngines.get(browser)?.some(e => e.title == engine.title)) { 90 return; 91 } 92 93 // If this engine (identified by title) is already in the list, add it 94 // to the list of hidden engines rather than to the main list. 95 let shouldBeHidden = !!Services.search.getEngineByName(engine.title); 96 97 let engines = 98 (shouldBeHidden 99 ? this.#hiddenEngines.get(browser) 100 : this.#offeredEngines.get(browser)) || []; 101 102 engines.push({ 103 uri: engine.href, 104 title: engine.title, 105 get icon() { 106 return browser.mIconURL; 107 }, 108 }); 109 110 if (shouldBeHidden) { 111 this.#hiddenEngines.set(browser, engines); 112 } else { 113 let win = browser.ownerGlobal; 114 this.#offeredEngines.set(browser, engines); 115 if (browser == win.gBrowser.selectedBrowser) { 116 this.updateOpenSearchBadge(win); 117 } 118 } 119 } 120 121 /** 122 * Updates the browser UI to show whether or not additional engines are 123 * available when a page is loaded or the user switches tabs to a page that 124 * has open search engines. 125 * 126 * @param {WindowProxy} win 127 * The window whose UI should be updated. 128 */ 129 updateOpenSearchBadge(win) { 130 let engines = this.#offeredEngines.get(win.gBrowser.selectedBrowser); 131 for (let urlbar of win.document.querySelectorAll("moz-urlbar")) { 132 if (!urlbar.controller) { 133 // This means it is not initialized and happens 134 // if the new searchbar is disabled. 135 continue; 136 } 137 urlbar.addSearchEngineHelper.setEnginesFromBrowser( 138 win.gBrowser.selectedBrowser, 139 engines || [] 140 ); 141 } 142 143 let searchBar = win.document.getElementById("searchbar"); 144 if (!searchBar) { 145 return; 146 } 147 148 if (engines && engines.length) { 149 searchBar.setAttribute("addengines", "true"); 150 } else { 151 searchBar.removeAttribute("addengines"); 152 } 153 } 154 155 #addMaybeOfferedEngine(engineName) { 156 for (let win of lazy.BrowserWindowTracker.orderedWindows) { 157 for (let browser of win.gBrowser.browsers) { 158 let hiddenEngines = this.#hiddenEngines.get(browser) || []; 159 let offeredEngines = this.#offeredEngines.get(browser) || []; 160 161 for (let i = 0; i < hiddenEngines.length; i++) { 162 if (hiddenEngines[i].title == engineName) { 163 offeredEngines.push(hiddenEngines[i]); 164 if (offeredEngines.length == 1) { 165 this.#offeredEngines.set(browser, offeredEngines); 166 } 167 168 hiddenEngines.splice(i, 1); 169 if (browser == win.gBrowser.selectedBrowser) { 170 this.updateOpenSearchBadge(win); 171 } 172 break; 173 } 174 } 175 } 176 } 177 } 178 179 #removeMaybeOfferedEngine(engineName) { 180 for (let win of lazy.BrowserWindowTracker.orderedWindows) { 181 for (let browser of win.gBrowser.browsers) { 182 let hiddenEngines = this.#hiddenEngines.get(browser) || []; 183 let offeredEngines = this.#offeredEngines.get(browser) || []; 184 185 for (let i = 0; i < offeredEngines.length; i++) { 186 if (offeredEngines[i].title == engineName) { 187 hiddenEngines.push(offeredEngines[i]); 188 if (hiddenEngines.length == 1) { 189 this.#hiddenEngines.set(browser, hiddenEngines); 190 } 191 192 offeredEngines.splice(i, 1); 193 if (browser == win.gBrowser.selectedBrowser) { 194 this.updateOpenSearchBadge(win); 195 } 196 break; 197 } 198 } 199 } 200 } 201 } 202 203 /** 204 * Get the open search engines offered by a certain browser. 205 * 206 * @param {MozBrowser} browser 207 * The browser for which to get the engines. 208 * @returns {OpenSearchData[]} 209 * The open search engines. 210 */ 211 getEngines(browser) { 212 return this.#offeredEngines.get(browser) || []; 213 } 214 215 clearEngines(browser) { 216 this.#offeredEngines.delete(browser); 217 this.#hiddenEngines.delete(browser); 218 } 219 } 220 221 export const OpenSearchManager = new _OpenSearchManager();