tor-browser

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

extension-storage.sys.mjs (8290B)


      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 const STORAGE_VERSION = 1; // This needs to be kept in-sync with the rust storage version
      6 
      7 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      8 import { BridgedEngine } from "resource://services-sync/bridged_engine.sys.mjs";
      9 import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
     10 
     11 const lazy = {};
     12 
     13 ChromeUtils.defineESModuleGetters(lazy, {
     14  MULTI_DEVICE_THRESHOLD: "resource://services-sync/constants.sys.mjs",
     15  SCORE_INCREMENT_MEDIUM: "resource://services-sync/constants.sys.mjs",
     16  Svc: "resource://services-sync/util.sys.mjs",
     17  extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs",
     18  setupLoggerForTarget: "resource://gre/modules/AppServicesTracing.sys.mjs",
     19  storageSyncService:
     20    "resource://gre/modules/ExtensionStorageComponents.sys.mjs",
     21 
     22  extensionStorageSyncKinto:
     23    "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs",
     24 });
     25 
     26 const PREF_FORCE_ENABLE = "engine.extension-storage.force";
     27 
     28 // A helper to indicate whether extension-storage is enabled - it's based on
     29 // the "addons" pref. The same logic is shared between both engine impls.
     30 function getEngineEnabled() {
     31  // By default, we sync extension storage if we sync addons. This
     32  // lets us simplify the UX since users probably don't consider
     33  // "extension preferences" a separate category of syncing.
     34  // However, we also respect engine.extension-storage.force, which
     35  // can be set to true or false, if a power user wants to customize
     36  // the behavior despite the lack of UI.
     37  if (
     38    lazy.Svc.PrefBranch.getPrefType(PREF_FORCE_ENABLE) !=
     39    Ci.nsIPrefBranch.PREF_INVALID
     40  ) {
     41    return lazy.Svc.PrefBranch.getBoolPref(PREF_FORCE_ENABLE);
     42  }
     43  return lazy.Svc.PrefBranch.getBoolPref("engine.addons", false);
     44 }
     45 
     46 function setEngineEnabled(enabled) {
     47  // This will be called by the engine manager when declined on another device.
     48  // Things will go a bit pear-shaped if the engine manager tries to end up
     49  // with 'addons' and 'extension-storage' in different states - however, this
     50  // *can* happen given we support the `engine.extension-storage.force`
     51  // preference. So if that pref exists, we set it to this value. If that pref
     52  // doesn't exist, we just ignore it and hope that the 'addons' engine is also
     53  // going to be set to the same state.
     54  if (
     55    lazy.Svc.PrefBranch.getPrefType(PREF_FORCE_ENABLE) !=
     56    Ci.nsIPrefBranch.PREF_INVALID
     57  ) {
     58    lazy.Svc.PrefBranch.setBoolPref(PREF_FORCE_ENABLE, enabled);
     59  }
     60 }
     61 
     62 // A "bridged engine" to our webext-storage component.
     63 export function ExtensionStorageEngineBridge(service) {
     64  lazy.setupLoggerForTarget("webext_storage", "Sync.Engine.Extension-Storage");
     65  BridgedEngine.call(this, "Extension-Storage", service);
     66 }
     67 
     68 ExtensionStorageEngineBridge.prototype = {
     69  syncPriority: 10,
     70 
     71  // Used to override the engine name in telemetry, so that we can distinguish .
     72  overrideTelemetryName: "rust-webext-storage",
     73 
     74  async initialize() {
     75    await SyncEngine.prototype.initialize.call(this);
     76    this._rustStore = await lazy.storageSyncService.getStorageAreaInstance();
     77    this._bridge = await this._rustStore.bridgedEngine();
     78 
     79    // Uniffi currently only supports async methods, so we'll need to hardcode
     80    // these values for now (which is fine for now as these hardly ever change)
     81    this._bridge.storageVersion = STORAGE_VERSION;
     82    this._bridge.allowSkippedRecord = true;
     83    this._bridge.getSyncId = async () => {
     84      let syncID = await this._bridge.syncId();
     85      return syncID;
     86    };
     87 
     88    this._log.info("Got a bridged engine!");
     89    this._tracker.modified = true;
     90  },
     91 
     92  async _notifyPendingChanges() {
     93    try {
     94      let changeSets = await this._rustStore.getSyncedChanges();
     95 
     96      changeSets.forEach(changeSet => {
     97        try {
     98          lazy.extensionStorageSync.notifyListeners(
     99            changeSet.extId,
    100            JSON.parse(changeSet.changes)
    101          );
    102        } catch (ex) {
    103          this._log.warn(
    104            `Error notifying change listeners for ${changeSet.extId}`,
    105            ex
    106          );
    107        }
    108      });
    109    } catch (ex) {
    110      this._log.warn("Error fetching pending synced changes", ex);
    111    }
    112  },
    113 
    114  async _processIncoming() {
    115    await super._processIncoming();
    116    try {
    117      await this._notifyPendingChanges();
    118    } catch (ex) {
    119      // Failing to notify `storage.onChanged` observers is bad, but shouldn't
    120      // interrupt syncing.
    121      this._log.warn("Error notifying about synced changes", ex);
    122    }
    123  },
    124 
    125  get enabled() {
    126    return getEngineEnabled();
    127  },
    128  set enabled(enabled) {
    129    setEngineEnabled(enabled);
    130  },
    131 };
    132 Object.setPrototypeOf(
    133  ExtensionStorageEngineBridge.prototype,
    134  BridgedEngine.prototype
    135 );
    136 
    137 /*******************************************************************************
    138 *
    139 * Deprecated support for Kinto
    140 *
    141 ******************************************************************************/
    142 
    143 /**
    144 * The Engine that manages syncing for the web extension "storage"
    145 * API, and in particular ext.storage.sync.
    146 *
    147 * ext.storage.sync is implemented using Kinto, so it has mechanisms
    148 * for syncing that we do not need to integrate in the Firefox Sync
    149 * framework, so this is something of a stub.
    150 */
    151 export function ExtensionStorageEngineKinto(service) {
    152  SyncEngine.call(this, "Extension-Storage", service);
    153  XPCOMUtils.defineLazyPreferenceGetter(
    154    this,
    155    "_skipPercentageChance",
    156    "services.sync.extension-storage.skipPercentageChance",
    157    0
    158  );
    159 }
    160 
    161 ExtensionStorageEngineKinto.prototype = {
    162  _trackerObj: ExtensionStorageTracker,
    163  // we don't need these since we implement our own sync logic
    164  _storeObj: undefined,
    165  _recordObj: undefined,
    166 
    167  syncPriority: 10,
    168  allowSkippedRecord: false,
    169 
    170  async _sync() {
    171    return lazy.extensionStorageSyncKinto.syncAll();
    172  },
    173 
    174  get enabled() {
    175    return getEngineEnabled();
    176  },
    177  // We only need the enabled setter for the edge-case where info/collections
    178  // has `extension-storage` - which could happen if the pref to flip the new
    179  // engine on was once set but no longer is.
    180  set enabled(enabled) {
    181    setEngineEnabled(enabled);
    182  },
    183 
    184  _wipeClient() {
    185    return lazy.extensionStorageSyncKinto.clearAll();
    186  },
    187 
    188  shouldSkipSync(syncReason) {
    189    if (syncReason == "user" || syncReason == "startup") {
    190      this._log.info(
    191        `Not skipping extension storage sync: reason == ${syncReason}`
    192      );
    193      // Always sync if a user clicks the button, or if we're starting up.
    194      return false;
    195    }
    196    // Ensure this wouldn't cause a resync...
    197    if (this._tracker.score >= lazy.MULTI_DEVICE_THRESHOLD) {
    198      this._log.info(
    199        "Not skipping extension storage sync: Would trigger resync anyway"
    200      );
    201      return false;
    202    }
    203 
    204    let probability = this._skipPercentageChance / 100.0;
    205    // Math.random() returns a value in the interval [0, 1), so `>` is correct:
    206    // if `probability` is 1 skip every time, and if it's 0, never skip.
    207    let shouldSkip = probability > Math.random();
    208 
    209    this._log.info(
    210      `Skipping extension-storage sync with a chance of ${probability}: ${shouldSkip}`
    211    );
    212    return shouldSkip;
    213  },
    214 };
    215 Object.setPrototypeOf(
    216  ExtensionStorageEngineKinto.prototype,
    217  SyncEngine.prototype
    218 );
    219 
    220 function ExtensionStorageTracker(name, engine) {
    221  Tracker.call(this, name, engine);
    222  this._ignoreAll = false;
    223 }
    224 ExtensionStorageTracker.prototype = {
    225  get ignoreAll() {
    226    return this._ignoreAll;
    227  },
    228 
    229  set ignoreAll(value) {
    230    this._ignoreAll = value;
    231  },
    232 
    233  onStart() {
    234    lazy.Svc.Obs.add("ext.storage.sync-changed", this.asyncObserver);
    235  },
    236 
    237  onStop() {
    238    lazy.Svc.Obs.remove("ext.storage.sync-changed", this.asyncObserver);
    239  },
    240 
    241  async observe(subject, topic) {
    242    if (this.ignoreAll) {
    243      return;
    244    }
    245 
    246    if (topic !== "ext.storage.sync-changed") {
    247      return;
    248    }
    249 
    250    // Single adds, removes and changes are not so important on their
    251    // own, so let's just increment score a bit.
    252    this.score += lazy.SCORE_INCREMENT_MEDIUM;
    253  },
    254 };
    255 Object.setPrototypeOf(ExtensionStorageTracker.prototype, Tracker.prototype);