blob: 2ca972fa7e4e38fc381d6d45cb848ac0a05893cd [file] [edit]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* chrome.storage based class with an async interface that is interchangeable
* with other lib.Storage.* implementations.
*
* @param {!StorageArea} storage The backing storage.
* @implements {lib.Storage}
* @constructor
*/
lib.Storage.Chrome = function(storage) {
this.storage_ = storage;
this.observers_ = [];
// R73 introduced onChanged for the storage area. Keep backwards compat
// for EOL systems until we drop app support completely.
if (storage.onChanged) {
// Newer R73+ code.
storage.onChanged.addListener(this.onChanged_.bind(this));
} else {
// Older <R73 code.
chrome.storage.onChanged.addListener((changes, areaname) => {
if (chrome.storage[areaname] === this.storage_) {
this.onChanged_(changes);
}
});
}
};
/**
* Called by the storage implementation when the storage is modified.
*
* @param {!Object<string, !StorageChange>} changes Object mapping each key that
* changed to its corresponding StorageChange for that item.
*/
lib.Storage.Chrome.prototype.onChanged_ = function(changes) {
this.observers_.forEach((o) => o(changes));
};
/**
* Register a function to observe storage changes.
*
* @param {function(!Object<string, !StorageChange>)} callback The function to
* invoke when the storage changes.
* @override
*/
lib.Storage.Chrome.prototype.addObserver = function(callback) {
this.observers_.push(callback);
};
/**
* Unregister a change observer.
*
* @param {function(!Object<string, !StorageChange>)} callback A previously
* registered callback.
* @override
*/
lib.Storage.Chrome.prototype.removeObserver = function(callback) {
const i = this.observers_.indexOf(callback);
if (i != -1) {
this.observers_.splice(i, 1);
}
};
/**
* Delete everything in this storage.
*
* @override
*/
lib.Storage.Chrome.prototype.clear = async function() {
return new Promise((resolve) => {
this.storage_.clear(resolve);
});
};
/**
* Return the current value of a storage item.
*
* @param {string} key The key to look up.
* @override
*/
lib.Storage.Chrome.prototype.getItem = async function(key) {
return this.getItems([key]).then((items) => items[key]);
};
/**
* Fetch the values of multiple storage items.
*
* @param {?Array<string>} keys The keys to look up. Pass null for all keys.
* @override
*/
lib.Storage.Chrome.prototype.getItems = async function(keys) {
return new Promise((resolve) => {
this.storage_.get(keys, resolve);
});
};
/**
* Set a value in storage.
*
* @param {string} key The key for the value to be stored.
* @param {*} value The value to be stored. Anything that can be serialized
* with JSON is acceptable.
* @override
*/
lib.Storage.Chrome.prototype.setItem = async function(key, value) {
return new Promise((resolve) => {
const onComplete = () => {
const err = lib.f.lastError();
if (err) {
// Doesn't seem to be any better way of handling this.
// https://crbug.com/764759
if (err.indexOf('MAX_WRITE_OPERATIONS')) {
console.warn(`Will retry '${key}' save after exceeding quota:`, err);
setTimeout(() => this.setItem(key, value).then(onComplete), 1000);
return;
} else {
console.error(`Unknown runtime error: ${err}`);
}
}
resolve();
};
this.setItems({[key]: value}).then(onComplete);
});
};
/**
* Set multiple values in storage.
*
* @param {!Object} obj A map of key/values to set in storage.
* @override
*/
lib.Storage.Chrome.prototype.setItems = async function(obj) {
return new Promise((resolve) => {
this.storage_.set(obj, resolve);
});
};
/**
* Remove an item from storage.
*
* @param {string} key The key to be removed.
* @override
*/
lib.Storage.Chrome.prototype.removeItem = async function(key) {
return this.removeItems([key]);
};
/**
* Remove multiple items from storage.
*
* @param {!Array<string>} keys The keys to be removed.
* @override
*/
lib.Storage.Chrome.prototype.removeItems = async function(keys) {
return new Promise((resolve) => {
this.storage_.remove(keys, resolve);
});
};