Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  Blocklist.sys.mjs   Sprache: unbekannt

 
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */

/* 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/. */

/* eslint "valid-jsdoc": [2, {requireReturn: false}] */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  jexlFilterFunc: "resource://services-settings/remote-settings.sys.mjs",
});

const CascadeFilter = Components.Constructor(
  "@mozilla.org/cascade-filter;1",
  "nsICascadeFilter",
  "setFilterData"
);

// The whole ID should be surrounded by literal ().
// IDs may contain alphanumerics, _, -, {}, @ and a literal '.'
// They may also contain backslashes (needed to escape the {} and dot)
// We filter out backslash escape sequences (like `\w`) separately
// (see kEscapeSequences).
const kIdSubRegex =
  "\\([" +
  "\\\\" + // note: just a backslash, but between regex and string it needs escaping.
  "\\w .{}@-]+\\)";

// prettier-ignore
// Find regular expressions of the form:
// /^((id1)|(id2)|(id3)|...|(idN))$/
// The outer set of parens enclosing the entire list of IDs is optional.
const kIsMultipleIds = new RegExp(
  // Start with literal sequence /^(
  //  (the `(` is optional)
  "^/\\^\\(?" +
    // Then at least one ID in parens ().
    kIdSubRegex +
    // Followed by any number of IDs in () separated by pipes.
    // Note: using a non-capturing group because we don't care about the value.
    "(?:\\|" + kIdSubRegex + ")*" +
  // Finally, we need to end with literal sequence )$/
  //  (the leading `)` is optional like at the start)
  "\\)?\\$/$"
);

// Check for a backslash followed by anything other than a literal . or curlies
const kEscapeSequences = /\\[^.{}]/;

// Used to remove the following 3 things:
// leading literal /^(
//    plus an optional (
// any backslash
// trailing literal )$/
//    plus an optional ) before the )$/
const kRegExpRemovalRegExp = /^\/\^\(\(?|\\|\)\)?\$\/$/g;

// In order to block add-ons, their type should be part of this list. There
// may be additional requirements such as requiring the add-on to be signed.
// See the uses of kXPIAddonTypes before introducing new addon types or
// providers that differ from the existing types.
ChromeUtils.defineLazyGetter(lazy, "kXPIAddonTypes", () => {
  // In practice, this result is equivalent to ALL_XPI_TYPES in
  // XPIProvider.sys.mjs.
  // "plugin" (from GMPProvider.sys.mjs) is intentionally omitted, as we decided to
  // not support blocklisting of GMP plugins in bug 1086668.
  return lazy.AddonManagerPrivate.getAddonTypesByProvider("XPIProvider");
});

// For a given input string matcher, produce either a string to compare with,
// a regular expression, or a set of strings to compare with.
function processMatcher(str) {
  if (!str.startsWith("/")) {
    return str;
  }
  // Process regexes matching multiple IDs into a set.
  if (kIsMultipleIds.test(str) && !kEscapeSequences.test(str)) {
    // Remove the regexp gunk at the start and end of the string, as well
    // as all backslashes, and split by )|( to leave the list of IDs.
    return new Set(str.replace(kRegExpRemovalRegExp, "").split(")|("));
  }
  let lastSlash = str.lastIndexOf("/");
  let pattern = str.slice(1, lastSlash);
  let flags = str.slice(lastSlash + 1);
  return new RegExp(pattern, flags);
}

// Returns true if the addonProps object passes the constraints set by matches.
// (For every non-null property in matches, the same key must exist in
// addonProps and its value must match)
function doesAddonEntryMatch(matches, addonProps) {
  for (let [key, value] of Object.entries(matches)) {
    if (value === null || value === undefined) {
      continue;
    }
    if (addonProps[key]) {
      // If this property matches (member of the set, matches regex, or
      // an exact string match), continue to look at the other properties of
      // the `matches` object.
      // If not, return false immediately.
      if (value.has && value.has(addonProps[key])) {
        continue;
      }
      if (value.test && value.test(addonProps[key])) {
        continue;
      }
      if (typeof value == "string" && value === addonProps[key]) {
        continue;
      }
    }
    // If we get here, the property doesn't match, so this entry doesn't match.
    return false;
  }
  // If we get here, all the properties must have matched.
  return true;
}

const TOOLKIT_ID = "toolkit@mozilla.org";
const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
const PREF_BLOCKLIST_ADDONITEM_URL = "extensions.blocklist.addonItemURL";
const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
const PREF_BLOCKLIST_SOFTBLOCK_ENABLED =
  "extensions.blocklist.softblock.enabled";
const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level";
const PREF_BLOCKLIST_USE_MLBF = "extensions.blocklist.useMLBF";
const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled";
const DEFAULT_SEVERITY = 3;
const DEFAULT_LEVEL = 2;
const MAX_BLOCK_LEVEL = 3;

const BLOCKLIST_BUCKET = "blocklists";

const BlocklistTelemetry = {
  /**
   * Record the RemoteSettings Blocklist lastModified server time into the
   * "blocklist.lastModified_rs keyed scalar (or "Missing Date" when unable
   * to retrieve a valid timestamp).
   *
   * @param {string} blocklistType
   *        The blocklist type that has been updated ("addons" or "addons_mlbf");
   *        the "gfx" blocklist is not covered by this telemetry).
   * @param {RemoteSettingsClient} remoteSettingsClient
   *        The RemoteSettings client to retrieve the lastModified timestamp from.
   */
  async recordRSBlocklistLastModified(blocklistType, remoteSettingsClient) {
    // In some tests overrides ensureInitialized and remoteSettingsClient
    // can be undefined, and in that case we don't want to record any
    // telemetry scalar.
    if (!remoteSettingsClient) {
      return;
    }

    let lastModified = await remoteSettingsClient.getLastModified();
    if (blocklistType === "addons_mlbf") {
      BlocklistTelemetry.recordGleanDateTime(
        Glean.blocklist.lastModifiedRsAddonsMblf,
        lastModified
      );
    }
  },

  /**
   * Records a glean datetime if time is > than 0, otherwise 0 is submitted.
   *
   * @param {nsIGleanDatetime} gleanTelemetry
   *        A glean telemetry datetime object.
   * @param {number} time
   *        A timestamp to record.
   */
  recordGleanDateTime(gleanTelemetry, time) {
    if (time > 0) {
      // Glean date times are provided in nanoseconds, `getTime()` yields
      // milliseconds (after the Unix epoch).
      gleanTelemetry.set(time * 1000);
    } else {
      gleanTelemetry.set(0);
    }
  },

  /**
   * Record whether an add-on is blocked and the parameters that guided the
   * decision to block or unblock the add-on.
   *
   * @param {AddonWrapper|object} addon
   *        The blocked or unblocked add-on. Not necessarily installed.
   *        Could be an object with the id, version and blocklistState
   *        properties when the AddonWrapper is not available (e.g. during
   *        update checks).
   * @param {string} reason
   *        The reason for recording the event,
   *        "addon_install", "addon_update", "addon_update_check",
   *        "addon_db_modified", "blocklist_update".
   */
  recordAddonBlockChangeTelemetry(addon, reason) {
    // Reduce the timer resolution for anonymity.
    let hoursSinceInstall = -1;
    if (reason === "blocklist_update" || reason === "addon_db_modified") {
      hoursSinceInstall = Math.round(
        (Date.now() - addon.installDate.getTime()) / 3600000
      );
    }

    Glean.blocklist.addonBlockChange.record({
      value: addon.id,
      object: reason,
      blocklist_state: addon.blocklistState,
      addon_version: addon.version,
      signed_date: addon.signedDate?.getTime() || 0,
      hours_since: hoursSinceInstall,

      ...ExtensionBlocklistMLBF.getBlocklistMetadataForTelemetry(),
    });
  },
};

const Utils = {
  /**
   * Checks whether this entry is valid for the current OS and ABI.
   * If the entry has an "os" property then the current OS must appear in
   * its comma separated list for it to be valid. Similarly for the
   * xpcomabi property.
   *
   * @param {Object} item
   *        The blocklist item.
   * @returns {bool}
   *        Whether the entry matches the current OS.
   */
  matchesOSABI(item) {
    if (item.os) {
      let os = item.os.split(",");
      if (!os.includes(lazy.gAppOS)) {
        return false;
      }
    }

    if (item.xpcomabi) {
      let xpcomabi = item.xpcomabi.split(",");
      if (!xpcomabi.includes(lazy.gApp.XPCOMABI)) {
        return false;
      }
    }
    return true;
  },

  /**
   * Checks if a version is higher than or equal to the minVersion (if provided)
   * and lower than or equal to the maxVersion (if provided).
   * @param {string} version
   *        The version to test.
   * @param {string?} minVersion
   *        The minimum version. If null it is assumed that version is always
   *        larger.
   * @param {string?} maxVersion
   *        The maximum version. If null it is assumed that version is always
   *        smaller.
   * @returns {boolean}
   *        Whether the item matches the range.
   */
  versionInRange(version, minVersion, maxVersion) {
    if (minVersion && Services.vc.compare(version, minVersion) < 0) {
      return false;
    }
    if (maxVersion && Services.vc.compare(version, maxVersion) > 0) {
      return false;
    }
    return true;
  },

  /**
   * Tests if this versionRange matches the item specified, and has a matching
   * targetApplication id and version.
   * @param {Object} versionRange
   *        The versionRange to check against
   * @param {string} itemVersion
   *        The version of the actual addon/plugin to test for.
   * @param {string} appVersion
   *        The version of the application to test for.
   * @param {string} toolkitVersion
   *        The version of toolkit to check for.
   * @returns {boolean}
   *        True if this version range covers the item and app/toolkit version given.
   */
  versionsMatch(versionRange, itemVersion, appVersion, toolkitVersion) {
    // Some platforms have no version for plugins, these don't match if there
    // was a min/maxVersion provided
    if (!itemVersion && (versionRange.minVersion || versionRange.maxVersion)) {
      return false;
    }

    // Check if the item version matches
    if (
      !this.versionInRange(
        itemVersion,
        versionRange.minVersion,
        versionRange.maxVersion
      )
    ) {
      return false;
    }

    // Check if the application or toolkit version matches
    for (let tA of versionRange.targetApplication) {
      if (
        tA.guid == lazy.gAppID &&
        this.versionInRange(appVersion, tA.minVersion, tA.maxVersion)
      ) {
        return true;
      }
      if (
        tA.guid == TOOLKIT_ID &&
        this.versionInRange(toolkitVersion, tA.minVersion, tA.maxVersion)
      ) {
        return true;
      }
    }
    return false;
  },

  /**
   * Given a blocklist JS object entry, ensure it has a versionRange property, where
   * each versionRange property has a valid severity property
   * and at least 1 valid targetApplication.
   * If it didn't have a valid targetApplication array before and/or it was empty,
   * fill it with an entry with null min/maxVersion properties, which will match
   * every version.
   *
   * If there *are* targetApplications, if any of them don't have a guid property,
   * assign them the current app's guid.
   *
   * @param {Object} entry
   *                 blocklist entry object.
   */
  ensureVersionRangeIsSane(entry) {
    if (!entry.versionRange.length) {
      entry.versionRange.push({});
    }
    for (let vr of entry.versionRange) {
      if (!vr.hasOwnProperty("severity")) {
        vr.severity = DEFAULT_SEVERITY;
      }
      if (!Array.isArray(vr.targetApplication)) {
        vr.targetApplication = [];
      }
      if (!vr.targetApplication.length) {
        vr.targetApplication.push({ minVersion: null, maxVersion: null });
      }
      vr.targetApplication.forEach(tA => {
        if (!tA.guid) {
          tA.guid = lazy.gAppID;
        }
      });
    }
  },

  /**
   * Create a blocklist URL for the given blockID
   * @param {String} id the blockID to use
   * @returns {String} the blocklist URL.
   */
  _createBlocklistURL(id) {
    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
    return url.replace(/%blockID%/g, id);
  },
};

/**
 * This custom filter function is used to limit the entries returned
 * by `RemoteSettings("...").get()` depending on the target app information
 * defined on entries.
 *
 * Note that this is async because `jexlFilterFunc` is async.
 *
 * @param {Object} entry a Remote Settings record
 * @param {Object} environment the JEXL environment object.
 * @returns {Object} The entry if it matches, `null` otherwise.
 */
async function targetAppFilter(entry, environment) {
  // If the entry has a JEXL filter expression, it should prevail.
  // The legacy target app mechanism will be kept in place for old entries.
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1463377
  const { filter_expression } = entry;
  if (filter_expression) {
    return lazy.jexlFilterFunc(entry, environment);
  }

  // Keep entries without target information.
  if (!("versionRange" in entry)) {
    return entry;
  }

  const { versionRange } = entry;

  // Everywhere in this method, we avoid checking the minVersion, because
  // we want to retain items whose minVersion is higher than the current
  // app version, so that we have the items around for app updates.

  // Gfx blocklist has a specific versionRange object, which is not a list.
  if (!Array.isArray(versionRange)) {
    const { maxVersion = "*" } = versionRange;
    const matchesRange =
      Services.vc.compare(lazy.gApp.version, maxVersion) <= 0;
    return matchesRange ? entry : null;
  }

  // Iterate the targeted applications, at least one of them must match.
  // If no target application, keep the entry.
  if (!versionRange.length) {
    return entry;
  }
  for (const vr of versionRange) {
    const { targetApplication = [] } = vr;
    if (!targetApplication.length) {
      return entry;
    }
    for (const ta of targetApplication) {
      const { guid } = ta;
      if (!guid) {
        return entry;
      }
      const { maxVersion = "*" } = ta;
      if (
        guid == lazy.gAppID &&
        Services.vc.compare(lazy.gApp.version, maxVersion) <= 0
      ) {
        return entry;
      }
      if (
        guid == "toolkit@mozilla.org" &&
        Services.vc.compare(Services.appinfo.platformVersion, maxVersion) <= 0
      ) {
        return entry;
      }
    }
  }
  // Skip this entry.
  return null;
}

/**
 * The Graphics blocklist implementation. The JSON objects for graphics blocks look
 * something like:
 *
 * {
 *  "blockID": "g35",
 *  "os": "WINNT 6.1",
 *  "vendor": "0xabcd",
 *  "devices": [
 *    "0x2783",
 *    "0x1234",
 *  ],
 *  "feature": " DIRECT2D ",
 *  "featureStatus": " BLOCKED_DRIVER_VERSION ",
 *  "driverVersion": " 8.52.322.2202 ",
 *  "driverVersionComparator": " LESS_THAN ",
 *  "versionRange": {"minVersion": "5.0", "maxVersion: "25.0"},
 * }
 *
 * The RemoteSetttings client takes care of filtering out versions that don't apply.
 * The code here stores entries in memory and sends them to the gfx component in
 * serialized text form, using ',', '\t' and '\n' as separators.
 */
const GfxBlocklistRS = {
  _ensureInitialized() {
    if (this._initialized || !gBlocklistEnabled) {
      return;
    }
    this._initialized = true;
    this._client = lazy.RemoteSettings("gfx", {
      bucketName: BLOCKLIST_BUCKET,
      filterFunc: targetAppFilter,
    });
    this.checkForEntries = this.checkForEntries.bind(this);
    this._client.on("sync", this.checkForEntries);
  },

  shutdown() {
    if (this._client) {
      this._client.off("sync", this.checkForEntries);
    }
  },

  sync() {
    this._ensureInitialized();
    return this._client.sync();
  },

  async checkForEntries() {
    this._ensureInitialized();
    if (!gBlocklistEnabled) {
      return []; // return value expected by tests.
    }
    let entries = await this._client.get().catch(ex => Cu.reportError(ex));
    // Handle error silently. This can happen if our request to fetch data is aborted,
    // e.g. by application shutdown.
    if (!entries) {
      return [];
    }
    // Trim helper (spaces, tabs, no-break spaces..)
    const trim = s =>
      (s || "").replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, "");

    entries = entries.map(entry => {
      let props = [
        "blockID",
        "driverVersion",
        "driverVersionMax",
        "driverVersionComparator",
        "feature",
        "featureStatus",
        "os",
        "vendor",
        "devices",
      ];
      let rv = {};
      for (let p of props) {
        let val = entry[p];
        // Ignore falsy values or empty arrays.
        if (!val || (Array.isArray(val) && !val.length)) {
          continue;
        }
        if (typeof val == "string") {
          val = trim(val);
        } else if (p == "devices") {
          let invalidDevices = [];
          let validDevices = [];
          // We serialize the array of devices as a comma-separated string, so
          // we need to ensure that none of the entries contain commas, also in
          // the future.
          val.forEach(v =>
            v.includes(",") ? invalidDevices.push(v) : validDevices.push(v)
          );
          for (let dev of invalidDevices) {
            const e = new Error(
              `Block ${entry.blockID} contains unsupported device: ${dev}`
            );
            Cu.reportError(e);
          }
          if (!validDevices) {
            continue;
          }
          val = validDevices;
        }
        rv[p] = val;
      }
      if (entry.versionRange) {
        rv.versionRange = {
          minVersion: trim(entry.versionRange.minVersion) || "0",
          maxVersion: trim(entry.versionRange.maxVersion) || "*",
        };
      }
      return rv;
    });
    if (entries.length) {
      let sortedProps = [
        "blockID",
        "devices",
        "driverVersion",
        "driverVersionComparator",
        "driverVersionMax",
        "feature",
        "featureStatus",
        "hardware",
        "manufacturer",
        "model",
        "os",
        "osversion",
        "product",
        "vendor",
        "versionRange",
      ];
      // Notify `GfxInfoBase`, by passing a string serialization.
      let payload = [];
      for (let gfxEntry of entries) {
        let entryLines = [];
        for (let key of sortedProps) {
          if (gfxEntry[key]) {
            let value = gfxEntry[key];
            if (Array.isArray(value)) {
              value = value.join(",");
            } else if (value.maxVersion) {
              // Both minVersion and maxVersion are always set on each entry.
              value = value.minVersion + "," + value.maxVersion;
            }
            entryLines.push(key + ":" + value);
          }
        }
        payload.push(entryLines.join("\t"));
      }
      Services.obs.notifyObservers(
        null,
        "blocklist-data-gfxItems",
        payload.join("\n")
      );
    }
    // The return value is only used by tests.
    return entries;
  },
};

/**
 * The extensions blocklist implementation. The JSON objects for extension
 * blocks look something like:
 *
 * {
 *   "guid": "someguid@addons.mozilla.org",
 *   "prefs": ["i.am.a.pref.that.needs.resetting"],
 *   "schema": 1480349193877,
 *   "blockID": "i12345",
 *   "details": {
 *     "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1234567",
 *     "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enable it in the Add-ons Manager.",
 *     "why": "This add-on is in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, using multiple add-on IDs and potentially doing other unwanted activities.",
 *     "name": "Some pretty name",
 *     "created": "2019-05-06T19:52:20Z"
 *   },
 *   "enabled": true,
 *   "versionRange": [
 *     {
 *       "severity": 1,
 *       "maxVersion": "*",
 *       "minVersion": "0",
 *       "targetApplication": []
 *     }
 *   ],
 *   "id": "<unique guid>",
 *   "last_modified": 1480349215672,
 * }
 *
 * This is a legacy format, and implements deprecated operations (bug 1620580).
 * ExtensionBlocklistMLBF supersedes this implementation.
 */
const ExtensionBlocklistRS = {
  async _ensureEntries() {
    this.ensureInitialized();
    if (!this._entries && gBlocklistEnabled) {
      await this._updateEntries();
    }
  },

  async _updateEntries() {
    if (!gBlocklistEnabled) {
      this._entries = [];
      return;
    }
    this._entries = await this._client.get().catch(ex => Cu.reportError(ex));
    // Handle error silently. This can happen if our request to fetch data is aborted,
    // e.g. by application shutdown.
    if (!this._entries) {
      this._entries = [];
      return;
    }
    this._entries.forEach(entry => {
      entry.matches = {};
      if (entry.guid) {
        entry.matches.id = processMatcher(entry.guid);
      }
      for (let key of EXTENSION_BLOCK_FILTERS) {
        if (key == "id" || !entry[key]) {
          continue;
        }
        entry.matches[key] = processMatcher(entry[key]);
      }
      Utils.ensureVersionRangeIsSane(entry);
    });

    BlocklistTelemetry.recordRSBlocklistLastModified("addons", this._client);
  },

  async _filterItem(entry, environment) {
    if (!(await targetAppFilter(entry, environment))) {
      return null;
    }
    if (!Utils.matchesOSABI(entry)) {
      return null;
    }
    // Need something to filter on - at least a guid or name (either could be a regex):
    if (!entry.guid && !entry.name) {
      let blockID = entry.blockID || entry.id;
      Cu.reportError(new Error(`Nothing to filter add-on item ${blockID} on`));
      return null;
    }
    return entry;
  },

  sync() {
    this.ensureInitialized();
    return this._client.sync();
  },

  ensureInitialized() {
    if (!gBlocklistEnabled || this._initialized) {
      return;
    }
    this._initialized = true;
    this._client = lazy.RemoteSettings("addons", {
      bucketName: BLOCKLIST_BUCKET,
      filterFunc: this._filterItem,
    });
    this._onUpdate = this._onUpdate.bind(this);
    this._client.on("sync", this._onUpdate);
  },

  shutdown() {
    if (this._client) {
      this._client.off("sync", this._onUpdate);
      this._didShutdown = true;
    }
  },

  // Called when the blocklist implementation is changed via a pref.
  undoShutdown() {
    if (this._didShutdown) {
      this._client.on("sync", this._onUpdate);
      this._didShutdown = false;
    }
  },

  async _onUpdate() {
    let oldEntries = this._entries || [];
    await this.ensureInitialized();
    await this._updateEntries();

    let addons = await lazy.AddonManager.getAddonsByTypes(lazy.kXPIAddonTypes);
    for (let addon of addons) {
      let oldState = addon.blocklistState;
      if (addon.updateBlocklistState) {
        await addon.updateBlocklistState(false);
      } else if (oldEntries) {
        let oldEntry = this._getEntry(addon, oldEntries);
        oldState = oldEntry
          ? oldEntry.state
          : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
      } else {
        oldState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
      }
      let state = addon.blocklistState;

      LOG(
        "Blocklist state for " +
          addon.id +
          " changed from " +
          oldState +
          " to " +
          state
      );

      // We don't want to re-warn about add-ons
      if (state == oldState) {
        continue;
      }

      // Ensure that softDisabled is false if the add-on is not soft-blocked
      if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
        await addon.setSoftDisabled(false);
      }

      // If an add-on has dropped from hard-blocked to soft-blocked just mark it as
      // softDisabled and don't warn about it.
      if (
        state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
        oldState == Ci.nsIBlocklistService.STATE_BLOCKED
      ) {
        await addon.setSoftDisabled(true);
      }

      if (
        state == Ci.nsIBlocklistService.STATE_BLOCKED ||
        state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED
      ) {
        // Mark it as soft-blocked if necessary. Note that we avoid setting
        // softDisabled at the same time as userDisabled to make it clear
        // which was the original cause of the add-on becoming disabled in a
        // way that the user can change.
        if (
          state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
          !addon.userDisabled
        ) {
          await addon.setSoftDisabled(true);
        }
        // It's a block. We must reset certain preferences.
        let entry = this._getEntry(addon, this._entries);
        if (entry.prefs && entry.prefs.length) {
          for (let pref of entry.prefs) {
            Services.prefs.clearUserPref(pref);
          }
        }
      }
    }

    lazy.AddonManagerPrivate.updateAddonAppDisabledStates();
  },

  async getState(addon, appVersion, toolkitVersion) {
    let entry = await this.getEntry(addon, appVersion, toolkitVersion);
    return entry ? entry.state : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  },

  async getEntry(addon, appVersion, toolkitVersion) {
    await this._ensureEntries();
    return this._getEntry(addon, this._entries, appVersion, toolkitVersion);
  },

  _getEntry(addon, addonEntries, appVersion, toolkitVersion) {
    if (!gBlocklistEnabled || !addon) {
      return null;
    }

    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (!appVersion && !lazy.gApp.version) {
      return null;
    }

    if (!appVersion) {
      appVersion = lazy.gApp.version;
    }
    if (!toolkitVersion) {
      toolkitVersion = lazy.gApp.platformVersion;
    }

    let addonProps = {};
    for (let key of EXTENSION_BLOCK_FILTERS) {
      addonProps[key] = addon[key];
    }
    if (addonProps.creator) {
      addonProps.creator = addonProps.creator.name;
    }

    for (let entry of addonEntries) {
      // First check if it matches our properties. If not, just skip to the next item.
      if (!doesAddonEntryMatch(entry.matches, addonProps)) {
        continue;
      }
      // If those match, check the app or toolkit version works:
      for (let versionRange of entry.versionRange) {
        if (
          Utils.versionsMatch(
            versionRange,
            addon.version,
            appVersion,
            toolkitVersion
          )
        ) {
          let blockID = entry.blockID || entry.id;
          return {
            state:
              versionRange.severity >= gBlocklistLevel
                ? Ci.nsIBlocklistService.STATE_BLOCKED
                : Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
            url: Utils._createBlocklistURL(blockID),
            prefs: entry.prefs || [],
          };
        }
      }
    }
    return null;
  },
};

/**
 * The extensions blocklist implementation, the third version.
 *
 * The current blocklist is represented by a multi-level bloom filter (MLBF)
 * (aka "Cascade Bloom Filter") that works like a set, i.e. supports a has()
 * operation, except it is probabilistic. The MLBF is 100% accurate for known
 * entries and unreliable for unknown entries.  When the backend generates the
 * MLBF, all known add-ons are recorded, including their block state. Unknown
 * add-ons are identified by their signature date being newer than the MLBF's
 * generation time, and they are considered to not be blocked.
 *
 * Legacy blocklists used to distinguish between "soft-block" and "hard-block",
 * but the current blocklist only supports one type of block ("hard-block").
 * After checking the blocklist states, any previous "soft-blocked" addons will
 * either be (hard) blocked or unblocked based on the blocklist.
 *
 * The MLBF is attached to a RemoteSettings record, as follows:
 *
 * {
 *   "generation_time": 1585692000000,
 *   "attachment": { ... RemoteSettings attachment ... }
 *   "attachment_type": "bloomfilter-base",
 * }
 *
 * The collection can also contain stashes:
 *
 * {
 *   "stash_time": 1585692000001,
 *   "stash": {
 *     "blocked": [ "addonid:1.0", ... ],
 *     "unblocked": [ "addonid:1.0", ... ]
 *  }
 *
 * Stashes can be used to update the blocklist without forcing the whole MLBF
 * to be downloaded again. These stashes are applied on top of the base MLBF.
 */
const ExtensionBlocklistMLBF = {
  // ID to identify cached or bundled attachment. Supersedes the id from
  // the record to ensure that there is only one cached entry, and enables
  // lookup of cached attachments even without record.
  RS_ATTACHMENT_ID: "addons-mlbf.bin",
  RS_ATTACHMENT_TYPE: "bloomfilter-base",
  RS_SOFTBLOCKS_ATTACHMENT_ID: "softblocks-addons-mlbf.bin",
  RS_SOFTBLOCKS_ATTACHMENT_TYPE: "softblocks-bloomfilter-base",

  async _getMLBFData(record, attachmentId, mlbfData) {
    // |record| may be unset. In that case, the MLBF dump is used instead
    // (provided that the client has been built with it included).
    let hash = record?.attachment.hash;
    if (mlbfData && hash && mlbfData.cascadeHash === hash) {
      // MLBF not changed, save the efforts of downloading the data again.

      // Although the MLBF has not changed, the time in the record has. This
      // means that the MLBF is known to provide accurate results for add-ons
      // that were signed after the previously known date (but before the newly
      // given date). To ensure that add-ons in this time range are also blocked
      // as expected, update the cached generationTime.
      if (record.generation_time > mlbfData.generationTime) {
        mlbfData.generationTime = record.generation_time;
      }
      return mlbfData;
    }
    const {
      buffer,
      record: actualRecord,
      _source: rsAttachmentSource,
    } = await this._client.attachments.download(record, {
      attachmentId,
      fallbackToCache: true,
      fallbackToDump: true,
    });
    return {
      cascadeHash: actualRecord.attachment.hash,
      cascadeFilter: new CascadeFilter(new Uint8Array(buffer)),
      // Note: generation_time is semantically distinct from last_modified.
      // generation_time is compared with the signing date of the add-on, so it
      // should be in sync with the signing service's clock.
      // In contrast, last_modified does not have such strong requirements.
      generationTime: actualRecord.generation_time,
      // Used for telemetry.
      rsAttachmentSource,
    };
  },

  async _fetchMLBF(recordHardBlocks, recordSoftBlocks) {
    const [mlbfRes, mlbfSoftBlocksRes] = await Promise.allSettled([
      this._getMLBFData(
        recordHardBlocks,
        this.RS_ATTACHMENT_ID,
        this._mlbfData
      ),
      gBlocklistSoftBlockEnabled
        ? this._getMLBFData(
            recordSoftBlocks,
            this.RS_SOFTBLOCKS_ATTACHMENT_ID,
            this._mlbfDataSoftBlocks
          )
        : undefined,
    ]);

    if (mlbfRes.reason) {
      throw mlbfRes.reason;
    }

    if (mlbfSoftBlocksRes.reason) {
      // Allow the soft-blocks mlbf attachment to fail to be retrieved.
      Cu.reportError(mlbfSoftBlocksRes.reason);
    }
    return {
      mlbf: mlbfRes.value,
      mlbfSoftBlocks: mlbfSoftBlocksRes.value,
    };
  },

  async _updateMLBF(forceUpdate = false) {
    // The update process consists of fetching the collection, followed by
    // potentially multiple network requests. As long as the collection has not
    // been changed, repeated update requests can be coalesced. But when the
    // collection has been updated, all pending update requests should await the
    // new update request instead of the previous one.
    if (!forceUpdate && this._updatePromise) {
      return this._updatePromise;
    }
    const isUpdateReplaced = () => this._updatePromise != updatePromise;
    const updatePromise = (async () => {
      if (!gBlocklistEnabled) {
        this._mlbfData = null;
        this._mlbfDataSoftBlocks = null;
        this._stashes = null;
        return;
      }
      if (!gBlocklistSoftBlockEnabled) {
        this._mlbfDataSoftBlocks = null;
      }
      let records = await this._client.get();
      if (isUpdateReplaced()) {
        return;
      }

      let mlbfRecords = records
        .filter(r => r.attachment)
        // Newest attachments first.
        .sort((a, b) => b.generation_time - a.generation_time);
      const mlbfRecord = mlbfRecords.find(
        r => r.attachment_type == this.RS_ATTACHMENT_TYPE
      );
      const mlbfRecordSoftBlocks = gBlocklistSoftBlockEnabled
        ? mlbfRecords.find(
            r => r.attachment_type == this.RS_SOFTBLOCKS_ATTACHMENT_TYPE
          )
        : undefined;

      this._stashes = records
        .filter(({ stash }) => {
          return (
            // Exclude non-stashes, e.g. MLBF attachments.
            stash &&
            // Sanity check for type.
            Array.isArray(stash.blocked) &&
            Array.isArray(stash.unblocked) &&
            // NOTE: `softblocked` property is expected to not be
            // available for old existing records.
            (!stash.softblocked || Array.isArray(stash.softblocked))
          );
        })
        // Sort by stash time - newest first.
        .sort((a, b) => b.stash_time - a.stash_time)
        .map(({ stash, stash_time }) => ({
          blocked: new Set(stash.blocked),
          unblocked: new Set(stash.unblocked),
          softblocked: new Set(
            (gBlocklistSoftBlockEnabled && stash.softblocked) || []
          ),
          stash_time,
        }));

      let { mlbf, mlbfSoftBlocks } = await this._fetchMLBF(
        mlbfRecord,
        mlbfRecordSoftBlocks
      );
      // When a MLBF dump is packaged with the browser, mlbf will always be
      // non-null at this point.
      if (isUpdateReplaced()) {
        return;
      }
      this._mlbfData = mlbf;
      this._mlbfDataSoftBlocks = mlbfSoftBlocks;
    })()
      .catch(e => {
        Cu.reportError(e);
      })
      .then(() => {
        if (!isUpdateReplaced()) {
          this._updatePromise = null;
          this._recordPostUpdateTelemetry();
        }
        return this._updatePromise;
      });
    this._updatePromise = updatePromise;
    return updatePromise;
  },

  // Update the telemetry of the blocklist. This is always called, even if
  // the update request failed (e.g. due to network errors or data corruption).
  _recordPostUpdateTelemetry() {
    BlocklistTelemetry.recordRSBlocklistLastModified(
      "addons_mlbf",
      this._client
    );
    Glean.blocklist.mlbfSource.set(
      this._mlbfData?.rsAttachmentSource || "unknown"
    );
    Glean.blocklist.mlbfSoftblocksSource.set(
      this._mlbfDataSoftBlocks?.rsAttachmentSource || "unknown"
    );
    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfGenerationTime,
      this._mlbfData?.generationTime
    );
    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfSoftblocksGenerationTime,
      this._mlbfDataSoftBlocks?.generationTime
    );
    // stashes has conveniently already been sorted by stash_time, newest first.
    let stashes = this._stashes || [];
    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfStashTimeOldest,
      stashes[stashes.length - 1]?.stash_time
    );
    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfStashTimeNewest,
      stashes[0]?.stash_time
    );
  },

  // Used by BlocklistTelemetry.recordAddonBlockChangeTelemetry.
  getBlocklistMetadataForTelemetry() {
    // Blocklist telemetry can only be reported when a blocklist decision
    // has been made. That implies that the blocklist has been loaded, so
    // ExtensionBlocklistMLBF should have been initialized.
    // (except when the blocklist is disabled, or blocklist v2 is used)
    const generationTime = this._mlbfData?.generationTime ?? 0;
    const softblocksGenerationTime =
      this._mlbfDataSoftBlocks?.generationTime ?? 0;

    // Keys to include in the blocklist.addonBlockChange telemetry event.
    return {
      mlbf_last_time:
        // stashes are sorted, newest first. Stashes are newer than the MLBF.
        `${this._stashes?.[0]?.stash_time ?? generationTime}`,
      mlbf_generation: `${generationTime}`,
      mlbf_source: this._mlbfData?.rsAttachmentSource ?? "unknown",
      mlbf_softblocks_generation: `${softblocksGenerationTime}`,
      mlbf_softblocks_source:
        this._mlbfDataSoftBlocks?.rsAttachmentSource ?? "unknown",
    };
  },

  ensureInitialized() {
    if (!gBlocklistEnabled || this._initialized) {
      return;
    }
    this._initialized = true;
    this._client = lazy.RemoteSettings("addons-bloomfilters", {
      bucketName: BLOCKLIST_BUCKET,
      // Prevent the attachment for being pruned, since its ID does
      // not match any record.
      keepAttachmentsIds: [
        this.RS_ATTACHMENT_ID,
        this.RS_SOFTBLOCKS_ATTACHMENT_ID,
      ],
    });
    this._onUpdate = this._onUpdate.bind(this);
    this._client.on("sync", this._onUpdate);
  },

  shutdown() {
    if (this._client) {
      this._client.off("sync", this._onUpdate);
      this._didShutdown = true;
    }
  },

  // Called when the blocklist implementation is changed via a pref.
  undoShutdown() {
    if (this._didShutdown) {
      this._client.on("sync", this._onUpdate);
      this._didShutdown = false;
    }
  },

  async _onUpdate() {
    this.ensureInitialized();
    await this._updateMLBF(true);

    let addons = await lazy.AddonManager.getAddonsByTypes(lazy.kXPIAddonTypes);
    for (let addon of addons) {
      let oldState = addon.blocklistState;
      await addon.updateBlocklistState(true /* applySoftBlock */);
      let state = addon.blocklistState;

      LOG(
        "Blocklist state for " +
          addon.id +
          " changed from " +
          oldState +
          " to " +
          state
      );

      if (state == oldState) {
        continue;
      }

      BlocklistTelemetry.recordAddonBlockChangeTelemetry(
        addon,
        "blocklist_update"
      );
    }

    lazy.AddonManagerPrivate.updateAddonAppDisabledStates();
  },

  async getState(addon) {
    let state = await this.getEntry(addon);
    return state ? state.state : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  },

  async getEntry(addon) {
    if (!this._stashes) {
      this.ensureInitialized();
      await this._updateMLBF(false);
    } else if (this._updatePromise) {
      // _stashes has been initialized, but the initialization of _mlbfData is
      // still pending.
      await this._updatePromise;
    }

    let blockKey = addon.id + ":" + addon.version;

    let blockTimestamp = -Infinity;
    let blockState;
    let blockSource;

    // _stashes will be unset if !gBlocklistEnabled.
    if (this._stashes) {
      // Stashes are ordered by newest first.
      for (let stash of this._stashes) {
        // hard-blocked and soft-blocked/not-blocked do not have overlapping entries.
        if (stash.blocked.has(blockKey)) {
          blockState = Ci.nsIBlocklistService.STATE_BLOCKED;
          blockTimestamp = stash.stash_time;
          blockSource = "stash";
          break;
        }
        if (stash.softblocked.has(blockKey)) {
          blockState = Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
          blockTimestamp = stash.stash_time;
          blockSource = "stash";
          break;
        }
        if (stash.unblocked.has(blockKey)) {
          blockState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
          blockTimestamp = stash.stash_time;
          blockSource = "stash";
          break;
        }
      }
    }

    // Determine if the hard-blocks MLBF data has a more recent
    // STATE_BLOCKED blocklist state.
    if (
      this._mlbfData?.generationTime > blockTimestamp &&
      this._isAddonInMLBFData(addon, this._mlbfData)
    ) {
      blockState = Ci.nsIBlocklistService.STATE_BLOCKED;
      blockSource = this.RS_ATTACHMENT_TYPE;
      blockTimestamp = this._mlbfData.generationTime;
    }

    // Determine if the soft-blocks MLBF data has a more recent
    // STATE_SOFTBLOCKED blocklist state.
    if (
      this._mlbfDataSoftBlocks?.generationTime > blockTimestamp &&
      this._isAddonInMLBFData(addon, this._mlbfDataSoftBlocks)
    ) {
      blockState = Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
      blockSource = this.RS_SOFTBLOCKS_ATTACHMENT_TYPE;
      blockTimestamp = this._mlbfDataSoftBlocks.generationTime;
    }

    if (blockSource) {
      LOG(
        `ExtensionBlocklistMLBF.getEntry: block entry for ${blockKey} - ` +
          `state ${blockState}, source ${blockSource}, timestamp ${blockTimestamp}`
      );
    }

    if (blockState === Ci.nsIBlocklistService.STATE_BLOCKED) {
      return this._createBlockEntry({ addon });
    }

    if (blockState === Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
      return this._createBlockEntry({ addon, softBlock: true });
    }

    return null;
  },

  _isAddonInMLBFData(addon, mlbfData) {
    // signedDate is a Date if the add-on is signed, null if not signed,
    // undefined if it's an addon update descriptor instead of an addon wrapper.
    let { signedDate } = addon;
    if (!signedDate) {
      // The MLBF does not apply to unsigned add-ons.
      return false;
    }

    if (!mlbfData) {
      // This could happen in theory in any of the following cases:
      // - the blocklist is disabled.
      // - The RemoteSettings backend served a malformed MLBF.
      // - The RemoteSettings backend is unreachable, and this client was built
      //   without including a dump of the MLBF.
      //
      // ... in other words, this is unlikely to happen for hard-blocks in practice.
      //
      // For soft-blocks MLBF data may be missing if the volume of the soft-blocks
      // is being low enough to fit into the stashes.
      return false;
    }

    let blockKey = addon.id + ":" + addon.version;
    let { cascadeFilter, generationTime } = mlbfData;
    if (!cascadeFilter.has(blockKey)) {
      // Add-on was not blocked when this MLBF was generated,
      // or add-on unknown at generation time of this MLBF.
      return false;
    }

    // Add-on blocked, or unknown add-on inadvertently labeled as blocked.
    let { signedState } = addon;
    if (
      signedState !== lazy.AddonManager.SIGNEDSTATE_PRELIMINARY &&
      signedState !== lazy.AddonManager.SIGNEDSTATE_SIGNED
    ) {
      // The block decision can only be relied upon for known add-ons, i.e.
      // signed via AMO. Anything else is unknown and ignored:
      //
      // - SIGNEDSTATE_SYSTEM and SIGNEDSTATE_PRIVILEGED are signed
      //   independently of AMO.
      //
      // - SIGNEDSTATE_NOT_REQUIRED already has an early return above due to
      //   signedDate being unset for these kinds of add-ons.
      //
      // - SIGNEDSTATE_BROKEN, SIGNEDSTATE_UNKNOWN and SIGNEDSTATE_MISSING
      //   means that the signature cannot be relied upon. It is equivalent to
      //   removing the signature from the XPI file, which already causes them
      //   to be disabled on release builds (where MOZ_REQUIRE_SIGNING=true).
      return false;
    }

    if (signedDate.getTime() > generationTime) {
      // The bloom filter only reports 100% accurate results for known add-ons.
      // Since the add-on was unknown when the bloom filter was generated, the
      // block decision is incorrect and should be treated as unblocked.
      return false;
    }

    if (AppConstants.NIGHTLY_BUILD && addon.type === "locale") {
      // Only Mozilla can create langpacks with a valid signature.
      // Langpacks for Release, Beta and ESR are submitted to AMO.
      // DevEd does not support external langpacks (bug 1563923), only builtins.
      //   (and built-in addons are not subjected to the blocklist).
      // Langpacks for Nightly are not known to AMO, so the MLBF cannot be used.
      return false;
    }

    // Addon found in the mlbfData and not exempted from blocks due to
    // signature type and datetime, nor a langpack xpi installed on Nightly builds.
    return true;
  },

  _createBlockEntry({ addon, softBlock = false }) {
    return {
      state: softBlock
        ? Ci.nsIBlocklistService.STATE_SOFTBLOCKED
        : Ci.nsIBlocklistService.STATE_BLOCKED,
      url: this.createBlocklistURL(addon.id, addon.version),
    };
  },

  createBlocklistURL(id, version) {
    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ADDONITEM_URL);
    return url.replace(/%addonID%/g, id).replace(/%addonVersion%/g, version);
  },
};

const EXTENSION_BLOCK_FILTERS = [
  "id",
  "name",
  "creator",
  "homepageURL",
  "updateURL",
];

var gLoggingEnabled = null;
var gBlocklistEnabled = true;
var gBlocklistSoftBlockEnabled = true;
var gBlocklistLevel = DEFAULT_LEVEL;

/**
 * @class nsIBlocklistPrompt
 *
 * nsIBlocklistPrompt is used, if available, by the default implementation of
 * nsIBlocklistService to display a confirmation UI to the user before blocking
 * extensions/plugins.
 */
/**
 * @method prompt
 *
 * Prompt the user about newly blocked addons. The prompt is then resposible
 * for soft-blocking any addons that need to be afterwards
 *
 * @param {object[]} aAddons
 *         An array of addons and plugins that are blocked. These are javascript
 *         objects with properties:
 *          name    - the plugin or extension name,
 *          version - the version of the extension or plugin,
 *          icon    - the plugin or extension icon,
 *          disable - can be used by the nsIBlocklistPrompt to allows users to decide
 *                    whether a soft-blocked add-on should be disabled,
 *          blocked - true if the item is hard-blocked, false otherwise,
 *          item    - the Addon object
 */

// It is not possible to use the one in Services since it will not successfully
// QueryInterface nsIXULAppInfo in xpcshell tests due to other code calling
// Services.appinfo before the nsIXULAppInfo is created by the tests.
ChromeUtils.defineLazyGetter(lazy, "gApp", function () {
  // eslint-disable-next-line mozilla/use-services
  let appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  try {
    appinfo.QueryInterface(Ci.nsIXULAppInfo);
  } catch (ex) {
    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (
      !(ex instanceof Components.Exception) ||
      ex.result != Cr.NS_NOINTERFACE
    ) {
      throw ex;
    }
  }
  return appinfo;
});

ChromeUtils.defineLazyGetter(lazy, "gAppID", function () {
  return lazy.gApp.ID;
});
ChromeUtils.defineLazyGetter(lazy, "gAppOS", function () {
  return lazy.gApp.OS;
});

/**
 * Logs a string to the error console.
 * @param {string} string
 *        The string to write to the error console..
 */
function LOG(string) {
  if (gLoggingEnabled) {
    dump("*** " + string + "\n");
    Services.console.logStringMessage(string);
  }
}

export let Blocklist = {
  get isEnabled() {
    return gBlocklistEnabled;
  },

  get isSoftBlockEnabled() {
    return gBlocklistSoftBlockEnabled;
  },

  _init() {
    Services.obs.addObserver(this, "xpcom-shutdown");
    gLoggingEnabled = Services.prefs.getBoolPref(
      PREF_EM_LOGGING_ENABLED,
      false
    );
    gBlocklistSoftBlockEnabled = Services.prefs.getBoolPref(
      PREF_BLOCKLIST_SOFTBLOCK_ENABLED,
      true
    );
    gBlocklistEnabled = Services.prefs.getBoolPref(
      PREF_BLOCKLIST_ENABLED,
      true
    );
    gBlocklistLevel = Math.min(
      Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
      MAX_BLOCK_LEVEL
    );
    this._chooseExtensionBlocklistImplementationFromPref();
    Services.prefs.addObserver("extensions.blocklist.", this);
    Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
  },
  isLoaded: true,

  shutdown() {
    GfxBlocklistRS.shutdown();
    this.ExtensionBlocklist.shutdown();

    Services.obs.removeObserver(this, "xpcom-shutdown");
    Services.prefs.removeObserver("extensions.blocklist.", this);
    Services.prefs.removeObserver(PREF_EM_LOGGING_ENABLED, this);
  },

  observe(subject, topic, prefName) {
    switch (topic) {
      case "xpcom-shutdown":
        this.shutdown();
        break;
      case "nsPref:changed":
        switch (prefName) {
          case PREF_EM_LOGGING_ENABLED:
            gLoggingEnabled = Services.prefs.getBoolPref(
              PREF_EM_LOGGING_ENABLED,
              false
            );
            break;
          case PREF_BLOCKLIST_ENABLED:
            gBlocklistEnabled = Services.prefs.getBoolPref(
              PREF_BLOCKLIST_ENABLED,
              true
            );
            this._blocklistUpdated();
            break;
          case PREF_BLOCKLIST_SOFTBLOCK_ENABLED:
            gBlocklistSoftBlockEnabled = Services.prefs.getBoolPref(
              PREF_BLOCKLIST_SOFTBLOCK_ENABLED,
              true
            );
            this._blocklistUpdated();
            break;
          case PREF_BLOCKLIST_LEVEL:
            gBlocklistLevel = Math.min(
              Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
              MAX_BLOCK_LEVEL
            );
            this._blocklistUpdated();
            break;
          case PREF_BLOCKLIST_USE_MLBF: {
            let oldImpl = this.ExtensionBlocklist;
            this._chooseExtensionBlocklistImplementationFromPref();
            // The implementation may be unchanged when the pref is ignored.
            if (oldImpl != this.ExtensionBlocklist && oldImpl._initialized) {
              oldImpl.shutdown();
              this.ExtensionBlocklist.undoShutdown();
              this.ExtensionBlocklist._onUpdate();
            } // else neither has been initialized yet. Wait for it to happen.
            break;
          }
        }
        break;
    }
  },

  loadBlocklistAsync() {
    // Need to ensure we notify gfx of new stuff.
    // Geckoview calls this for each new tab (bug 1730026), so ensure we only
    // check for entries when first initialized.
    if (!GfxBlocklistRS._initialized) {
      GfxBlocklistRS.checkForEntries();
    }
    this.ExtensionBlocklist.ensureInitialized();
  },

  getAddonBlocklistState(addon, appVersion, toolkitVersion) {
    // NOTE: appVersion/toolkitVersion are only used by ExtensionBlocklistRS.
    return this.ExtensionBlocklist.getState(addon, appVersion, toolkitVersion);
  },

  getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
    // NOTE: appVersion/toolkitVersion are only used by ExtensionBlocklistRS.
    return this.ExtensionBlocklist.getEntry(addon, appVersion, toolkitVersion);
  },

  recordAddonBlockChangeTelemetry(addon, reason) {
    BlocklistTelemetry.recordAddonBlockChangeTelemetry(addon, reason);
  },
  // TODO bug 1649906: Remove blocklist v2 (dead code).
  allowDeprecatedBlocklistV2: false,

  _chooseExtensionBlocklistImplementationFromPref() {
    if (
      this.allowDeprecatedBlocklistV2 &&
      !Services.prefs.getBoolPref(PREF_BLOCKLIST_USE_MLBF, false)
    ) {
      this.ExtensionBlocklist = ExtensionBlocklistRS;
    } else {
      this.ExtensionBlocklist = ExtensionBlocklistMLBF;
    }
  },

  _blocklistUpdated() {
    this.ExtensionBlocklist._onUpdate();
  },
};

Blocklist._init();

// Allow tests to reach implementation objects.
export const BlocklistPrivate = {
  BlocklistTelemetry,
  ExtensionBlocklistMLBF,
  ExtensionBlocklistRS,
  GfxBlocklistRS,
};

[ Dauer der Verarbeitung: 0.40 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge