// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/** @enum {number} */
const SchemeType = {
NORMAL: 0,
INCREASED_CONTRAST: 1,
GRAYSCALE: 2,
INVERTED_COLOR: 3,
INVERTED_GRAYSCALE: 4,
YELLOW_ON_BLACK: 5,
};
/**
* Class to handle interactions with the chrome.storage API and values that are
* stored there.
*/
class Storage {
/**
* @param {function()=} opt_callbackForTesting
* @private
*/
constructor(opt_callbackForTesting) {
/** @private {boolean} */
this.enabled_ = Storage.ENABLED.defaultValue;
/** @private {!SchemeType} */
this.baseScheme_ = Storage.SCHEME.defaultValue;
/** @private {!Object<string, !SchemeType>} */
this.siteSchemes_ = Storage.SITE_SCHEMES.defaultValue;
this.init_();
}
// ======= Public Methods =======
static initialize() {
if (!Storage.instance) {
Storage.instance = new Storage();
}
}
/** @return {boolean} */
static get enabled() { return Storage.instance.enabled_; }
/** @return {!SchemeType} */
static get baseScheme() { return Storage.instance.baseScheme_; }
/**
* @param {string} site
* @return {!SchemeType}
*/
static getSiteScheme(site) {
const scheme = Storage.instance.siteSchemes_[site];
if (Storage.SCHEME.validate(scheme)) {
return scheme;
}
return Storage.baseScheme;
}
/** @param {boolean} newValue */
static set enabled(newValue) {
Storage.instance.setOrResetValue_(Storage.ENABLED, newValue);
Storage.instance.store_(Storage.ENABLED);
}
/** @param {!SchemeType} newScheme */
static set baseScheme(newScheme) {
Storage.instance.setOrResetValue_(Storage.SCHEME, newScheme);
Storage.instance.store_(Storage.SCHEME);
}
/**
* @param {string} site
* @param {!SchemeType} scheme
*/
static setSiteScheme(site, scheme) {
if (Storage.SCHEME.validate(scheme)) {
Storage.instance.siteSchemes_[site] = scheme;
} else {
Storage.instance.siteSchemes_[site] = Storage.baseScheme;
}
Storage.instance.store_(Storage.SITE_SCHEMES);
}
static resetSiteSchemes() {
Storage.instance.siteSchemes_ = Storage.SITE_SCHEMES.defaultValue;
Storage.instance.store_(Storage.SITE_SCHEMES);
}
// ======= 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 {function()=} opt_callback
* @private
*/
init_(opt_callback) {
chrome.storage.onChanged.addListener(this.onChange_);
chrome.storage.local.get(null /* all values */, (results) => {
const storedData = Storage.ALL_VALUES.filter(v => results[v.key]);
for (const data of storedData) {
this.setOrResetValue_(data, results[data.key]);
}
opt_callback ? opt_callback() : undefined;
});
}
/**
* @param {!Object<string, chrome.storage.StorageChange>} changes
* @private
*/
onChange_(changes) {
const changedData = Storage.ALL_VALUES.filter(v => changes[v.key]);
for (const data of changedData) {
Storage.instance.setOrResetValue_(data, changes[data.key].newValue);
}
}
/**
* @param {!Storage.Value} value
* @private
*/
store_(value) {
chrome.storage.local.set({ [value.key]: value.get() });
}
// ======= Stored Values =======
/**
* @typedef {{
* key: string,
* defaultValue: *,
* validate: function(*): boolean,
* get: function: *,
* set: function(*),
* reset: function(),
* listeners: !Array<function(*)>
* }}
*/
static Value;
/** @const {!Storage.Value} */
static ENABLED = {
key: 'enabled',
defaultValue: true,
validate: (enabled) => enabled === true || enabled === false,
get: () => Storage.instance.enabled_,
set: (enabled) => Storage.instance.enabled_ = enabled,
reset: () => Storage.instance.enabled_ = Storage.ENABLED.defaultValue,
listeners: [],
};
/** @const {!Storage.Value} */
static SCHEME = {
key: 'scheme',
defaultValue: SchemeType.INVERTED_COLOR,
validate: (scheme) => Object.values(SchemeType).includes(scheme),
get: () => Storage.instance.baseScheme_,
set: (scheme) => Storage.instance.baseScheme_ = scheme,
reset: () => Storage.instance.baseScheme_ = Storage.SCHEME.defaultValue,
listeners: [],
};
/** @const {!Storage.Value} */
static SITE_SCHEMES = {
key: 'siteschemes',
defaultValue: {},
validate: (siteSchemes) => typeof (siteSchemes) === 'object',
get: () => Storage.instance.siteSchemes_,
set: (siteSchemes) => {
for (const site of Object.keys(siteSchemes)) {
if (Storage.SCHEME.validate(siteSchemes[site])) {
Storage.instance.siteSchemes_[site] = siteSchemes[site];
}
}
},
reset: () => {} /** Do nothing */,
listeners: [],
};
/** @const {!Array<!Storage.Value>} */
static ALL_VALUES = [
Storage.ENABLED, Storage.SCHEME, Storage.SITE_SCHEMES
];
}