Enhance: make prefs use storage.sync
This commit is contained in:
parent
282bdf7706
commit
874a2da33e
|
@ -312,6 +312,21 @@ window.addEventListener('storageReady', function _() {
|
||||||
updateAPI(null, prefs.get('exposeIframes'));
|
updateAPI(null, prefs.get('exposeIframes'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// register hotkeys
|
||||||
|
if (FIREFOX && browser.commands && browser.commands.update) {
|
||||||
|
const hotkeyPrefs = Object.keys(prefs.defaults).filter(k => k.startsWith('hotkey.'));
|
||||||
|
this.subscribe(hotkeyPrefs, (name, value) => {
|
||||||
|
try {
|
||||||
|
name = name.split('.')[1];
|
||||||
|
if (value.trim()) {
|
||||||
|
browser.commands.update({name, shortcut: value}).catch(ignoreChromeError);
|
||||||
|
} else {
|
||||||
|
browser.commands.reset(name).catch(ignoreChromeError);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
|
|
||||||
function webNavigationListener(method, {url, tabId, frameId}) {
|
function webNavigationListener(method, {url, tabId, frameId}) {
|
||||||
|
|
241
js/prefs.js
241
js/prefs.js
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var prefs = new function Prefs() {
|
var prefs = (() => {
|
||||||
const defaults = {
|
const defaults = {
|
||||||
'openEditInWindow': false, // new editor opens in a own browser window
|
'openEditInWindow': false, // new editor opens in a own browser window
|
||||||
'windowPosition': {}, // detached window position
|
'windowPosition': {}, // detached window position
|
||||||
|
@ -98,28 +98,27 @@ var prefs = new function Prefs() {
|
||||||
};
|
};
|
||||||
const values = deepCopy(defaults);
|
const values = deepCopy(defaults);
|
||||||
|
|
||||||
const affectsIcon = [
|
|
||||||
'show-badge',
|
|
||||||
'disableAll',
|
|
||||||
'badgeDisabled',
|
|
||||||
'badgeNormal',
|
|
||||||
'iconset',
|
|
||||||
];
|
|
||||||
|
|
||||||
const onChange = {
|
const onChange = {
|
||||||
any: new Set(),
|
any: new Set(),
|
||||||
specific: new Map(),
|
specific: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// coalesce multiple pref changes in broadcast
|
const initializing = promisify(chrome.storage.sync.get.bind(chrome.storage.sync))('settings')
|
||||||
let broadcastPrefs = {};
|
.then(result => setAll(result.settings, true));
|
||||||
|
|
||||||
Object.defineProperties(this, {
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
defaults: {value: deepCopy(defaults)}
|
if (area !== 'sync' || !changes.settings || !changes.settings.newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initializing.then(() => setAll(changes.settings.newValue, true));
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(Prefs.prototype, {
|
// coalesce multiple pref changes in broadcast
|
||||||
|
// let changes = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
initializing,
|
||||||
|
defaults,
|
||||||
get(key, defaultValue) {
|
get(key, defaultValue) {
|
||||||
if (key in values) {
|
if (key in values) {
|
||||||
return values[key];
|
return values[key];
|
||||||
|
@ -132,61 +131,11 @@ var prefs = new function Prefs() {
|
||||||
}
|
}
|
||||||
console.warn("No default preference for '%s'", key);
|
console.warn("No default preference for '%s'", key);
|
||||||
},
|
},
|
||||||
|
|
||||||
getAll() {
|
getAll() {
|
||||||
return deepCopy(values);
|
return deepCopy(values);
|
||||||
},
|
},
|
||||||
|
set,
|
||||||
set(key, value, {broadcast = true, sync = true, fromBroadcast} = {}) {
|
reset: key => set(key, deepCopy(defaults[key])),
|
||||||
const oldValue = values[key];
|
|
||||||
switch (typeof defaults[key]) {
|
|
||||||
case typeof value:
|
|
||||||
break;
|
|
||||||
case 'string':
|
|
||||||
value = String(value);
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
value |= 0;
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
value = value === true || value === 'true';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
values[key] = value;
|
|
||||||
const hasChanged = !equal(value, oldValue);
|
|
||||||
if (!fromBroadcast || FIREFOX_NO_DOM_STORAGE) {
|
|
||||||
localStorage[key] = typeof defaults[key] === 'object'
|
|
||||||
? JSON.stringify(value)
|
|
||||||
: value;
|
|
||||||
}
|
|
||||||
if (!fromBroadcast && broadcast && hasChanged) {
|
|
||||||
this.broadcast(key, value, {sync});
|
|
||||||
}
|
|
||||||
if (hasChanged) {
|
|
||||||
const specific = onChange.specific.get(key);
|
|
||||||
if (typeof specific === 'function') {
|
|
||||||
specific(key, value);
|
|
||||||
} else if (specific instanceof Set) {
|
|
||||||
for (const listener of specific.values()) {
|
|
||||||
listener(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const listener of onChange.any.values()) {
|
|
||||||
listener(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
reset: key => this.set(key, deepCopy(defaults[key])),
|
|
||||||
|
|
||||||
broadcast(key, value, {sync = true} = {}) {
|
|
||||||
broadcastPrefs[key] = value;
|
|
||||||
debounce(doBroadcast);
|
|
||||||
if (sync) {
|
|
||||||
debounce(doSyncSet);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
subscribe(keys, listener) {
|
subscribe(keys, listener) {
|
||||||
// keys: string[] ids
|
// keys: string[] ids
|
||||||
// or a falsy value to subscribe to everything
|
// or a falsy value to subscribe to everything
|
||||||
|
@ -206,7 +155,6 @@ var prefs = new function Prefs() {
|
||||||
onChange.any.add(listener);
|
onChange.any.add(listener);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
unsubscribe(keys, listener) {
|
unsubscribe(keys, listener) {
|
||||||
if (keys) {
|
if (keys) {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
|
@ -224,134 +172,75 @@ var prefs = new function Prefs() {
|
||||||
onChange.all.remove(listener);
|
onChange.all.remove(listener);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
{
|
function promisify(fn) {
|
||||||
const importFromBG = () =>
|
return (...args) =>
|
||||||
API.getPrefs().then(prefs => {
|
new Promise((resolve, reject) => {
|
||||||
for (const id in prefs) {
|
fn(...args, (...result) => {
|
||||||
this.set(id, prefs[id], {fromBroadcast: true});
|
if (chrome.runtime.lastError) {
|
||||||
|
reject(chrome.runtime.lastError);
|
||||||
|
} else if (result.length === 0) {
|
||||||
|
resolve(undefined);
|
||||||
|
} else if (result.length === 1) {
|
||||||
|
resolve(result[0]);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Unlike chrome.storage or messaging, HTML5 localStorage is synchronous and always ready,
|
});
|
||||||
// so we'll mirror the prefs to avoid using the wrong defaults during the startup phase
|
}
|
||||||
const importFromLocalStorage = () => {
|
|
||||||
forgetOutdatedDefaults(localStorage);
|
function setAll(settings, synced) {
|
||||||
for (const key in defaults) {
|
for (const [key, value] of Object.entries(settings)) {
|
||||||
const defaultValue = defaults[key];
|
set(key, value, synced);
|
||||||
let value = localStorage[key];
|
}
|
||||||
if (typeof value === 'string') {
|
}
|
||||||
switch (typeof defaultValue) {
|
|
||||||
case 'boolean':
|
function set(key, value, synced = false) {
|
||||||
value = value.toLowerCase() === 'true';
|
const oldValue = values[key];
|
||||||
|
switch (typeof defaults[key]) {
|
||||||
|
case typeof value:
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
value = String(value);
|
||||||
break;
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
value |= 0;
|
value |= 0;
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'boolean':
|
||||||
value = tryJSONparse(value) || defaultValue;
|
value = value === true || value === 'true';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (FIREFOX_NO_DOM_STORAGE && BG) {
|
if (equal(value, oldValue)) {
|
||||||
value = BG.localStorage[key];
|
return;
|
||||||
value = value === undefined ? defaultValue : value;
|
|
||||||
localStorage[key] = value;
|
|
||||||
} else {
|
|
||||||
value = defaultValue;
|
|
||||||
}
|
}
|
||||||
if (BG === window) {
|
|
||||||
// when in bg page, .set() will write to localStorage
|
|
||||||
this.set(key, value, {broadcast: false, sync: false});
|
|
||||||
} else {
|
|
||||||
values[key] = value;
|
values[key] = value;
|
||||||
}
|
emitChange(key, value);
|
||||||
}
|
if (synced) {
|
||||||
return Promise.resolve();
|
|
||||||
};
|
|
||||||
(FIREFOX_NO_DOM_STORAGE && !BG ? importFromBG() : importFromLocalStorage()).then(() => {
|
|
||||||
if (BG && BG !== window) return;
|
|
||||||
if (BG === window) {
|
|
||||||
affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
|
|
||||||
chromeSync.getValue('settings').then(settings => importFromSync.call(this, settings));
|
|
||||||
}
|
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
|
||||||
if (area === 'sync' && 'settings' in changes) {
|
|
||||||
importFromSync.call(this, changes.settings.newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// any access to chrome API takes time due to initialization of bindings
|
|
||||||
window.addEventListener('load', function _() {
|
|
||||||
window.removeEventListener('load', _);
|
|
||||||
chrome.runtime.onMessage.addListener(msg => {
|
|
||||||
if (msg.prefs) {
|
|
||||||
for (const id in msg.prefs) {
|
|
||||||
prefs.set(id, msg.prefs[id], {fromBroadcast: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// register hotkeys
|
|
||||||
if (FIREFOX && (browser.commands || {}).update) {
|
|
||||||
const hotkeyPrefs = Object.keys(values).filter(k => k.startsWith('hotkey.'));
|
|
||||||
this.subscribe(hotkeyPrefs, (name, value) => {
|
|
||||||
try {
|
|
||||||
name = name.split('.')[1];
|
|
||||||
if (value.trim()) {
|
|
||||||
browser.commands.update({name, shortcut: value}).catch(ignoreChromeError);
|
|
||||||
} else {
|
|
||||||
browser.commands.reset(name).catch(ignoreChromeError);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
function doBroadcast() {
|
|
||||||
if (BG && BG === window && !BG.dbExec.initialized) {
|
|
||||||
window.addEventListener('storageReady', function _() {
|
|
||||||
window.removeEventListener('storageReady', _);
|
|
||||||
doBroadcast();
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const affects = {
|
// changes[key] = value;
|
||||||
all: 'disableAll' in broadcastPrefs
|
debounce(syncPrefs);
|
||||||
|| 'exposeIframes' in broadcastPrefs,
|
|
||||||
};
|
|
||||||
if (!affects.all) {
|
|
||||||
for (const key in broadcastPrefs) {
|
|
||||||
affects.icon = affects.icon || affectsIcon.includes(key);
|
|
||||||
affects.popup = affects.popup || key.startsWith('popup');
|
|
||||||
affects.editor = affects.editor || key.startsWith('editor');
|
|
||||||
affects.manager = affects.manager || key.startsWith('manage');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
notifyAllTabs({method: 'prefChanged', prefs: broadcastPrefs, affects});
|
|
||||||
broadcastPrefs = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function doSyncSet() {
|
function emitChange(key, value) {
|
||||||
chromeSync.setValue('settings', values);
|
const specific = onChange.specific.get(key);
|
||||||
|
if (typeof specific === 'function') {
|
||||||
|
specific(key, value);
|
||||||
|
} else if (specific instanceof Set) {
|
||||||
|
for (const listener of specific.values()) {
|
||||||
|
listener(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function importFromSync(synced = {}) {
|
|
||||||
forgetOutdatedDefaults(synced);
|
|
||||||
for (const key in defaults) {
|
|
||||||
if (key in synced) {
|
|
||||||
this.set(key, synced[key], {sync: false});
|
|
||||||
}
|
}
|
||||||
|
for (const listener of onChange.any.values()) {
|
||||||
|
listener(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function forgetOutdatedDefaults(storage) {
|
function syncPrefs() {
|
||||||
// our linter runs as a worker so we can reduce the delay and forget the old default values
|
// FIXME: we always set the entire object? Ideally, this should only use `changes`.
|
||||||
if (Number(storage['editor.lintDelay']) === 500) delete storage['editor.lintDelay'];
|
chrome.sync.set('settings', values);
|
||||||
if (Number(storage['editor.lintReportDelay']) === 4500) delete storage['editor.lintReportDelay'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function equal(a, b) {
|
function equal(a, b) {
|
||||||
|
@ -383,7 +272,7 @@ var prefs = new function Prefs() {
|
||||||
!Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
|
!Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}();
|
})();
|
||||||
|
|
||||||
|
|
||||||
// Accepts an array of pref names (values are fetched via prefs.get)
|
// Accepts an array of pref names (values are fetched via prefs.get)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user