ditch localStorage, use on-demand SessionStore proxy

This commit is contained in:
tophf 2020-11-11 19:29:11 +03:00
parent 0b3e027bfd
commit e5ad9fce46
10 changed files with 76 additions and 107 deletions

View File

@ -135,24 +135,13 @@ if (chrome.commands) {
// ************************************************************************* // *************************************************************************
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => { chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
// save install type: "admin", "development", "normal", "sideload" or "other"
// "normal" = addon installed from webstore
chrome.management.getSelf(info => {
localStorage.installType = info.installType;
if (reason === 'install' && info.installType === 'development' && chrome.contextMenus) {
createContextMenus(['reload']);
}
});
if (reason !== 'update') return; if (reason !== 'update') return;
// translations may change
localStorage.L10N = JSON.stringify({
browserUIlanguage: chrome.i18n.getUILanguage(),
});
// themes may change
delete localStorage.codeMirrorThemes;
// inline search cache for USO is not needed anymore, TODO: remove this by the middle of 2021
if (semverCompare(previousVersion, '1.5.13') <= 0) { if (semverCompare(previousVersion, '1.5.13') <= 0) {
// Removing unused stuff
// TODO: delete this entire block by the middle of 2021
try {
localStorage.clear();
} catch (e) {}
setTimeout(async () => { setTimeout(async () => {
const del = Object.keys(await chromeLocal.get()) const del = Object.keys(await chromeLocal.get())
.filter(key => key.startsWith('usoSearchCache')); .filter(key => key.startsWith('usoSearchCache'));
@ -181,7 +170,7 @@ contextMenus = {
click: browserCommands.openOptions, click: browserCommands.openOptions,
}, },
'reload': { 'reload': {
presentIf: () => localStorage.installType === 'development', presentIf: async () => (await browser.management.getSelf()).installType === 'development',
title: 'reload', title: 'reload',
click: browserCommands.reload, click: browserCommands.reload,
}, },
@ -198,10 +187,10 @@ contextMenus = {
} }
}; };
function createContextMenus(ids) { async function createContextMenus(ids) {
for (const id of ids) { for (const id of ids) {
let item = contextMenus[id]; let item = contextMenus[id];
if (item.presentIf && !item.presentIf()) { if (item.presentIf && !await item.presentIf()) {
continue; continue;
} }
item = Object.assign({id}, item); item = Object.assign({id}, item);

View File

@ -24,14 +24,11 @@ const db = (() => {
async function tryUsingIndexedDB() { async function tryUsingIndexedDB() {
// we use chrome.storage.local fallback if IndexedDB doesn't save data, // 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 // which, once detected on the first run, is remembered in chrome.storage.local
// for reliablility and in localStorage for fast synchronous access // note that accessing indexedDB may throw, https://github.com/openstyles/stylus/issues/615
// (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') { if (typeof indexedDB === 'undefined') {
throw new Error('indexedDB is undefined'); throw new Error('indexedDB is undefined');
} }
switch (await getFallback()) { switch (await chromeLocal.getValue(FALLBACK)) {
case true: throw null; case true: throw null;
case false: break; case false: break;
default: await testDB(); default: await testDB();
@ -39,12 +36,6 @@ const db = (() => {
return useIndexedDB(); return useIndexedDB();
} }
async function getFallback() {
return localStorage[FALLBACK] === 'true' ? true :
localStorage[FALLBACK] === 'false' ? false :
chromeLocal.getValue(FALLBACK);
}
async function testDB() { async function testDB() {
let e = await dbExecIndexedDB('getAllKeys', IDBKeyRange.lowerBound(1), 1); let e = await dbExecIndexedDB('getAllKeys', IDBKeyRange.lowerBound(1), 1);
// throws if result is null // throws if result is null
@ -62,13 +53,11 @@ const db = (() => {
chromeLocal.setValue(FALLBACK + 'Reason', workerUtil.cloneError(err)); chromeLocal.setValue(FALLBACK + 'Reason', workerUtil.cloneError(err));
console.warn('Failed to access indexedDB. Switched to storage API.', err); console.warn('Failed to access indexedDB. Switched to storage API.', err);
} }
localStorage[FALLBACK] = 'true';
return createChromeStorageDB().exec; return createChromeStorageDB().exec;
} }
function useIndexedDB() { function useIndexedDB() {
chromeLocal.setValue(FALLBACK, false); chromeLocal.setValue(FALLBACK, false);
localStorage[FALLBACK] = 'false';
return dbExecIndexedDB; return dbExecIndexedDB;
} }

View File

@ -35,7 +35,7 @@
const ALARM_NAME = 'scheduledUpdate'; const ALARM_NAME = 'scheduledUpdate';
const MIN_INTERVAL_MS = 60e3; const MIN_INTERVAL_MS = 60e3;
let lastUpdateTime = parseInt(localStorage.lastUpdateTime) || Date.now(); let lastUpdateTime;
let checkingAll = false; let checkingAll = false;
let logQueue = []; let logQueue = [];
let logLastWriteTime = 0; let logLastWriteTime = 0;
@ -46,9 +46,11 @@
API_METHODS.updateCheck = checkStyle; API_METHODS.updateCheck = checkStyle;
API_METHODS.getUpdaterStates = () => STATES; API_METHODS.getUpdaterStates = () => STATES;
prefs.subscribe(['updateInterval'], schedule); chromeLocal.getValue('lastUpdateTime').then(val => {
schedule(); lastUpdateTime = val || Date.now();
prefs.subscribe('updateInterval', schedule, {now: true});
chrome.alarms.onAlarm.addListener(onAlarm); chrome.alarms.onAlarm.addListener(onAlarm);
});
return {checkAllStyles, checkStyle, STATES}; return {checkAllStyles, checkStyle, STATES};
@ -255,7 +257,7 @@
} }
function resetInterval() { function resetInterval() {
localStorage.lastUpdateTime = lastUpdateTime = Date.now(); chromeLocal.setValue('lastUpdateTime', lastUpdateTime = Date.now());
schedule(); schedule();
} }

View File

@ -22,7 +22,7 @@
prefs prefs
rerouteHotkeys rerouteHotkeys
SectionsEditor SectionsEditor
sessionStorageHash sessionStore
setupLivePrefs setupLivePrefs
SourceEditor SourceEditor
t t
@ -100,7 +100,7 @@ lazyInit();
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded // switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss')); editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
document.documentElement.classList.toggle('usercss', editor.isUsercss); document.documentElement.classList.toggle('usercss', editor.isUsercss);
sessionStorage.justEditedStyleId = style.id || ''; sessionStore.justEditedStyleId = style.id || '';
// no such style so let's clear the invalid URL parameters // no such style so let's clear the invalid URL parameters
if (!style.id) history.replaceState({}, '', location.pathname); if (!style.id) history.replaceState({}, '', location.pathname);
updateTitle(false); updateTitle(false);
@ -335,7 +335,7 @@ function lazyInit() {
async function patchHistoryBack(tab) { async function patchHistoryBack(tab) {
ownTabId = tab.id; ownTabId = tab.id;
// use browser history back when 'back to manage' is clicked // use browser history back when 'back to manage' is clicked
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) { if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
await onDOMready(); await onDOMready();
$('#cancel-button').onclick = event => { $('#cancel-button').onclick = event => {
event.stopPropagation(); event.stopPropagation();
@ -346,8 +346,8 @@ function lazyInit() {
} }
/** resize on 'undo close' */ /** resize on 'undo close' */
function restoreWindowSize() { function restoreWindowSize() {
const pos = tryJSONparse(sessionStorage.windowPos); const pos = tryJSONparse(sessionStore.windowPos);
delete sessionStorage.windowPos; delete sessionStore.windowPos;
if (pos && pos.left != null && chrome.windows) { if (pos && pos.left != null && chrome.windows) {
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos); chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
} }
@ -408,7 +408,7 @@ function onRuntimeMessage(request) {
} }
function beforeUnload(e) { function beforeUnload(e) {
sessionStorage.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition')); sessionStore.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition'));
const activeElement = document.activeElement; const activeElement = document.activeElement;
if (activeElement) { if (activeElement) {
// blurring triggers 'change' or 'input' event if needed // blurring triggers 'change' or 'input' event if needed

View File

@ -15,6 +15,7 @@
messageBox messageBox
prefs prefs
sectionsToMozFormat sectionsToMozFormat
sessionStore
showCodeMirrorPopup showCodeMirrorPopup
showHelp showHelp
t t
@ -117,7 +118,7 @@ function SectionsEditor() {
} }
newStyle = await API.editSave(newStyle); newStyle = await API.editSave(newStyle);
destroyRemovedSections(); destroyRemovedSections();
sessionStorage.justEditedStyleId = newStyle.id; sessionStore.justEditedStyleId = newStyle.id;
editor.replaceStyle(newStyle, false); editor.replaceStyle(newStyle, false);
}, },

View File

@ -16,6 +16,7 @@
MozSectionWidget MozSectionWidget
prefs prefs
sectionsToMozFormat sectionsToMozFormat
sessionStore
t t
*/ */
@ -217,7 +218,7 @@ function SourceEditor() {
if (style.id !== newStyle.id) { if (style.id !== newStyle.id) {
history.replaceState({}, '', `?id=${newStyle.id}`); history.replaceState({}, '', `?id=${newStyle.id}`);
} }
sessionStorage.justEditedStyleId = newStyle.id; sessionStore.justEditedStyleId = newStyle.id;
Object.assign(style, newStyle); Object.assign(style, newStyle);
$('#preview-label').classList.remove('hidden'); $('#preview-label').classList.remove('hidden');
updateMeta(); updateMeta();

View File

@ -7,14 +7,8 @@ tDocLoader();
function t(key, params) { function t(key, params) {
const cache = !params && t.cache[key]; const s = chrome.i18n.getMessage(key, params);
const s = cache || chrome.i18n.getMessage(key, params); if (!s) throw `Missing string "${key}"`;
if (s === '') {
throw `Missing string "${key}"`;
}
if (!params && !cache) {
t.cache[key] = s;
}
return s; return s;
} }
@ -138,11 +132,6 @@ function tNodeList(nodes) {
function tDocLoader() { function tDocLoader() {
t.DOMParser = new DOMParser(); t.DOMParser = new DOMParser();
t.cache = (() => {
try {
return JSON.parse(localStorage.L10N);
} catch (e) {}
})() || {};
t.RX_WORD_BREAK = new RegExp([ t.RX_WORD_BREAK = new RegExp([
'(', '(',
/[\d\w\u007B-\uFFFF]{10}/, /[\d\w\u007B-\uFFFF]{10}/,
@ -154,14 +143,6 @@ function tDocLoader() {
/(?!\b|\s|$)/, /(?!\b|\s|$)/,
].map(rx => rx.source || rx).join(''), 'g'); ].map(rx => rx.source || rx).join(''), 'g');
// reset L10N cache on UI language change
const UIlang = chrome.i18n.getUILanguage();
if (t.cache.browserUIlanguage !== UIlang) {
t.cache = {browserUIlanguage: UIlang};
localStorage.L10N = JSON.stringify(t.cache);
}
const cacheLength = Object.keys(t.cache).length;
Object.assign(tDocLoader, { Object.assign(tDocLoader, {
observer: new MutationObserver(process), observer: new MutationObserver(process),
start() { start() {
@ -197,9 +178,6 @@ function tDocLoader() {
document.removeEventListener('DOMContentLoaded', onLoad); document.removeEventListener('DOMContentLoaded', onLoad);
process(tDocLoader.observer.takeRecords()); process(tDocLoader.observer.takeRecords());
tDocLoader.stop(); tDocLoader.stop();
if (cacheLength !== Object.keys(t.cache).length) {
localStorage.L10N = JSON.stringify(t.cache);
}
} }
} }

View File

@ -1,6 +1,20 @@
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError /* exported
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual capitalize
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */ CHROME_HAS_BORDER_BUG
closeCurrentTab
deepEqual
download
getActiveTab
getStyleWithNoCode
getTab
ignoreChromeError
onTabReady
openURL
sessionStore
stringAsRegExp
tryCatch
tryRegExp
*/
'use strict'; 'use strict';
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]); const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]);
@ -316,24 +330,28 @@ function deepEqual(a, b, ignoredKeys) {
return true; return true;
} }
/* A simple polyfill in case DOM storage is disabled in the browser */
function sessionStorageHash(name) { const sessionStore = new Proxy({}, {
return { get(target, name) {
name, try {
value: tryCatch(JSON.parse, sessionStorage[name]) || {}, return sessionStorage[name];
set(k, v) { } catch (e) {
this.value[k] = v; Object.defineProperty(window, 'sessionStorage', {value: target});
this.updateStorage();
},
unset(k) {
delete this.value[k];
this.updateStorage();
},
updateStorage() {
sessionStorage[this.name] = JSON.stringify(this.value);
} }
}; },
set(target, name, value, proxy) {
try {
sessionStorage[name] = `${value}`;
} catch (e) {
proxy[name]; // eslint-disable-line no-unused-expressions
target[name] = `${value}`;
} }
return true;
},
deleteProperty(target, name) {
return delete target[name];
},
});
/** /**
* @param {String} url * @param {String} url

View File

@ -66,15 +66,6 @@ self.INJECTED !== 1 && (() => {
//#region for our extension pages //#region for our extension pages
for (const storage of ['localStorage', 'sessionStorage']) {
try {
window[storage]._access_check = 1;
delete window[storage]._access_check;
} catch (err) {
Object.defineProperty(window, storage, {value: {}});
}
}
if (!(new URLSearchParams({foo: 1})).get('foo')) { if (!(new URLSearchParams({foo: 1})).get('foo')) {
// TODO: remove when minimum_chrome_version >= 61 // TODO: remove when minimum_chrome_version >= 61
window.URLSearchParams = class extends URLSearchParams { window.URLSearchParams = class extends URLSearchParams {

View File

@ -6,7 +6,7 @@ global messageBox getStyleWithNoCode
configDialog configDialog
sorter msg prefs API $ $$ $create template setupLivePrefs sorter msg prefs API $ $$ $create template setupLivePrefs
t tWordBreak formatDate t tWordBreak formatDate
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce getOwnTab getActiveTab openURL animateElement sessionStore debounce
scrollElementIntoView CHROME VIVALDI router scrollElementIntoView CHROME VIVALDI router
*/ */
'use strict'; 'use strict';
@ -129,7 +129,7 @@ function showStyles(styles = [], matchUrlIds) {
let firstRun = true; let firstRun = true;
installed.dataset.total = styles.length; installed.dataset.total = styles.length;
const scrollY = (history.state || {}).scrollY; const scrollY = (history.state || {}).scrollY;
const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId; const shouldRenderAll = scrollY > window.innerHeight || sessionStore.justEditedStyleId;
const renderBin = document.createDocumentFragment(); const renderBin = document.createDocumentFragment();
if (scrollY) { if (scrollY) {
renderStyles(); renderStyles();
@ -155,7 +155,7 @@ function showStyles(styles = [], matchUrlIds) {
return; return;
} }
setTimeout(getFaviconImgSrc); setTimeout(getFaviconImgSrc);
if (sessionStorage.justEditedStyleId) { if (sessionStore.justEditedStyleId) {
highlightEditedStyle(); highlightEditedStyle();
} else if ('scrollY' in (history.state || {})) { } else if ('scrollY' in (history.state || {})) {
setTimeout(window.scrollTo, 0, 0, history.state.scrollY); setTimeout(window.scrollTo, 0, 0, history.state.scrollY);
@ -400,7 +400,7 @@ Object.assign(handleEvent, {
} else { } else {
onVisibilityChange(); onVisibilityChange();
getActiveTab().then(tab => { getActiveTab().then(tab => {
sessionStorageHash('manageStylesHistory').set(tab.id, url); sessionStore['manageStylesHistory' + tab.id] = url;
location.href = url; location.href = url;
}); });
} }
@ -691,10 +691,10 @@ function onVisibilityChange() {
// the catch here is that DOM may be outdated so we'll at least refresh the just edited style // the catch here is that DOM may be outdated so we'll at least refresh the just edited style
// assuming other changes aren't important enough to justify making a complicated DOM sync // assuming other changes aren't important enough to justify making a complicated DOM sync
case 'visible': { case 'visible': {
const id = sessionStorage.justEditedStyleId; const id = sessionStore.justEditedStyleId;
if (id) { if (id) {
handleUpdateForId(Number(id), {method: 'styleUpdated'}); handleUpdateForId(Number(id), {method: 'styleUpdated'});
delete sessionStorage.justEditedStyleId; delete sessionStore.justEditedStyleId;
} }
break; break;
} }
@ -707,9 +707,9 @@ function onVisibilityChange() {
function highlightEditedStyle() { function highlightEditedStyle() {
if (!sessionStorage.justEditedStyleId) return; if (!sessionStore.justEditedStyleId) return;
const entry = $(ENTRY_ID_PREFIX + sessionStorage.justEditedStyleId); const entry = $(ENTRY_ID_PREFIX + sessionStore.justEditedStyleId);
delete sessionStorage.justEditedStyleId; delete sessionStore.justEditedStyleId;
if (entry) { if (entry) {
animateElement(entry); animateElement(entry);
requestAnimationFrame(() => scrollElementIntoView(entry)); requestAnimationFrame(() => scrollElementIntoView(entry));