112 lines
3.5 KiB
JavaScript
112 lines
3.5 KiB
JavaScript
/* global chromeLocal workerUtil createChromeStorageDB */
|
|
/* exported db */
|
|
/*
|
|
Initialize a database. There are some problems using IndexedDB in Firefox:
|
|
https://www.reddit.com/r/firefox/comments/74wttb/note_to_firefox_webextension_developers_who_use/
|
|
|
|
Some of them are fixed in FF59:
|
|
https://www.reddit.com/r/firefox/comments/7ijuaq/firefox_59_webextensions_can_use_indexeddb_when/
|
|
*/
|
|
'use strict';
|
|
|
|
const db = (() => {
|
|
const DATABASE = 'stylish';
|
|
const STORE = 'styles';
|
|
const FALLBACK = 'dbInChromeStorage';
|
|
const dbApi = {
|
|
async exec(...args) {
|
|
dbApi.exec = await tryUsingIndexedDB().catch(useChromeStorage);
|
|
return dbApi.exec(...args);
|
|
},
|
|
};
|
|
return dbApi;
|
|
|
|
async function tryUsingIndexedDB() {
|
|
// 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)
|
|
// note that it may throw when accessing the variable
|
|
// https://github.com/openstyles/stylus/issues/615
|
|
if (typeof indexedDB === 'undefined') {
|
|
throw new Error('indexedDB is undefined');
|
|
}
|
|
switch (await getFallback()) {
|
|
case true: throw null;
|
|
case false: break;
|
|
default: await testDB();
|
|
}
|
|
return useIndexedDB();
|
|
}
|
|
|
|
async function getFallback() {
|
|
return localStorage[FALLBACK] === 'true' ? true :
|
|
localStorage[FALLBACK] === 'false' ? false :
|
|
chromeLocal.getValue(FALLBACK);
|
|
}
|
|
|
|
async function testDB() {
|
|
let e = await dbExecIndexedDB('getAllKeys', IDBKeyRange.lowerBound(1), 1);
|
|
// throws if result is null
|
|
e = e.target.result[0];
|
|
const id = `${performance.now()}.${Math.random()}.${Date.now()}`;
|
|
await dbExecIndexedDB('put', {id});
|
|
e = await dbExecIndexedDB('get', id);
|
|
// throws if result or id is null
|
|
await dbExecIndexedDB('delete', e.target.result.id);
|
|
}
|
|
|
|
function useChromeStorage(err) {
|
|
chromeLocal.setValue(FALLBACK, true);
|
|
if (err) {
|
|
chromeLocal.setValue(FALLBACK + 'Reason', workerUtil.cloneError(err));
|
|
console.warn('Failed to access indexedDB. Switched to storage API.', err);
|
|
}
|
|
localStorage[FALLBACK] = 'true';
|
|
return createChromeStorageDB().exec;
|
|
}
|
|
|
|
function useIndexedDB() {
|
|
chromeLocal.setValue(FALLBACK, false);
|
|
localStorage[FALLBACK] = 'false';
|
|
return dbExecIndexedDB;
|
|
}
|
|
|
|
async function dbExecIndexedDB(method, ...args) {
|
|
const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
|
|
const store = (await open()).transaction([STORE], mode).objectStore(STORE);
|
|
const fn = method === 'putMany' ? putMany : storeRequest;
|
|
return fn(store, method, ...args);
|
|
}
|
|
|
|
function storeRequest(store, method, ...args) {
|
|
return new Promise((resolve, reject) => {
|
|
const request = store[method](...args);
|
|
request.onsuccess = resolve;
|
|
request.onerror = reject;
|
|
});
|
|
}
|
|
|
|
function putMany(store, _method, items) {
|
|
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
|
|
}
|
|
|
|
function open() {
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(DATABASE, 2);
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = reject;
|
|
request.onupgradeneeded = create;
|
|
});
|
|
}
|
|
|
|
function create(event) {
|
|
if (event.oldVersion === 0) {
|
|
event.target.result.createObjectStore(STORE, {
|
|
keyPath: 'id',
|
|
autoIncrement: true,
|
|
});
|
|
}
|
|
}
|
|
})();
|