/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const { EventEmitter } = ChromeUtils.importESModule( "resource://gre/modules/EventEmitter.sys.mjs" ); /** * This is the interface for the async setting classes to implement. * * For the actual implementation see AsyncSettingMixin. */ export class AsyncSetting extends EventEmitter { /** @type {string} */ static id = ""; /** @type {Object} */ static controllingExtensionInfo; defaultValue = ""; defaultDisabled = false; defaultVisible = true; defaultGetControlConfig = {}; /** * Emit a change event to notify listeners that the setting's data has * changed and should be updated. */ emitChange() { this.emit("change"); } /** * Setup any external listeners that are required for managing this * setting's state. When the state needs to update the Setting.emitChange method should be called. * @returns {Function | void} Teardown function to clean up external listeners. */ setup() {} /** * Get the value of this setting. * @abstract * @returns {Promise} */ async get() {} /** * Set the value of this setting. * @abstract * @param value {any} The value from the input that triggered the update. * @returns {Promise} */ async set() {} /** * Whether the control should be disabled. * @returns {Promise} */ async disabled() { return false; } /** * Whether the control should be visible. * @returns {Promise} */ async visible() { return true; } /** * Override the initial control config. This will be spread into the * initial config, with this object taking precedence. * @returns {Promise} */ async getControlConfig() { return {}; } /** * Callback fired after a user has changed the setting's value. Useful for * recording telemetry. * @param value {any} * @returns {Promise} */ async onUserChange() {} } /** * Wraps an AsyncSetting and adds caching of values to provide a synchronous * API to the Setting class. */ export class AsyncSettingHandler { /** @type {AsyncSetting} */ asyncSetting; /** @type {Function} */ #emitChange; /** @type {import("./Setting.mjs").PreferenceSettingDepsMap} */ deps; /** @type {Setting} */ setting; /** * @param {AsyncSetting} asyncSetting */ constructor(asyncSetting) { this.asyncSetting = asyncSetting; this.#emitChange = () => {}; // Initialize cached values with defaults this.cachedValue = asyncSetting.defaultValue; this.cachedDisabled = asyncSetting.defaultDisabled; this.cachedVisible = asyncSetting.defaultVisible; this.cachedGetControlConfig = asyncSetting.defaultGetControlConfig; // Listen for change events from the async setting this.asyncSetting.on("change", () => this.refresh()); } /** * @param emitChange {Function} * @param deps {Record} * @param setting {Setting} * @returns {Function | void} */ setup(emitChange, deps, setting) { let teardown = this.asyncSetting.setup(); this.#emitChange = emitChange; this.deps = deps; this.setting = setting; this.refresh(); return teardown; } /** * Called to trigger async tasks and re-cache values. */ async refresh() { [ this.cachedValue, this.cachedDisabled, this.cachedVisible, this.cachedGetControlConfig, ] = await Promise.all([ this.asyncSetting.get(), this.asyncSetting.disabled(), this.asyncSetting.visible(), this.asyncSetting.getControlConfig(), ]); this.#emitChange(); } /** * @returns {boolean | number | string | void} */ get() { return this.cachedValue; } /** * @param value {any} * @returns {Promise} */ set(value) { return this.asyncSetting.set(value); } /** * @returns {boolean} */ disabled() { return this.cachedDisabled; } /** * @returns {boolean} */ visible() { return this.cachedVisible; } /** * @param config {Object} * @returns {Object} */ getControlConfig(config) { return { ...config, ...this.cachedGetControlConfig, }; } /** * @param value {any} * @returns {Promise} */ onUserChange(value) { return this.asyncSetting.onUserChange(value); } }