extract and improve dummy chrome.storage in FF
* chrome.storage.onChanged supported in own pages * values are stored in the background page * chrome.storage in own pages accesses that background storage
This commit is contained in:
parent
bf17c7de94
commit
71c3e0c7a8
|
@ -8,8 +8,7 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
var API_METHODS = {
|
|
||||||
|
|
||||||
getStyles,
|
getStyles,
|
||||||
saveStyle,
|
saveStyle,
|
||||||
|
@ -32,7 +31,7 @@ var API_METHODS = {
|
||||||
});
|
});
|
||||||
return KEEP_CHANNEL_OPEN;
|
return KEEP_CHANNEL_OPEN;
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var browserCommands, contextMenus;
|
var browserCommands, contextMenus;
|
||||||
|
|
78
background/storage-dummy.js
Normal file
78
background/storage-dummy.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
(chrome.runtime.id.includes('@temporary') || !('sync' in chrome.storage)) && (() => {
|
||||||
|
|
||||||
|
const listeners = new Set();
|
||||||
|
Object.assign(chrome.storage.onChanged, {
|
||||||
|
addListener: fn => listeners.add(fn),
|
||||||
|
hasListener: fn => listeners.has(fn),
|
||||||
|
removeListener: fn => listeners.delete(fn),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const name of ['local', 'sync']) {
|
||||||
|
const dummy = tryJSONparse(localStorage['dummyStorage.' + name]) || {};
|
||||||
|
chrome.storage[name] = {
|
||||||
|
get(data, cb) {
|
||||||
|
let result = {};
|
||||||
|
if (data === null) {
|
||||||
|
result = deepCopy(dummy);
|
||||||
|
} else if (Array.isArray(data)) {
|
||||||
|
for (const key of data) {
|
||||||
|
result[key] = dummy[key];
|
||||||
|
}
|
||||||
|
} else if (typeof data === 'object') {
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
for (const key in data) {
|
||||||
|
if (hasOwnProperty.call(data, key)) {
|
||||||
|
const value = dummy[key];
|
||||||
|
result[key] = value === undefined ? data[key] : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[data] = dummy[data];
|
||||||
|
}
|
||||||
|
if (typeof cb === 'function') cb(result);
|
||||||
|
},
|
||||||
|
set(data, cb) {
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
const changes = {};
|
||||||
|
for (const key in data) {
|
||||||
|
if (!hasOwnProperty.call(data, key)) continue;
|
||||||
|
const newValue = data[key];
|
||||||
|
changes[key] = {newValue, oldValue: dummy[key]};
|
||||||
|
dummy[key] = newValue;
|
||||||
|
}
|
||||||
|
localStorage['dummyStorage.' + name] = JSON.stringify(dummy);
|
||||||
|
if (typeof cb === 'function') cb();
|
||||||
|
notify(changes);
|
||||||
|
},
|
||||||
|
remove(keyOrKeys, cb) {
|
||||||
|
const changes = {};
|
||||||
|
for (const key of Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys]) {
|
||||||
|
changes[key] = {oldValue: dummy[key]};
|
||||||
|
delete dummy[key];
|
||||||
|
}
|
||||||
|
localStorage['dummyStorage.' + name] = JSON.stringify(dummy);
|
||||||
|
if (typeof cb === 'function') cb();
|
||||||
|
notify(changes);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
|
dummyStorageGet: ({data, name}) => new Promise(resolve => chrome.storage[name].get(data, resolve)),
|
||||||
|
dummyStorageSet: ({data, name}) => new Promise(resolve => chrome.storage[name].set(data, resolve)),
|
||||||
|
dummyStorageRemove: ({data, name}) => new Promise(resolve => chrome.storage[name].remove(data, resolve)),
|
||||||
|
});
|
||||||
|
|
||||||
|
function notify(changes, name) {
|
||||||
|
for (const fn of listeners.values()) {
|
||||||
|
fn(changes, name);
|
||||||
|
}
|
||||||
|
sendMessage({
|
||||||
|
dummyStorageChanges: changes,
|
||||||
|
dummyStorageName: name,
|
||||||
|
}, ignoreChromeError);
|
||||||
|
}
|
||||||
|
})();
|
27
js/prefs.js
27
js/prefs.js
|
@ -270,7 +270,7 @@ var prefs = new function Prefs() {
|
||||||
if (BG && BG !== window) return;
|
if (BG && BG !== window) return;
|
||||||
if (BG === window) {
|
if (BG === window) {
|
||||||
affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
|
affectsIcon.forEach(key => this.broadcast(key, values[key], {sync: false}));
|
||||||
getSync().get('settings', data => importFromSync.call(this, data.settings));
|
chromeSync.getValue('settings', settings => importFromSync.call(this, settings));
|
||||||
}
|
}
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
if (area === 'sync' && 'settings' in changes) {
|
if (area === 'sync' && 'settings' in changes) {
|
||||||
|
@ -319,30 +319,7 @@ var prefs = new function Prefs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function doSyncSet() {
|
function doSyncSet() {
|
||||||
getSync().set({'settings': values});
|
chromeSync.setValue('settings', values);
|
||||||
}
|
|
||||||
|
|
||||||
// Polyfill for Firefox < 53 https://bugzilla.mozilla.org/show_bug.cgi?id=1220494
|
|
||||||
function getSync() {
|
|
||||||
if ('sync' in chrome.storage && !chrome.runtime.id.includes('@temporary')) {
|
|
||||||
return chrome.storage.sync;
|
|
||||||
}
|
|
||||||
const crappyStorage = {};
|
|
||||||
return {
|
|
||||||
get(key, callback) {
|
|
||||||
callback(crappyStorage[key] || {});
|
|
||||||
},
|
|
||||||
set(source, callback) {
|
|
||||||
for (const property in source) {
|
|
||||||
if (source.hasOwnProperty(property)) {
|
|
||||||
crappyStorage[property] = source[property];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function importFromSync(synced = {}) {
|
function importFromSync(synced = {}) {
|
||||||
|
|
|
@ -1,99 +1,85 @@
|
||||||
/* global LZString loadScript */
|
/* global loadScript */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var [chromeLocal, chromeSync] = [
|
var [chromeLocal, chromeSync] = (() => {
|
||||||
chrome.storage.local,
|
const native = 'sync' in chrome.storage &&
|
||||||
chrome.storage.sync,
|
!chrome.runtime.id.includes('@temporary');
|
||||||
].map(storage => {
|
if (!native && BG !== window) {
|
||||||
const wrapper = {
|
setupOnChangeRelay();
|
||||||
get(options) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
storage.get(options, data => resolve(data));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
set(data) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
storage.set(data, () => resolve(data));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
remove(keyOrKeys) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
storage.remove(keyOrKeys, resolve);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getValue(key) {
|
|
||||||
return wrapper.get(key).then(data => data[key]);
|
|
||||||
},
|
|
||||||
setValue(key, value) {
|
|
||||||
return wrapper.set({[key]: value});
|
|
||||||
},
|
|
||||||
loadLZStringScript() {
|
|
||||||
return Promise.resolve(
|
|
||||||
window.LZString ||
|
|
||||||
loadScript('/vendor/lz-string/lz-string-unsafe.js').then(() => {
|
|
||||||
window.LZString = window.LZStringUnsafe;
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
getLZValue(key) {
|
|
||||||
return wrapper.getLZValues([key]).then(data => data[key]);
|
|
||||||
},
|
|
||||||
getLZValues(keys) {
|
|
||||||
return Promise.all([
|
|
||||||
wrapper.get(keys),
|
|
||||||
wrapper.loadLZStringScript(),
|
|
||||||
]).then(([data = {}]) => {
|
|
||||||
for (const key of keys) {
|
|
||||||
const value = data[key];
|
|
||||||
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setLZValue(key, value) {
|
|
||||||
return wrapper.loadLZStringScript().then(() =>
|
|
||||||
wrapper.set({
|
|
||||||
[key]: LZString.compressToUTF16(JSON.stringify(value)),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return wrapper;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function styleSectionsEqual({sections: a}, {sections: b}) {
|
|
||||||
if (!a || !b) {
|
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
if (a.length !== b.length) {
|
return [
|
||||||
return false;
|
createWrapper('local'),
|
||||||
}
|
createWrapper('sync'),
|
||||||
// order of sections should be identical to account for the case of multiple
|
];
|
||||||
// sections matching the same URL because the order of rules is part of cascading
|
|
||||||
return a.every((sectionA, index) => propertiesEqual(sectionA, b[index]));
|
|
||||||
|
|
||||||
function propertiesEqual(secA, secB) {
|
function createWrapper(name) {
|
||||||
for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
|
if (!native) createDummyStorage(name);
|
||||||
if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
|
const storage = chrome.storage[name];
|
||||||
return false;
|
const wrapper = {
|
||||||
|
get: data => new Promise(resolve => storage.get(data, resolve)),
|
||||||
|
set: data => new Promise(resolve => storage.set(data, () => resolve(data))),
|
||||||
|
remove: data => new Promise(resolve => storage.remove(data, resolve)),
|
||||||
|
|
||||||
|
getValue: key => wrapper.get(key).then(data => data[key]),
|
||||||
|
setValue: (key, value) => wrapper.set({[key]: value}),
|
||||||
|
|
||||||
|
getLZValue: key => wrapper.getLZValues([key]).then(data => data[key]),
|
||||||
|
getLZValues: keys =>
|
||||||
|
Promise.all([
|
||||||
|
wrapper.get(keys),
|
||||||
|
loadLZStringScript(),
|
||||||
|
]).then(([data = {}, LZString]) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = data[key];
|
||||||
|
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}),
|
||||||
|
setLZValue: (key, value) =>
|
||||||
|
loadLZStringScript().then(LZString =>
|
||||||
|
wrapper.set({
|
||||||
|
[key]: LZString.compressToUTF16(JSON.stringify(value)),
|
||||||
|
})),
|
||||||
|
|
||||||
|
loadLZStringScript,
|
||||||
|
};
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDummyStorage(name) {
|
||||||
|
chrome.storage[name] = {
|
||||||
|
get: (data, cb) => API.dummyStorageGet({data, name}).then(cb),
|
||||||
|
set: (data, cb) => API.dummyStorageSet({data, name}).then(cb),
|
||||||
|
remove: (data, cb) => API.dummyStorageRemove({data, name}).then(cb),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLZStringScript() {
|
||||||
|
return window.LZString ?
|
||||||
|
Promise.resolve(window.LZString) :
|
||||||
|
loadScript('/vendor/lz-string/lz-string-unsafe.js').then(() =>
|
||||||
|
(window.LZString = window.LZString || window.LZStringUnsafe));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupOnChangeRelay() {
|
||||||
|
const listeners = new Set();
|
||||||
|
const onMessage = msg => {
|
||||||
|
if (!msg.dummyStorageChanges) return;
|
||||||
|
for (const fn of listeners.values()) {
|
||||||
|
fn(msg.dummyStorageChanges, msg.dummyStorageName);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a === b);
|
Object.assign(chrome.storage.onChanged, {
|
||||||
|
addListener(fn) {
|
||||||
|
if (!listeners.size) chrome.runtime.onMessage.addListener(onMessage);
|
||||||
|
listeners.add(fn);
|
||||||
|
},
|
||||||
|
hasListener: fn => listeners.has(fn),
|
||||||
|
removeListener(fn) {
|
||||||
|
listeners.delete(fn);
|
||||||
|
if (!listeners.size) chrome.runtime.onMessage.removeListener(onMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
function equalOrEmpty(a, b, telltale, comparator) {
|
|
||||||
const typeA = a && typeof a[telltale] === 'function';
|
|
||||||
const typeB = b && typeof b[telltale] === 'function';
|
|
||||||
return (
|
|
||||||
(a === null || a === undefined || (typeA && !a.length)) &&
|
|
||||||
(b === null || b === undefined || (typeB && !b.length))
|
|
||||||
) || typeA && typeB && a.length === b.length && comparator(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayMirrors(array1, array2) {
|
|
||||||
return (
|
|
||||||
array1.every(el => array2.includes(el)) &&
|
|
||||||
array2.every(el => array1.includes(el))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"js/messaging.js",
|
"js/messaging.js",
|
||||||
"js/storage-util.js",
|
"js/storage-util.js",
|
||||||
"js/sections-equal.js",
|
"js/sections-equal.js",
|
||||||
|
"background/storage-dummy.js",
|
||||||
"background/storage.js",
|
"background/storage.js",
|
||||||
"js/prefs.js",
|
"js/prefs.js",
|
||||||
"js/script-loader.js",
|
"js/script-loader.js",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user