146 lines
4.3 KiB
JavaScript
146 lines
4.3 KiB
JavaScript
'use strict';
|
|
|
|
const db = (() => {
|
|
let exec;
|
|
const preparing = prepare();
|
|
return {
|
|
exec: (...args) =>
|
|
preparing.then(() => exec(...args))
|
|
};
|
|
|
|
function prepare() {
|
|
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
|
// which, once detected on the first run, is remembered in chrome.storage.local
|
|
// for reliablility and in localStorage for fast synchronous access
|
|
// (FF may block localStorage depending on its privacy options)
|
|
|
|
// test localStorage
|
|
const fallbackSet = localStorage.dbInChromeStorage;
|
|
if (fallbackSet === 'true' || !tryCatch(() => indexedDB)) {
|
|
useChromeStorage();
|
|
return Promise.resolve();
|
|
}
|
|
if (fallbackSet === 'false') {
|
|
useIndexedDB();
|
|
return Promise.resolve();
|
|
}
|
|
// test storage.local
|
|
return chromeLocal.get('dbInChromeStorage')
|
|
.then(data =>
|
|
data && data.dbInChromeStorage && Promise.reject())
|
|
.then(() =>
|
|
tryCatch(dbExecIndexedDB, 'getAllKeys', IDBKeyRange.lowerBound(1), 1) ||
|
|
Promise.reject())
|
|
.then(({target}) => (
|
|
(target.result || [])[0] ?
|
|
Promise.reject('ok') :
|
|
dbExecIndexedDB('put', {id: -1})))
|
|
.then(() =>
|
|
dbExecIndexedDB('get', -1))
|
|
.then(({target}) => (
|
|
(target.result || {}).id === -1 ?
|
|
dbExecIndexedDB('delete', -1) :
|
|
Promise.reject()))
|
|
.then(() =>
|
|
Promise.reject('ok'))
|
|
.catch(result => {
|
|
if (result === 'ok') {
|
|
useIndexedDB();
|
|
} else {
|
|
useChromeStorage();
|
|
}
|
|
});
|
|
}
|
|
|
|
function useChromeStorage() {
|
|
exec = dbExecChromeStorage;
|
|
chromeLocal.set({dbInChromeStorage: true}, ignoreChromeError);
|
|
localStorage.dbInChromeStorage = 'true';
|
|
}
|
|
|
|
function useIndexedDB() {
|
|
exec = dbExecIndexedDB;
|
|
chromeLocal.set({dbInChromeStorage: false}, ignoreChromeError);
|
|
localStorage.dbInChromeStorage = 'false';
|
|
}
|
|
|
|
function dbExecIndexedDB(method, ...args) {
|
|
return new Promise((resolve, reject) => {
|
|
Object.assign(indexedDB.open('stylish', 2), {
|
|
onsuccess(event) {
|
|
const database = event.target.result;
|
|
if (!method) {
|
|
resolve(database);
|
|
} else {
|
|
const transaction = database.transaction(['styles'], 'readwrite');
|
|
const store = transaction.objectStore('styles');
|
|
try {
|
|
Object.assign(store[method](...args), {
|
|
onsuccess: event => resolve(event, store, transaction, database),
|
|
onerror: reject,
|
|
});
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
}
|
|
},
|
|
onerror(event) {
|
|
console.warn(event.target.error || event.target.errorCode);
|
|
reject(event);
|
|
},
|
|
onupgradeneeded(event) {
|
|
if (event.oldVersion === 0) {
|
|
event.target.result.createObjectStore('styles', {
|
|
keyPath: 'id',
|
|
autoIncrement: true,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
function dbExecChromeStorage(method, data) {
|
|
const STYLE_KEY_PREFIX = 'style-';
|
|
switch (method) {
|
|
case 'get':
|
|
return chromeLocal.getValue(STYLE_KEY_PREFIX + data)
|
|
.then(result => ({target: {result}}));
|
|
|
|
case 'put':
|
|
if (!data.id) {
|
|
return getAllStyles().then(styles => {
|
|
data.id = 1;
|
|
for (const style of styles) {
|
|
data.id = Math.max(data.id, style.id + 1);
|
|
}
|
|
return dbExecChromeStorage('put', data);
|
|
});
|
|
}
|
|
return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data)
|
|
.then(() => (chrome.runtime.lastError ? Promise.reject() : data.id));
|
|
|
|
case 'delete':
|
|
return chromeLocal.remove(STYLE_KEY_PREFIX + data);
|
|
|
|
case 'getAll':
|
|
return getAllStyles()
|
|
.then(styles => ({target: {result: styles}}));
|
|
}
|
|
return Promise.reject();
|
|
|
|
function getAllStyles() {
|
|
return chromeLocal.get(null).then(storage => {
|
|
const styles = [];
|
|
for (const key in storage) {
|
|
if (key.startsWith(STYLE_KEY_PREFIX) &&
|
|
Number(key.substr(STYLE_KEY_PREFIX.length))) {
|
|
styles.push(storage[key]);
|
|
}
|
|
}
|
|
return styles;
|
|
});
|
|
}
|
|
}
|
|
})();
|