tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ASRouterPreferences.sys.mjs (9804B)


      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 // eslint-disable-next-line mozilla/use-static-import
      6 const { XPCOMUtils } = ChromeUtils.importESModule(
      7  "resource://gre/modules/XPCOMUtils.sys.mjs"
      8 );
      9 
     10 const lazy = {};
     11 
     12 ChromeUtils.defineESModuleGetters(lazy, {
     13  SelectableProfileService:
     14    "resource:///modules/profiles/SelectableProfileService.sys.mjs",
     15 });
     16 
     17 const PROVIDER_PREF_BRANCH =
     18  "browser.newtabpage.activity-stream.asrouter.providers.";
     19 const DEVTOOLS_PREF =
     20  "browser.newtabpage.activity-stream.asrouter.devtoolsEnabled";
     21 
     22 /**
     23 * Use `ASRouterPreferences.console.debug()` and friends from ASRouter files to
     24 * log messages during development.  See LOG_LEVELS in Console.sys.mjs for the
     25 * available methods as well as the available values for this pref.
     26 */
     27 const DEBUG_PREF = "browser.newtabpage.activity-stream.asrouter.debugLogLevel";
     28 
     29 const FXA_USERNAME_PREF = "services.sync.username";
     30 // To observe changes to Selectable Profiles
     31 const SELECTABLE_PROFILES_UPDATED = "sps-profiles-updated";
     32 const MESSAGING_PROFILE_ID_PREF = "messaging-system.profile.messagingProfileId";
     33 
     34 const DEFAULT_STATE = {
     35  _initialized: false,
     36  _providers: null,
     37  _providerPrefBranch: PROVIDER_PREF_BRANCH,
     38  _devtoolsEnabled: null,
     39  _devtoolsPref: DEVTOOLS_PREF,
     40 };
     41 
     42 const USER_PREFERENCES = {
     43  cfrAddons: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons",
     44  cfrFeatures:
     45    "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
     46 };
     47 
     48 XPCOMUtils.defineLazyPreferenceGetter(
     49  lazy,
     50  "messagingProfileId",
     51  MESSAGING_PROFILE_ID_PREF,
     52  ""
     53 );
     54 
     55 XPCOMUtils.defineLazyPreferenceGetter(
     56  lazy,
     57  "disableSingleProfileMessaging",
     58  "messaging-system.profile.singleProfileMessaging.disable",
     59  false,
     60  async prefVal => {
     61    if (!prefVal) {
     62      return;
     63    }
     64    // unset the user value of the profile ID pref
     65    Services.prefs.clearUserPref(MESSAGING_PROFILE_ID_PREF);
     66    await lazy.SelectableProfileService.flushSharedPrefToDatabase(
     67      MESSAGING_PROFILE_ID_PREF
     68    );
     69  }
     70 );
     71 
     72 // Preferences that influence targeting attributes. When these change we need
     73 // to re-evaluate if the message targeting still matches
     74 export const TARGETING_PREFERENCES = [FXA_USERNAME_PREF];
     75 
     76 export const TEST_PROVIDERS = [
     77  {
     78    id: "panel_local_testing",
     79    type: "local",
     80    localProvider: "PanelTestProvider",
     81    enabled: true,
     82  },
     83 ];
     84 
     85 export class _ASRouterPreferences {
     86  constructor() {
     87    Object.assign(this, DEFAULT_STATE);
     88    this._callbacks = new Set();
     89 
     90    ChromeUtils.defineLazyGetter(this, "console", () => {
     91      let { ConsoleAPI } = ChromeUtils.importESModule(
     92        /* eslint-disable mozilla/use-console-createInstance */
     93        "resource://gre/modules/Console.sys.mjs"
     94      );
     95      let consoleOptions = {
     96        maxLogLevel: "error",
     97        maxLogLevelPref: DEBUG_PREF,
     98        prefix: "ASRouter",
     99      };
    100      return new ConsoleAPI(consoleOptions);
    101    });
    102  }
    103 
    104  _transformPersonalizedCfrScores(value) {
    105    let result = {};
    106    try {
    107      result = JSON.parse(value);
    108    } catch (e) {
    109      console.error(e);
    110    }
    111    return result;
    112  }
    113 
    114  _getProviderConfig() {
    115    const prefList = Services.prefs.getChildList(this._providerPrefBranch);
    116    return prefList.reduce((filtered, pref) => {
    117      let value;
    118      try {
    119        value = JSON.parse(Services.prefs.getStringPref(pref, ""));
    120      } catch (e) {
    121        console.error(
    122          `Could not parse ASRouter preference. Try resetting ${pref} in about:config.`
    123        );
    124      }
    125      if (value) {
    126        filtered.push(value);
    127      }
    128      return filtered;
    129    }, []);
    130  }
    131 
    132  get providers() {
    133    if (!this._initialized || this._providers === null) {
    134      const config = this._getProviderConfig();
    135      const providers = config.map(provider => Object.freeze(provider));
    136      if (this.devtoolsEnabled) {
    137        providers.unshift(...TEST_PROVIDERS);
    138      }
    139      this._providers = Object.freeze(providers);
    140    }
    141 
    142    return this._providers;
    143  }
    144 
    145  enableOrDisableProvider(id, value) {
    146    const providers = this._getProviderConfig();
    147    const config = providers.find(p => p.id === id);
    148    if (!config) {
    149      console.error(
    150        `Cannot set enabled state for '${id}' because the pref ${this._providerPrefBranch}${id} does not exist or is not correctly formatted.`
    151      );
    152      return;
    153    }
    154 
    155    Services.prefs.setStringPref(
    156      this._providerPrefBranch + id,
    157      JSON.stringify({ ...config, enabled: value })
    158    );
    159  }
    160 
    161  resetProviderPref() {
    162    for (const pref of Services.prefs.getChildList(this._providerPrefBranch)) {
    163      Services.prefs.clearUserPref(pref);
    164    }
    165    for (const id of Object.keys(USER_PREFERENCES)) {
    166      Services.prefs.clearUserPref(USER_PREFERENCES[id]);
    167    }
    168  }
    169 
    170  /**
    171   * Bug 1800087 - Migrate the ASRouter message provider prefs' values to the
    172   * current format (provider.bucket -> provider.collection).
    173   *
    174   * TODO (Bug 1800937): Remove migration code after the next watershed release.
    175   */
    176  _migrateProviderPrefs() {
    177    const prefList = Services.prefs.getChildList(this._providerPrefBranch);
    178    for (const pref of prefList) {
    179      if (!Services.prefs.prefHasUserValue(pref)) {
    180        continue;
    181      }
    182      try {
    183        let value = JSON.parse(Services.prefs.getStringPref(pref, ""));
    184        if (value && "bucket" in value && !("collection" in value)) {
    185          const { bucket, ...rest } = value;
    186          Services.prefs.setStringPref(
    187            pref,
    188            JSON.stringify({
    189              ...rest,
    190              collection: bucket,
    191            })
    192          );
    193        }
    194      } catch (e) {
    195        Services.prefs.clearUserPref(pref);
    196      }
    197    }
    198  }
    199 
    200  async _maybeSetMessagingProfileID() {
    201    // If the pref for this mitigation is disabled, skip these checks.
    202    if (lazy.disableSingleProfileMessaging) {
    203      return;
    204    }
    205    await lazy.SelectableProfileService.init();
    206    let currentProfileID =
    207      lazy.SelectableProfileService.currentProfile?.id?.toString();
    208    // if multiple profiles exist and messagingProfileID isn't set,
    209    // set it and copy it around to the rest of the profile group.
    210    try {
    211      if (!lazy.messagingProfileId && currentProfileID) {
    212        Services.prefs.setStringPref(
    213          MESSAGING_PROFILE_ID_PREF,
    214          currentProfileID
    215        );
    216        await lazy.SelectableProfileService.trackPref(
    217          MESSAGING_PROFILE_ID_PREF
    218        );
    219      }
    220      // if multiple profiles exist and messagingProfileID is set, make
    221      // sure that a profile with that ID exists.
    222      if (
    223        lazy.messagingProfileId &&
    224        lazy.SelectableProfileService.initialized
    225      ) {
    226        let messagingProfile = await lazy.SelectableProfileService.getProfile(
    227          parseInt(lazy.messagingProfileId, 10)
    228        );
    229        if (!messagingProfile) {
    230          // the messaging profile got deleted; set the current profile instead
    231          Services.prefs.setStringPref(
    232            MESSAGING_PROFILE_ID_PREF,
    233            currentProfileID
    234          );
    235        }
    236      }
    237    } catch (e) {
    238      console.error(`Could not set profile ID: ${e}`);
    239    }
    240  }
    241 
    242  get devtoolsEnabled() {
    243    if (!this._initialized || this._devtoolsEnabled === null) {
    244      this._devtoolsEnabled = Services.prefs.getBoolPref(
    245        this._devtoolsPref,
    246        false
    247      );
    248    }
    249    return this._devtoolsEnabled;
    250  }
    251 
    252  observe(aSubject, aTopic, aPrefName) {
    253    if (aPrefName && aPrefName.startsWith(this._providerPrefBranch)) {
    254      this._providers = null;
    255    } else if (aPrefName === this._devtoolsPref) {
    256      this._providers = null;
    257      this._devtoolsEnabled = null;
    258    }
    259    this._callbacks.forEach(cb => cb(aPrefName));
    260  }
    261 
    262  getUserPreference(name) {
    263    const prefName = USER_PREFERENCES[name] || name;
    264    return Services.prefs.getBoolPref(prefName, true);
    265  }
    266 
    267  getAllUserPreferences() {
    268    const values = {};
    269    for (const id of Object.keys(USER_PREFERENCES)) {
    270      values[id] = this.getUserPreference(id);
    271    }
    272    return values;
    273  }
    274 
    275  setUserPreference(providerId, value) {
    276    if (!USER_PREFERENCES[providerId]) {
    277      return;
    278    }
    279    Services.prefs.setBoolPref(USER_PREFERENCES[providerId], value);
    280  }
    281 
    282  addListener(callback) {
    283    this._callbacks.add(callback);
    284  }
    285 
    286  removeListener(callback) {
    287    this._callbacks.delete(callback);
    288  }
    289 
    290  init() {
    291    if (this._initialized) {
    292      return;
    293    }
    294    this._migrateProviderPrefs();
    295    Services.prefs.addObserver(this._providerPrefBranch, this);
    296    Services.prefs.addObserver(this._devtoolsPref, this);
    297    Services.obs.addObserver(
    298      this._maybeSetMessagingProfileID,
    299      SELECTABLE_PROFILES_UPDATED
    300    );
    301    for (const id of Object.keys(USER_PREFERENCES)) {
    302      Services.prefs.addObserver(USER_PREFERENCES[id], this);
    303    }
    304    for (const targetingPref of TARGETING_PREFERENCES) {
    305      Services.prefs.addObserver(targetingPref, this);
    306    }
    307    this._maybeSetMessagingProfileID();
    308    this._initialized = true;
    309  }
    310 
    311  uninit() {
    312    if (this._initialized) {
    313      Services.prefs.removeObserver(this._providerPrefBranch, this);
    314      Services.prefs.removeObserver(this._devtoolsPref, this);
    315      Services.obs.removeObserver(
    316        this._maybeSetMessagingProfileID,
    317        SELECTABLE_PROFILES_UPDATED
    318      );
    319      for (const id of Object.keys(USER_PREFERENCES)) {
    320        Services.prefs.removeObserver(USER_PREFERENCES[id], this);
    321      }
    322      for (const targetingPref of TARGETING_PREFERENCES) {
    323        Services.prefs.removeObserver(targetingPref, this);
    324      }
    325    }
    326    Object.assign(this, DEFAULT_STATE);
    327    this._callbacks.clear();
    328  }
    329 }
    330 
    331 export const ASRouterPreferences = new _ASRouterPreferences();