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}) => {
// 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;
// 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) {
// Removing unused stuff
// TODO: delete this entire block by the middle of 2021
try {
localStorage.clear();
} catch (e) {}
setTimeout(async () => {
const del = Object.keys(await chromeLocal.get())
.filter(key => key.startsWith('usoSearchCache'));
@ -181,7 +170,7 @@ contextMenus = {
click: browserCommands.openOptions,
},
'reload': {
presentIf: () => localStorage.installType === 'development',
presentIf: async () => (await browser.management.getSelf()).installType === 'development',
title: 'reload',
click: browserCommands.reload,
},
@ -198,10 +187,10 @@ contextMenus = {
}
};
function createContextMenus(ids) {
async function createContextMenus(ids) {
for (const id of ids) {
let item = contextMenus[id];
if (item.presentIf && !item.presentIf()) {
if (item.presentIf && !await item.presentIf()) {
continue;
}
item = Object.assign({id}, item);

View File

@ -24,14 +24,11 @@ const db = (() => {
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
// note that accessing indexedDB may throw, https://github.com/openstyles/stylus/issues/615
if (typeof indexedDB === 'undefined') {
throw new Error('indexedDB is undefined');
}
switch (await getFallback()) {
switch (await chromeLocal.getValue(FALLBACK)) {
case true: throw null;
case false: break;
default: await testDB();
@ -39,12 +36,6 @@ const db = (() => {
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
@ -62,13 +53,11 @@ const db = (() => {
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;
}

View File

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

View File

@ -22,7 +22,7 @@
prefs
rerouteHotkeys
SectionsEditor
sessionStorageHash
sessionStore
setupLivePrefs
SourceEditor
t
@ -100,7 +100,7 @@ lazyInit();
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
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
if (!style.id) history.replaceState({}, '', location.pathname);
updateTitle(false);
@ -335,7 +335,7 @@ function lazyInit() {
async function patchHistoryBack(tab) {
ownTabId = tab.id;
// 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();
$('#cancel-button').onclick = event => {
event.stopPropagation();
@ -346,8 +346,8 @@ function lazyInit() {
}
/** resize on 'undo close' */
function restoreWindowSize() {
const pos = tryJSONparse(sessionStorage.windowPos);
delete sessionStorage.windowPos;
const pos = tryJSONparse(sessionStore.windowPos);
delete sessionStore.windowPos;
if (pos && pos.left != null && chrome.windows) {
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
}
@ -408,7 +408,7 @@ function onRuntimeMessage(request) {
}
function beforeUnload(e) {
sessionStorage.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition'));
sessionStore.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition'));
const activeElement = document.activeElement;
if (activeElement) {
// blurring triggers 'change' or 'input' event if needed

View File

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

View File

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

View File

@ -7,14 +7,8 @@ tDocLoader();
function t(key, params) {
const cache = !params && t.cache[key];
const s = cache || chrome.i18n.getMessage(key, params);
if (s === '') {
throw `Missing string "${key}"`;
}
if (!params && !cache) {
t.cache[key] = s;
}
const s = chrome.i18n.getMessage(key, params);
if (!s) throw `Missing string "${key}"`;
return s;
}
@ -138,11 +132,6 @@ function tNodeList(nodes) {
function tDocLoader() {
t.DOMParser = new DOMParser();
t.cache = (() => {
try {
return JSON.parse(localStorage.L10N);
} catch (e) {}
})() || {};
t.RX_WORD_BREAK = new RegExp([
'(',
/[\d\w\u007B-\uFFFF]{10}/,
@ -154,14 +143,6 @@ function tDocLoader() {
/(?!\b|\s|$)/,
].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, {
observer: new MutationObserver(process),
start() {
@ -197,9 +178,6 @@ function tDocLoader() {
document.removeEventListener('DOMContentLoaded', onLoad);
process(tDocLoader.observer.takeRecords());
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
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
/* exported
capitalize
CHROME_HAS_BORDER_BUG
closeCurrentTab
deepEqual
download
getActiveTab
getStyleWithNoCode
getTab
ignoreChromeError
onTabReady
openURL
sessionStore
stringAsRegExp
tryCatch
tryRegExp
*/
'use strict';
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]);
@ -316,24 +330,28 @@ function deepEqual(a, b, ignoredKeys) {
return true;
}
function sessionStorageHash(name) {
return {
name,
value: tryCatch(JSON.parse, sessionStorage[name]) || {},
set(k, v) {
this.value[k] = v;
this.updateStorage();
},
unset(k) {
delete this.value[k];
this.updateStorage();
},
updateStorage() {
sessionStorage[this.name] = JSON.stringify(this.value);
/* A simple polyfill in case DOM storage is disabled in the browser */
const sessionStore = new Proxy({}, {
get(target, name) {
try {
return sessionStorage[name];
} catch (e) {
Object.defineProperty(window, 'sessionStorage', {value: target});
}
};
}
},
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

View File

@ -66,15 +66,6 @@ self.INJECTED !== 1 && (() => {
//#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')) {
// TODO: remove when minimum_chrome_version >= 61
window.URLSearchParams = class extends URLSearchParams {

View File

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