chromium/ui/accessibility/extensions/colorenhancer/src/storage.js

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Class to handle both storing values using the chrome.storage API, and
 * fetching/caching values that have been stored that way.
 */
class Storage {
   /** @private */
  constructor() {
    /** @private {number} */
    this.baseDelta_ = Storage.DELTA.defaultValue;

    /** @private {!Object<string, number>} */
    this.siteDeltas_ = Storage.SITE_DELTAS.defaultValue;

    /** @private {number} */
    this.severity_ = Storage.SEVERITY.defaultValue;

    /** @private {!OptionalCvdType} */
    this.type_ = Storage.TYPE.defaultValue;

    /** @private {boolean} */
    this.simulate_ = Storage.SIMULATE.defaultValue;

    /** @private {boolean} */
    this.enable_ = Storage.ENABLE.defaultValue;

    /** @private {!CvdAxis|undefined} */
    this.axis_ = undefined;
  }

  // ======= Public methods =======

  /**
   * @param {function()=} opt_callbackForTesting
   */
  static initialize(opt_callbackForTesting) {
    if (!Storage.instance || opt_callbackForTesting) {
      Storage.instance = new Storage();
      Storage.instance.init_(opt_callbackForTesting);
    }
  }

  /** @return {number} */
  static get baseDelta() { return Storage.instance.baseDelta_; }
  /** @return {number} */
  static get severity() { return Storage.instance.severity_; }
  /** @return {!OptionalCvdType} */
  static get type() { return Storage.instance.type_; }
  /** @return {boolean} */
  static get simulate() { return Storage.instance.simulate_; }
  /** @return {boolean} */
  static get enable() { return Storage.instance.enable_; }
  /** @return {!CvdAxis} */
  static get axis() {
    // on earlier versions axis was not defined and deutan
    // correction used a RED shift. Ensure backwards compatibility
    // with legacy behavior
    if (Storage.instance.axis_ === undefined) {
      if (Storage.instance.type_ === CvdType.DEUTERANOMALY)
        return CvdAxis.RED;
      else
        return CvdAxis.DEFAULT;
    }
    return Storage.instance.axis_;
  }

  /**
   * @param {string} site
   * @return {number}
   */
  static getSiteDelta(site) {
    const delta = Storage.instance.siteDeltas_[site];
    if (Storage.DELTA.validate(delta)) {
      return delta;
    }
    Storage.setSiteDelta(site, Storage.baseDelta);
    return Storage.baseDelta;
  }

  /** @param {number} newDelta */
  static set baseDelta(newDelta) {
    Storage.instance.setOrResetValue_(Storage.DELTA, newDelta);
    Storage.instance.store_(Storage.DELTA);
  }

  /** @param {number} newSeverity */
  static set severity(newSeverity) {
    Storage.instance.setOrResetValue_(Storage.SEVERITY, newSeverity);
    Storage.instance.store_(Storage.SEVERITY);
  }

  /** @param {!CvdType} newCvdType */
  static set type(newCvdType) {
    Storage.instance.setOrResetValue_(Storage.TYPE, newCvdType);
    Storage.instance.store_(Storage.TYPE);
  }

  /** @param {boolean} newValue */
  static set simulate(newValue) {
    Storage.instance.setOrResetValue_(Storage.SIMULATE, newValue);
    Storage.instance.store_(Storage.SIMULATE);
  }

  /** @param {boolean} newValue */
  static set enable(newValue) {
    Storage.instance.setOrResetValue_(Storage.ENABLE, newValue);
    Storage.instance.store_(Storage.ENABLE);
  }

  /**
   * @param {string} site
   * @param {number} delta
   */
  static setSiteDelta(site, delta) {
    if (Storage.DELTA.validate(delta)) {
      Storage.instance.siteDeltas_[site] = delta;
    } else {
      Storage.instance.siteDeltas_[site] = Storage.baseDelta;
    }
    Storage.instance.store_(Storage.SITE_DELTAS);
  }

  /** @param {!CvdAxis} newCvdAxis */
  static set axis(newCvdAxis) {
    Storage.instance.setOrResetValue_(Storage.AXIS, newCvdAxis);
    Storage.instance.store_(Storage.AXIS);
  }

  // ======== Private Methods ========

  /**
   * @param {!Storage.Value} container
   * @param {*} newValue
   * @private
   */
  setOrResetValue_(container, newValue) {
    if (newValue === container.get()) {
      return;
    }

    if (container.validate(newValue)) {
      container.set(newValue);
    } else {
      container.reset();
    }

    container.listeners.forEach(listener => listener(newValue));
  }

  /**
   * @param {!Storage.Value} value
   * @private
   */
  store_(value) {
    chrome.storage.local.set({ [value.key]: value.get() });
  }

  /**
   * @param {function()} opt_callback
   * @private
   */
  init_(opt_callback) {
    chrome.storage.onChanged.addListener(this.onChange_.bind(this));

    chrome.storage.local.get(null /* all values */, (results) => {
      const storedValues = Storage.ALL_VALUES.filter(v => results[v.key]);
      for (const value of storedValues) {
        this.setOrResetValue_(value, results[value.key]);
      }

      opt_callback ? opt_callback() : undefined;
    });
  }

  /**
   * @param {!Object<string, chrome.storage.StorageChange>} changes
   * @private
   */
  onChange_(changes) {
    const changedValues = Storage.ALL_VALUES.filter(v => changes[v.key]);
    for (const value of changedValues) {
      this.setOrResetValue_(value, changes[value.key].newValue);
    }
  }

  // ======== Stored Values ========

  /** @const {string} */
  static INVALID_TYPE_PLACEHOLDER = '';

  /**
   * @typedef {{
   *     key: string,
   *     defaultValue: *,
   *     validate: function(*): boolean,
   *     get: function(): *,
   *     set: function(*),
   *     reset: function(),
   *     listeners: !Array<function(*)>
   * }}
   */
  static Value;

  /** @const {!Storage.Value} */
  static DELTA = {
    key: 'cvd_delta',
    defaultValue: 0.5,
    validate: (delta) => delta >= 0 && delta <= 1,
    get: () => Storage.instance.baseDelta_,
    set: (delta) => Storage.instance.baseDelta_ = delta,
    reset: () => Storage.instance.baseDelta_ = Storage.DELTA.defaultValue,
    listeners: [],
  };

  /** @const {!Storage.Value} */
  static SITE_DELTAS = {
    key: 'cvd_site_delta',
    defaultValue: {},
    validate: (siteDeltas) => typeof (siteDeltas) === 'object',
    get: () => Storage.instance.siteDeltas_,
    set: (siteDeltas) => {
      for (const site of Object.keys(siteDeltas)) {
        if (Storage.DELTA.validate(siteDeltas[site])) {
          Storage.instance.siteDeltas_[site] = siteDeltas[site];
        }
      }
    },
    reset: () => {} /* Do nothing */,
    listeners: [],
  };

  /** @const {!Storage.Value} */
  static SEVERITY = {
    key: 'cvd_severity',
    defaultValue: 1.0,
    validate: (severity) => severity >= 0 && severity <= 1,
    get: () => Storage.instance.severity_,
    set: (severity) => Storage.instance.severity_ = severity,
    reset: () => Storage.instance.severity_ = Storage.SEVERITY.defaultValue,
    listeners: [],
  };

  /** @const {!Storage.Value} */
  static TYPE = {
    key: 'cvd_type',
    defaultValue: Storage.INVALID_TYPE_PLACEHOLDER,
    validate: (type) => Object.values(CvdType).includes(type),
    get: () => Storage.instance.type_,
    set: (type) => Storage.instance.type_ = type,
    reset: () => Storage.instance.type_ = Storage.TYPE.defaultValue,
    listeners: [],
  };

  /** @const {!Storage.Value} */
  static SIMULATE = {
    key: 'cvd_simulate',
    defaultValue: false,
    validate: (simulate) => typeof (simulate) === 'boolean',
    get: () => Storage.instance.simulate_,
    set: (simulate) => Storage.instance.simulate_ = simulate,
    reset: () => Storage.instance.simulate_ = Storage.SIMULATE.defaultValue,
    listeners: [],
  };

  /** @const {!Storage.Value} */
  static ENABLE = {
    key: 'cvd_enable',
    defaultValue: false,
    validate: (enable) => typeof (enable) === 'boolean',
    get: () => Storage.instance.enable_,
    set: (enable) => Storage.instance.enable_ = enable,
    reset: () => Storage.instance.enable_ = Storage.ENABLE.defaultValue,
    listeners: [],
  };

  /** @const {!Storage.Value} */
  static AXIS = {
    key: 'cvd_axis',
    defaultValue: 'DEFAULT',
    validate: (axis) => Object.values(CvdAxis).includes(axis),
    get: () => Storage.instance.axis_,
    set: (axis) => Storage.instance.axis_ = axis,
    reset: () => Storage.instance.axis_ = Storage.AXIS.defaultValue,
    listeners: [],
  };

  /** @const {!Array<!Storage.Value>} */
  static ALL_VALUES = [
      Storage.DELTA, Storage.SITE_DELTAS, Storage.SEVERITY, Storage.TYPE,
      Storage.SIMULATE, Storage.ENABLE, Storage.AXIS,
  ];
}