EssentialDomainsRemoteSettings.sys.mjs (3878B)
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 https://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", 9 RemoteSettingsClient: 10 "resource://services-settings/RemoteSettingsClient.sys.mjs", 11 }); 12 13 export const ESSENTIAL_DOMAINS_REMOTE_BUCKET = "moz-essential-domain-fallbacks"; 14 15 export class EssentialDomainsRemoteSettings { 16 #initialized = false; 17 #fallbackDomains; 18 classID = Components.ID("{962dbf40-2c3f-4c1f-8ae8-90e8c9d85368}"); 19 QueryInterface = ChromeUtils.generateQI(["nsIObserver"]); 20 21 observe(subject, topic) { 22 // signal selected because RemoteSettingsClient is first getting initialised 23 // by the AddonManager at addons-startup 24 if (topic == "profile-after-change" || !this.#initialized) { 25 this.#initialized = true; 26 this.#init(); 27 } 28 } 29 30 /** 31 * This method updates the io service with the local scheme list used to 32 * bypass the defaultURI parser and use the simpleURI parser. 33 * It also subscribes to Remote Settings changes to this list which are then 34 * broadcast to processes interested in URL parsing. 35 * 36 * note that there doesn't appear to be a way to get a URI with a non-special 37 * scheme into about:preferences so it should be safe to spin this up early 38 */ 39 async #init() { 40 if (!this.#fallbackDomains) { 41 this.#fallbackDomains = lazy.RemoteSettings( 42 ESSENTIAL_DOMAINS_REMOTE_BUCKET 43 ); 44 } 45 46 // Trigger a get from local remote settings and update the io service. 47 const settingsList = await this.#getFallbackList(); 48 for (let setting of settingsList) { 49 Services.io.addEssentialDomainMapping(setting.from, setting.to); 50 } 51 52 // Listen for future updates after we first get the values. 53 this.#fallbackDomains.on("sync", this.#updateFallbackDomains.bind(this)); 54 } 55 56 async #updateFallbackDomains() { 57 Services.io.clearEssentialDomainMapping(); 58 59 const settingsList = await this.#getFallbackList(); 60 for (let setting of settingsList) { 61 Services.io.addEssentialDomainMapping(setting.from, setting.to); 62 } 63 } 64 65 async #getFallbackList() { 66 if (this._getSettingsPromise) { 67 return this._getSettingsPromise; 68 } 69 70 const settings = await (this._getSettingsPromise = 71 this.#getFallbackDomains()); 72 delete this._getSettingsPromise; 73 return settings; 74 } 75 76 /** 77 * Obtains the current bypass list from remote settings. This includes 78 * verifying the signature of the bypass list within the database. 79 * 80 * If the signature in the database is invalid, the database will be wiped 81 * and the stored dump will be used, until the settings next update. 82 * 83 * Note that this may cause a network check of the certificate, but that 84 * should generally be quick. 85 * 86 * @param {boolean} [firstTime] 87 * Internal boolean to indicate if this is the first time check or not. 88 * @returns {Array} 89 * An array of objects in the database, or an empty array if none 90 * could be obtained. 91 */ 92 async #getFallbackDomains(firstTime = true) { 93 let result = []; 94 try { 95 result = await this.#fallbackDomains.get({ 96 verifySignature: true, 97 }); 98 } catch (ex) { 99 if ( 100 ex instanceof lazy.RemoteSettingsClient.InvalidSignatureError && 101 firstTime 102 ) { 103 // The local database is invalid, try and reset it. 104 await this.#fallbackDomains.db.clear(); 105 // Now call this again. 106 return this.#getFallbackDomains(false); 107 } 108 // Don't throw an error just log it, just continue with no data, and hopefully 109 // a sync will fix things later on. 110 console.error(ex); 111 } 112 return result; 113 } 114 }