db.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /* global chromeLocal */// storage-util.js
  2. /* global cloneError */// worker-util.js
  3. 'use strict';
  4. /*
  5. Initialize a database. There are some problems using IndexedDB in Firefox:
  6. https://www.reddit.com/r/firefox/comments/74wttb/note_to_firefox_webextension_developers_who_use/
  7. Some of them are fixed in FF59:
  8. https://www.reddit.com/r/firefox/comments/7ijuaq/firefox_59_webextensions_can_use_indexeddb_when/
  9. */
  10. /* exported db */
  11. const db = (() => {
  12. const DATABASE = 'stylish';
  13. const STORE = 'styles';
  14. const FALLBACK = 'dbInChromeStorage';
  15. const dbApi = {
  16. async exec(...args) {
  17. dbApi.exec = await tryUsingIndexedDB().catch(useChromeStorage);
  18. return dbApi.exec(...args);
  19. },
  20. };
  21. return dbApi;
  22. async function tryUsingIndexedDB() {
  23. // we use chrome.storage.local fallback if IndexedDB doesn't save data,
  24. // which, once detected on the first run, is remembered in chrome.storage.local
  25. // note that accessing indexedDB may throw, https://github.com/openstyles/stylus/issues/615
  26. if (typeof indexedDB === 'undefined') {
  27. throw new Error('indexedDB is undefined');
  28. }
  29. switch (await chromeLocal.getValue(FALLBACK)) {
  30. case true: throw null;
  31. case false: break;
  32. default: await testDB();
  33. }
  34. chromeLocal.setValue(FALLBACK, false);
  35. return dbExecIndexedDB;
  36. }
  37. async function testDB() {
  38. const id = `${performance.now()}.${Math.random()}.${Date.now()}`;
  39. await dbExecIndexedDB('put', {id});
  40. const e = await dbExecIndexedDB('get', id);
  41. await dbExecIndexedDB('delete', e.id); // throws if `e` or id is null
  42. }
  43. async function useChromeStorage(err) {
  44. chromeLocal.setValue(FALLBACK, true);
  45. if (err) {
  46. chromeLocal.setValue(FALLBACK + 'Reason', cloneError(err));
  47. console.warn('Failed to access indexedDB. Switched to storage API.', err);
  48. }
  49. await require(['/background/db-chrome-storage']); /* global createChromeStorageDB */
  50. return createChromeStorageDB();
  51. }
  52. async function dbExecIndexedDB(method, ...args) {
  53. const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
  54. const store = (await open()).transaction([STORE], mode).objectStore(STORE);
  55. const fn = method === 'putMany' ? putMany : storeRequest;
  56. return fn(store, method, ...args);
  57. }
  58. function storeRequest(store, method, ...args) {
  59. return new Promise((resolve, reject) => {
  60. /** @type {IDBRequest} */
  61. const request = store[method](...args);
  62. request.onsuccess = () => resolve(request.result);
  63. request.onerror = reject;
  64. });
  65. }
  66. function putMany(store, _method, items) {
  67. return Promise.all(items.map(item => storeRequest(store, 'put', item)));
  68. }
  69. function open() {
  70. return new Promise((resolve, reject) => {
  71. const request = indexedDB.open(DATABASE, 2);
  72. request.onsuccess = () => resolve(request.result);
  73. request.onerror = reject;
  74. request.onupgradeneeded = create;
  75. });
  76. }
  77. function create(event) {
  78. if (event.oldVersion === 0) {
  79. event.target.result.createObjectStore(STORE, {
  80. keyPath: 'id',
  81. autoIncrement: true,
  82. });
  83. }
  84. }
  85. })();