fallback to chrome.storage when IndexedDB is dysfunctional

This commit is contained in:
tophf 2017-09-02 18:36:32 +03:00
parent ea8eaf3146
commit 53aa239da3
7 changed files with 123 additions and 61 deletions

View File

@ -140,7 +140,7 @@
"description": "Label for the style editor's CSS theme." "description": "Label for the style editor's CSS theme."
}, },
"dysfunctional": { "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" "description": "Displayed in Firefox when its settings make Stylus dysfunctional"
}, },
"dysfunctionalBackgroundConnection": { "dysfunctionalBackgroundConnection": {

View File

@ -5,10 +5,6 @@
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var browserCommands, contextMenus; var browserCommands, contextMenus;
// *************************************************************************
// preload the DB
tryCatch(getStyles);
// ************************************************************************* // *************************************************************************
// register all listeners // register all listeners
chrome.runtime.onMessage.addListener(onRuntimeMessage); chrome.runtime.onMessage.addListener(onRuntimeMessage);

View File

@ -40,6 +40,11 @@ var chromeLocal = {
chrome.storage.local.set(data, () => resolve(data)); chrome.storage.local.set(data, () => resolve(data));
}); });
}, },
remove(keyOrKeys) {
return new Promise(resolve => {
chrome.storage.local.remove(keyOrKeys, resolve);
});
},
getValue(key) { getValue(key) {
return chromeLocal.get(key).then(data => data[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) => { return new Promise((resolve, reject) => {
Object.assign(indexedDB.open('stylish', 2), { Object.assign(indexedDB.open('stylish', 2), {
onsuccess(event) { onsuccess(event) {
@ -88,7 +139,7 @@ function dbExec(method, data) {
} else { } else {
const transaction = database.transaction(['styles'], 'readwrite'); const transaction = database.transaction(['styles'], 'readwrite');
const store = transaction.objectStore('styles'); const store = transaction.objectStore('styles');
Object.assign(store[method](data), { Object.assign(store[method](...args), {
onsuccess: event => resolve(event, store, transaction, database), onsuccess: event => resolve(event, store, transaction, database),
onerror: reject, 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) { function getStyles(options) {
if (cachedStyles.list) { if (cachedStyles.list) {
return Promise.resolve(filterStyles(options)); return Promise.resolve(filterStyles(options));

View File

@ -38,8 +38,18 @@ for (const type of [NodeList, NamedNodeMap, HTMLCollection, HTMLAllCollection])
// add favicon in Firefox // add favicon in Firefox
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
navigator.userAgent.includes('Firefox') && setTimeout(() => { if (navigator.userAgent.includes('Firefox')) {
dieOnDysfunction(); 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')] || ''; const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) { for (const size of [38, 32, 19, 16]) {
document.head.appendChild($element({ document.head.appendChild($element({
@ -49,9 +59,10 @@ navigator.userAgent.includes('Firefox') && setTimeout(() => {
sizes: size + 'x' + size, sizes: size + 'x' + size,
})); }));
} }
});
// set hyphenation language // set hyphenation language
document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage()); document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage());
}); }
function onDOMready() { function onDOMready() {
@ -259,36 +270,3 @@ function $element(opt) {
} }
return element; 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());
}
});
}
});
}

View File

@ -555,7 +555,10 @@ function dieOnNullBackground() {
title: 'Stylus', title: 'Stylus',
className: 'danger center', className: 'danger center',
contents: t('dysfunctionalBackgroundConnection'), 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'; document.documentElement.style.pointerEvents = 'none';
}); });

View File

@ -1,6 +1,6 @@
html { html {
height: 100vh; height: 100vh;
min-height: 450px; min-height: 12em;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -16,7 +16,7 @@ html {
body { body {
margin: 2em; margin: 2em;
color: white; color: white;
max-width: 600px; max-width: 20em;
} }
div { div {

View File

@ -1,8 +1,3 @@
'use strict'; 'use strict';
document.body.textContent = document.body.textContent = chrome.i18n.getMessage('dysfunctional');
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());