fallback to chrome.storage when IndexedDB is dysfunctional
This commit is contained in:
parent
ea8eaf3146
commit
53aa239da3
|
@ -140,7 +140,7 @@
|
|||
"description": "Label for the style editor's CSS theme."
|
||||
},
|
||||
"dysfunctional": {
|
||||
"message": "Stylus cannot function because Firefox is either in private mode or is applying its website cookies policy to IndexedDB storage used by Stylus, which erroneously marks the secure moz-extension:// origin as insecure even though WebExtensions aren't websites and Stylus doesn't use cookies.\n\n1. Open Firefox options\n2. Go to 'Privacy & Security'\n3. Set 'History' mode to 'Use custom settings'\n4. Click 'Exceptions'\n5. Paste our manifest URL and click 'Allow'\n6. Click 'Save settings'\n7. Uncheck 'Always use private browsing mode'\n\nThe actual manifest URL is shown below.\nYou can also find it on about:debugging page.",
|
||||
"message": "Stylus cannot function in private windows because Firefox disallows direct connection to the internal background page context of the extension.",
|
||||
"description": "Displayed in Firefox when its settings make Stylus dysfunctional"
|
||||
},
|
||||
"dysfunctionalBackgroundConnection": {
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
// eslint-disable-next-line no-var
|
||||
var browserCommands, contextMenus;
|
||||
|
||||
// *************************************************************************
|
||||
// preload the DB
|
||||
tryCatch(getStyles);
|
||||
|
||||
// *************************************************************************
|
||||
// register all listeners
|
||||
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
||||
|
|
|
@ -40,6 +40,11 @@ var chromeLocal = {
|
|||
chrome.storage.local.set(data, () => resolve(data));
|
||||
});
|
||||
},
|
||||
remove(keyOrKeys) {
|
||||
return new Promise(resolve => {
|
||||
chrome.storage.local.remove(keyOrKeys, resolve);
|
||||
});
|
||||
},
|
||||
getValue(key) {
|
||||
return chromeLocal.get(key).then(data => data[key]);
|
||||
},
|
||||
|
@ -77,8 +82,54 @@ var chromeSync = {
|
|||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var dbExec = dbExecIndexedDB;
|
||||
|
||||
function dbExec(method, 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
|
||||
// for reliablility and in localStorage for fast synchronous access
|
||||
// (FF may block localStorage depending on its privacy options)
|
||||
do {
|
||||
const fallback = () => {
|
||||
dbExec = dbExecChromeStorage;
|
||||
chromeLocal.set({dbInChromeStorage: true});
|
||||
localStorage.dbInChromeStorage = 'true';
|
||||
ignoreChromeError();
|
||||
getStyles();
|
||||
};
|
||||
const fallbackSet = localStorage.dbInChromeStorage;
|
||||
if (fallbackSet === 'true' || !tryCatch(() => indexedDB)) {
|
||||
fallback();
|
||||
break;
|
||||
} else if (fallbackSet === 'false') {
|
||||
getStyles();
|
||||
break;
|
||||
}
|
||||
chromeLocal.get('dbInChromeStorage')
|
||||
.then(data =>
|
||||
data && data.dbInChromeStorage && Promise.reject())
|
||||
.then(() => dbExecIndexedDB('getAllKeys', IDBKeyRange.lowerBound(1), 1))
|
||||
.then(({target}) => (
|
||||
(target.result || [])[0] ?
|
||||
Promise.reject('ok') :
|
||||
dbExecIndexedDB('get', -1)))
|
||||
.then(({target}) => (
|
||||
(target.result || {}).id === -1 ?
|
||||
dbExecIndexedDB('delete', -1).then(() => 'ok') :
|
||||
Promise.reject()))
|
||||
.catch(result => {
|
||||
if (result === 'ok') {
|
||||
chromeLocal.set({dbInChromeStorage: false});
|
||||
localStorage.dbInChromeStorage = 'false';
|
||||
getStyles();
|
||||
} else {
|
||||
fallback();
|
||||
}
|
||||
});
|
||||
} while (0);
|
||||
|
||||
|
||||
function dbExecIndexedDB(method, ...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
Object.assign(indexedDB.open('stylish', 2), {
|
||||
onsuccess(event) {
|
||||
|
@ -88,7 +139,7 @@ function dbExec(method, data) {
|
|||
} else {
|
||||
const transaction = database.transaction(['styles'], 'readwrite');
|
||||
const store = transaction.objectStore('styles');
|
||||
Object.assign(store[method](data), {
|
||||
Object.assign(store[method](...args), {
|
||||
onsuccess: event => resolve(event, store, transaction, database),
|
||||
onerror: reject,
|
||||
});
|
||||
|
@ -111,6 +162,45 @@ function dbExec(method, data) {
|
|||
}
|
||||
|
||||
|
||||
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 getStyles().then(() => {
|
||||
data.id = 1;
|
||||
for (const style of cachedStyles.list) {
|
||||
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 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 {target: {result: styles}};
|
||||
});
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
|
||||
function getStyles(options) {
|
||||
if (cachedStyles.list) {
|
||||
return Promise.resolve(filterStyles(options));
|
||||
|
|
68
js/dom.js
68
js/dom.js
|
@ -38,20 +38,31 @@ for (const type of [NodeList, NamedNodeMap, HTMLCollection, HTMLAllCollection])
|
|||
|
||||
// add favicon in Firefox
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
navigator.userAgent.includes('Firefox') && setTimeout(() => {
|
||||
dieOnDysfunction();
|
||||
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
|
||||
for (const size of [38, 32, 19, 16]) {
|
||||
document.head.appendChild($element({
|
||||
tag: 'link',
|
||||
rel: 'icon',
|
||||
href: `/images/icon/${iconset}${size}.png`,
|
||||
sizes: size + 'x' + size,
|
||||
}));
|
||||
}
|
||||
if (navigator.userAgent.includes('Firefox')) {
|
||||
chrome.windows.getCurrent(wnd => {
|
||||
if (!BG && wnd.incognito) {
|
||||
// private windows can't get bg page
|
||||
location.href = '/msgbox/dysfunctional.html';
|
||||
throw 0;
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (!window.prefs) {
|
||||
return;
|
||||
}
|
||||
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
|
||||
for (const size of [38, 32, 19, 16]) {
|
||||
document.head.appendChild($element({
|
||||
tag: 'link',
|
||||
rel: 'icon',
|
||||
href: `/images/icon/${iconset}${size}.png`,
|
||||
sizes: size + 'x' + size,
|
||||
}));
|
||||
}
|
||||
});
|
||||
// set hyphenation language
|
||||
document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function onDOMready() {
|
||||
|
@ -259,36 +270,3 @@ function $element(opt) {
|
|||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
function dieOnDysfunction() {
|
||||
function die() {
|
||||
location.href = '/msgbox/dysfunctional.html';
|
||||
throw 0;
|
||||
}
|
||||
(() => {
|
||||
try {
|
||||
return indexedDB;
|
||||
} catch (e) {
|
||||
die();
|
||||
}
|
||||
})();
|
||||
Object.assign(indexedDB.open('test'), {
|
||||
onerror: die,
|
||||
onupgradeneeded: indexedDB.deleteDatabase('test'),
|
||||
});
|
||||
// TODO: fallback to sendMessage in FF since private windows can't get bg page
|
||||
chrome.windows.getCurrent(wnd => wnd.incognito && die());
|
||||
// check if privacy settings were fixed but the extension wasn't reloaded,
|
||||
// use setTimeout to auto-cancel if already dead
|
||||
setTimeout(() => {
|
||||
const bg = chrome.extension.getBackgroundPage();
|
||||
if (bg && !(bg.cachedStyles || {}).list) {
|
||||
chrome.storage.local.get('reloaded', data => {
|
||||
if (!data || Date.now() - (data.reloaded || 0) > 10e3) {
|
||||
chrome.storage.local.set({reloaded: Date.now()}, () => chrome.runtime.reload());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -555,7 +555,10 @@ function dieOnNullBackground() {
|
|||
title: 'Stylus',
|
||||
className: 'danger center',
|
||||
contents: t('dysfunctionalBackgroundConnection'),
|
||||
onshow: () => $('#message-box-close-icon').remove(),
|
||||
onshow: () => {
|
||||
$('#message-box-close-icon').remove();
|
||||
window.removeEventListener('keydown', messageBox.listeners.key, true);
|
||||
}
|
||||
});
|
||||
document.documentElement.style.pointerEvents = 'none';
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
html {
|
||||
height: 100vh;
|
||||
min-height: 450px;
|
||||
min-height: 12em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
@ -16,7 +16,7 @@ html {
|
|||
body {
|
||||
margin: 2em;
|
||||
color: white;
|
||||
max-width: 600px;
|
||||
max-width: 20em;
|
||||
}
|
||||
|
||||
div {
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
'use strict';
|
||||
|
||||
document.body.textContent =
|
||||
chrome.i18n.getMessage('dysfunctional');
|
||||
document.body.appendChild(document.createElement('div')).textContent =
|
||||
chrome.runtime.getURL('manifest.json');
|
||||
// set hyphenation language
|
||||
document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage());
|
||||
document.body.textContent = chrome.i18n.getMessage('dysfunctional');
|
||||
|
|
Loading…
Reference in New Issue
Block a user