tor-browser

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

PrefsFeed.sys.mjs (12969B)


      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 import {
      6  actionCreators as ac,
      7  actionTypes as at,
      8 } from "resource://newtab/common/Actions.mjs";
      9 import { Prefs } from "resource://newtab/lib/ActivityStreamPrefs.sys.mjs";
     10 
     11 // We use importESModule here instead of static import so that
     12 // the Karma test environment won't choke on this module. This
     13 // is because the Karma test environment already stubs out
     14 // AppConstants, and overrides importESModule to be a no-op (which
     15 // can't be done for a static import statement).
     16 
     17 // eslint-disable-next-line mozilla/use-static-import
     18 const { AppConstants } = ChromeUtils.importESModule(
     19  "resource://gre/modules/AppConstants.sys.mjs"
     20 );
     21 
     22 const lazy = {};
     23 
     24 ChromeUtils.defineESModuleGetters(lazy, {
     25  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     26  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
     27  Region: "resource://gre/modules/Region.sys.mjs",
     28 });
     29 
     30 export class PrefsFeed {
     31  constructor(prefMap) {
     32    this._prefMap = prefMap;
     33    this._prefs = new Prefs();
     34    this.onExperimentUpdated = this.onExperimentUpdated.bind(this);
     35    this.onTrainhopExperimentUpdated =
     36      this.onTrainhopExperimentUpdated.bind(this);
     37    this.onPocketExperimentUpdated = this.onPocketExperimentUpdated.bind(this);
     38    this.onSmartShortcutsExperimentUpdated =
     39      this.onSmartShortcutsExperimentUpdated.bind(this);
     40    this.onWidgetsUpdated = this.onWidgetsUpdated.bind(this);
     41    this.onOhttpImagesUpdated = this.onOhttpImagesUpdated.bind(this);
     42    this.onInferredPersonalizationExperimentUpdated =
     43      this.onInferredPersonalizationExperimentUpdated.bind(this);
     44  }
     45 
     46  onPrefChanged(name, value) {
     47    const prefItem = this._prefMap.get(name);
     48    if (prefItem) {
     49      let action = "BroadcastToContent";
     50      if (prefItem.skipBroadcast) {
     51        action = "OnlyToMain";
     52        if (prefItem.alsoToPreloaded) {
     53          action = "AlsoToPreloaded";
     54        }
     55      }
     56 
     57      this.store.dispatch(
     58        ac[action]({
     59          type: at.PREF_CHANGED,
     60          data: { name, value },
     61        })
     62      );
     63    }
     64  }
     65 
     66  _setStringPref(values, key, defaultValue) {
     67    this._setPref(values, key, defaultValue, Services.prefs.getStringPref);
     68  }
     69 
     70  _setBoolPref(values, key, defaultValue) {
     71    this._setPref(values, key, defaultValue, Services.prefs.getBoolPref);
     72  }
     73 
     74  _setIntPref(values, key, defaultValue) {
     75    this._setPref(values, key, defaultValue, Services.prefs.getIntPref);
     76  }
     77 
     78  _setPref(values, key, defaultValue, getPrefFunction) {
     79    let value = getPrefFunction(
     80      `browser.newtabpage.activity-stream.${key}`,
     81      defaultValue
     82    );
     83    values[key] = value;
     84    this._prefMap.set(key, { value });
     85  }
     86 
     87  /**
     88   * Handler for when experiment data updates.
     89   */
     90  onExperimentUpdated() {
     91    const value = lazy.NimbusFeatures.newtab.getAllVariables() || {};
     92    this.store.dispatch(
     93      ac.BroadcastToContent({
     94        type: at.PREF_CHANGED,
     95        data: {
     96          name: "featureConfig",
     97          value,
     98        },
     99      })
    100    );
    101  }
    102 
    103  /**
    104   * Computes the trainhop config by processing all enrollments.
    105   * Supports two formats:
    106   * - Single payload: { type: "feature", payload: { "enabled": true, ... }}
    107   * - Multi-payload: { type: "multi-payload", payload: [{ type: "feature", payload: { "enabled": true, ... }}] }
    108   * Both formats output the same structure: { "feature": { "enabled": true, ... }}
    109   */
    110  _getTrainhopConfig() {
    111    const allEnrollments =
    112      lazy.NimbusFeatures.newtabTrainhop.getAllEnrollments() || [];
    113 
    114    let enrollmentsToProcess = [];
    115 
    116    allEnrollments.forEach(enrollment => {
    117      if (
    118        enrollment?.value?.type === "multi-payload" &&
    119        Array.isArray(enrollment?.value?.payload)
    120      ) {
    121        enrollment.value.payload.forEach(item => {
    122          if (item?.type && item?.payload) {
    123            enrollmentsToProcess.push({
    124              value: {
    125                type: item.type,
    126                payload: item.payload,
    127              },
    128              meta: enrollment.meta,
    129            });
    130          }
    131        });
    132      } else if (enrollment?.value?.type) {
    133        enrollmentsToProcess.push(enrollment);
    134      }
    135    });
    136 
    137    const valueObj = {};
    138    enrollmentsToProcess.reduce((accumulator, currentValue) => {
    139      if (currentValue?.value?.type) {
    140        if (
    141          !accumulator[currentValue.value.type] ||
    142          (accumulator[currentValue.value.type].meta.isRollout &&
    143            !currentValue.meta.isRollout)
    144        ) {
    145          accumulator[currentValue.value.type] = currentValue;
    146          valueObj[currentValue.value.type] = currentValue.value.payload;
    147        }
    148      }
    149      return accumulator;
    150    }, {});
    151 
    152    return valueObj;
    153  }
    154 
    155  /**
    156   * Handler for when experiment data updates.
    157   */
    158  onTrainhopExperimentUpdated() {
    159    const valueObj = this._getTrainhopConfig();
    160 
    161    this.store.dispatch(
    162      ac.BroadcastToContent({
    163        type: at.PREF_CHANGED,
    164        data: {
    165          name: "trainhopConfig",
    166          value: valueObj,
    167        },
    168      })
    169    );
    170  }
    171 
    172  /**
    173   * Handler for Pocket specific experiment data updates.
    174   */
    175  onPocketExperimentUpdated(event, reason) {
    176    const value = lazy.NimbusFeatures.pocketNewtab.getAllVariables() || {};
    177    // Loaded experiments are set up inside init()
    178    if (
    179      reason !== "feature-experiment-loaded" &&
    180      reason !== "feature-rollout-loaded"
    181    ) {
    182      this.store.dispatch(
    183        ac.BroadcastToContent({
    184          type: at.PREF_CHANGED,
    185          data: {
    186            name: "pocketConfig",
    187            value,
    188          },
    189        })
    190      );
    191    }
    192  }
    193 
    194  /**
    195   * Handler for when smart shortcuts experiment data updates.
    196   */
    197  onSmartShortcutsExperimentUpdated() {
    198    const value =
    199      lazy.NimbusFeatures.newtabSmartShortcuts.getAllVariables() || {};
    200    this.store.dispatch(
    201      ac.BroadcastToContent({
    202        type: at.PREF_CHANGED,
    203        data: {
    204          name: "smartShortcutsConfig",
    205          value,
    206        },
    207      })
    208    );
    209  }
    210 
    211  /**
    212   * Handler for when inferred personalization experiment config values update.
    213   */
    214  onInferredPersonalizationExperimentUpdated() {
    215    const value =
    216      lazy.NimbusFeatures.newtabInferredPersonalization.getAllVariables() || {};
    217    this.store.dispatch(
    218      ac.BroadcastToContent({
    219        type: at.PREF_CHANGED,
    220        data: {
    221          name: "inferredPersonalizationConfig",
    222          value,
    223        },
    224      })
    225    );
    226  }
    227 
    228  /**
    229   * Handler for when widget experiment data updates.
    230   */
    231  onWidgetsUpdated() {
    232    const value = lazy.NimbusFeatures.newtabWidgets.getAllVariables() || {};
    233    this.store.dispatch(
    234      ac.BroadcastToContent({
    235        type: at.PREF_CHANGED,
    236        data: {
    237          name: "widgetsConfig",
    238          value,
    239        },
    240      })
    241    );
    242  }
    243 
    244  /**
    245   * Handler for when OHTTP images experiment data updates.
    246   */
    247  onOhttpImagesUpdated() {
    248    const value = lazy.NimbusFeatures.newtabOhttpImages.getAllVariables() || {};
    249    this.store.dispatch(
    250      ac.BroadcastToContent({
    251        type: at.PREF_CHANGED,
    252        data: {
    253          name: "ohttpImagesConfig",
    254          value,
    255        },
    256      })
    257    );
    258  }
    259 
    260  init() {
    261    this._prefs.observeBranch(this);
    262    lazy.NimbusFeatures.newtab.onUpdate(this.onExperimentUpdated);
    263    lazy.NimbusFeatures.newtabTrainhop.onUpdate(
    264      this.onTrainhopExperimentUpdated
    265    );
    266    lazy.NimbusFeatures.pocketNewtab.onUpdate(this.onPocketExperimentUpdated);
    267    lazy.NimbusFeatures.newtabSmartShortcuts.onUpdate(
    268      this.onSmartShortcutsExperimentUpdated
    269    );
    270    lazy.NimbusFeatures.newtabInferredPersonalization.onUpdate(
    271      this.onInferredPersonalizationExperimentUpdated
    272    );
    273    lazy.NimbusFeatures.newtabWidgets.onUpdate(this.onWidgetsUpdated);
    274    lazy.NimbusFeatures.newtabOhttpImages.onUpdate(this.onOhttpImagesUpdated);
    275 
    276    // Get the initial value of each activity stream pref
    277    const values = {};
    278    for (const name of this._prefMap.keys()) {
    279      values[name] = this._prefs.get(name);
    280    }
    281 
    282    // These are not prefs, but are needed to determine stuff in content that can only be
    283    // computed in main process
    284    values.isPrivateBrowsingEnabled = lazy.PrivateBrowsingUtils.enabled;
    285    values.platform = AppConstants.platform;
    286 
    287    // Save the geo pref if we have it
    288    if (lazy.Region.home) {
    289      values.region = lazy.Region.home;
    290      this.geo = values.region;
    291    } else if (this.geo !== "") {
    292      // Watch for geo changes and use a dummy value for now
    293      Services.obs.addObserver(this, lazy.Region.REGION_TOPIC);
    294      this.geo = "";
    295    }
    296 
    297    // Get the firefox accounts url for links and to send firstrun metrics to.
    298    values.fxa_endpoint = Services.prefs.getStringPref(
    299      "browser.newtabpage.activity-stream.fxaccounts.endpoint",
    300      "https://accounts.firefox.com"
    301    );
    302 
    303    // Get the firefox update channel with values as default, nightly, beta or release
    304    values.appUpdateChannel = Services.prefs.getStringPref(
    305      "app.update.channel",
    306      ""
    307    );
    308 
    309    // Read the pref for search shortcuts top sites experiment from firefox.js and store it
    310    // in our internal list of prefs to watch
    311    let searchTopSiteExperimentPrefValue = Services.prefs.getBoolPref(
    312      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts"
    313    );
    314    values["improvesearch.topSiteSearchShortcuts"] =
    315      searchTopSiteExperimentPrefValue;
    316    this._prefMap.set("improvesearch.topSiteSearchShortcuts", {
    317      value: searchTopSiteExperimentPrefValue,
    318    });
    319 
    320    values.mayHaveSponsoredTopSites = Services.prefs.getBoolPref(
    321      "browser.topsites.useRemoteSetting"
    322    );
    323 
    324    // Add experiment values and default values
    325    values.featureConfig = lazy.NimbusFeatures.newtab.getAllVariables() || {};
    326    values.pocketConfig =
    327      lazy.NimbusFeatures.pocketNewtab.getAllVariables() || {};
    328    values.smartShortcutsConfig =
    329      lazy.NimbusFeatures.newtabSmartShortcuts.getAllVariables() || {};
    330    values.widgetsConfig =
    331      lazy.NimbusFeatures.newtabWidgets.getAllVariables() || {};
    332    values.trainhopConfig = this._getTrainhopConfig();
    333    this._setBoolPref(values, "logowordmark.alwaysVisible", false);
    334    this._setBoolPref(values, "feeds.section.topstories", false);
    335    this._setBoolPref(values, "discoverystream.enabled", false);
    336    this._setBoolPref(values, "discoverystream.hardcoded-basic-layout", false);
    337    this._setBoolPref(values, "discoverystream.personalization.enabled", false);
    338    this._setBoolPref(
    339      values,
    340      "discoverystream.personalization.override",
    341      false
    342    );
    343    this._setStringPref(
    344      values,
    345      "discoverystream.personalization.modelKeys",
    346      ""
    347    );
    348    this._setStringPref(values, "discoverystream.spocs-endpoint", "");
    349    this._setStringPref(values, "discoverystream.spocs-endpoint-query", "");
    350    this._setStringPref(values, "newNewtabExperience.colors", "");
    351    this._setBoolPref(values, "search.useHandoffComponent", false);
    352    this._setBoolPref(values, "externalComponents.enabled", false);
    353 
    354    // Set the initial state of all prefs in redux
    355    this.store.dispatch(
    356      ac.BroadcastToContent({
    357        type: at.PREFS_INITIAL_VALUES,
    358        data: values,
    359        meta: {
    360          isStartup: true,
    361        },
    362      })
    363    );
    364  }
    365 
    366  uninit() {
    367    this.removeListeners();
    368  }
    369 
    370  removeListeners() {
    371    this._prefs.ignoreBranch(this);
    372    lazy.NimbusFeatures.newtab.offUpdate(this.onExperimentUpdated);
    373    lazy.NimbusFeatures.newtabTrainhop.offUpdate(
    374      this.onTrainhopExperimentUpdated
    375    );
    376    lazy.NimbusFeatures.pocketNewtab.offUpdate(this.onPocketExperimentUpdated);
    377    lazy.NimbusFeatures.newtabSmartShortcuts.offUpdate(
    378      this.onSmartShortcutsExperimentUpdated
    379    );
    380    lazy.NimbusFeatures.newtabInferredPersonalization.offUpdate(
    381      this.onInferredPersonalizationExperimentUpdated
    382    );
    383    lazy.NimbusFeatures.newtabWidgets.offUpdate(this.onWidgetsUpdated);
    384    lazy.NimbusFeatures.newtabOhttpImages.offUpdate(this.onOhttpImagesUpdated);
    385 
    386    if (this.geo === "") {
    387      Services.obs.removeObserver(this, lazy.Region.REGION_TOPIC);
    388    }
    389  }
    390 
    391  observe(subject, topic) {
    392    switch (topic) {
    393      case lazy.Region.REGION_TOPIC:
    394        this.store.dispatch(
    395          ac.BroadcastToContent({
    396            type: at.PREF_CHANGED,
    397            data: { name: "region", value: lazy.Region.home },
    398          })
    399        );
    400        break;
    401    }
    402  }
    403 
    404  onAction(action) {
    405    switch (action.type) {
    406      case at.INIT:
    407        this.init();
    408        break;
    409      case at.UNINIT:
    410        this.uninit();
    411        break;
    412      case at.CLEAR_PREF:
    413        Services.prefs.clearUserPref(this._prefs._branchStr + action.data.name);
    414        break;
    415      case at.SET_PREF:
    416        this._prefs.set(action.data.name, action.data.value);
    417        break;
    418    }
    419  }
    420 }