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