refactor bg updater; add prefs.subscribe()
This commit is contained in:
parent
c52b8c453f
commit
2e60af40f0
|
@ -112,7 +112,7 @@ rules:
|
||||||
no-case-declarations: [2]
|
no-case-declarations: [2]
|
||||||
no-class-assign: [2]
|
no-class-assign: [2]
|
||||||
no-cond-assign: [2, except-parens]
|
no-cond-assign: [2, except-parens]
|
||||||
no-confusing-arrow: [2, {allowParens: true}]
|
no-confusing-arrow: [1, {allowParens: true}]
|
||||||
no-const-assign: [2]
|
no-const-assign: [2]
|
||||||
no-constant-condition: [0]
|
no-constant-condition: [0]
|
||||||
no-continue: [0]
|
no-continue: [0]
|
||||||
|
|
|
@ -137,17 +137,9 @@ for (const id of Object.keys(contextMenus)) {
|
||||||
chrome.contextMenus.create(item, ignoreChromeError);
|
chrome.contextMenus.create(item, ignoreChromeError);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.defineProperty(contextMenus, 'updateOnPrefChanged', {
|
prefs.subscribe((id, checked) => {
|
||||||
value: changedPrefs => {
|
chrome.contextMenus.update(id, {checked}, ignoreChromeError);
|
||||||
for (const id in changedPrefs) {
|
}, Object.keys(contextMenus));
|
||||||
if (id in contextMenus) {
|
|
||||||
chrome.contextMenus.update(id, {
|
|
||||||
checked: changedPrefs[id],
|
|
||||||
}, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
// [re]inject content scripts
|
// [re]inject content scripts
|
||||||
|
@ -282,10 +274,6 @@ function onRuntimeMessage(request, sender, sendResponse) {
|
||||||
() => sendResponse(false));
|
() => sendResponse(false));
|
||||||
return KEEP_CHANNEL_OPEN;
|
return KEEP_CHANNEL_OPEN;
|
||||||
|
|
||||||
case 'prefChanged':
|
|
||||||
contextMenus.updateOnPrefChanged(request.prefs);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'download':
|
case 'download':
|
||||||
download(request.url)
|
download(request.url)
|
||||||
.then(sendResponse)
|
.then(sendResponse)
|
||||||
|
|
|
@ -499,10 +499,7 @@ function checkUpdateAll() {
|
||||||
let updatesFound = false;
|
let updatesFound = false;
|
||||||
let checked = 0;
|
let checked = 0;
|
||||||
processQueue();
|
processQueue();
|
||||||
// notify the automatic updater to reset the next automatic update accordingly
|
BG.updater.resetInterval();
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
method: 'resetInterval'
|
|
||||||
});
|
|
||||||
|
|
||||||
function processQueue(status) {
|
function processQueue(status) {
|
||||||
if (status === true) {
|
if (status === true) {
|
||||||
|
|
|
@ -203,6 +203,10 @@ function debounce(fn, delay, ...args) {
|
||||||
timers.delete(fn);
|
timers.delete(fn);
|
||||||
fn(...args);
|
fn(...args);
|
||||||
});
|
});
|
||||||
|
debounce.unregister = debounce.unregister || (fn => {
|
||||||
|
clearTimeout(timers.get(fn));
|
||||||
|
timers.delete(fn);
|
||||||
|
});
|
||||||
clearTimeout(timers.get(fn));
|
clearTimeout(timers.get(fn));
|
||||||
timers.set(fn, setTimeout(debounce.run, delay, fn, ...args));
|
timers.set(fn, setTimeout(debounce.run, delay, fn, ...args));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,54 +22,13 @@ document.onclick = e => {
|
||||||
// prevent double-triggering in case a sub-element was clicked
|
// prevent double-triggering in case a sub-element was clicked
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
function check() {
|
|
||||||
let total = 0;
|
|
||||||
let checked = 0;
|
|
||||||
let updated = 0;
|
|
||||||
$('#update-progress').style.width = 0;
|
|
||||||
$('#updates-installed').dataset.value = '';
|
|
||||||
document.body.classList.add('update-in-progress');
|
|
||||||
const maxWidth = $('#update-progress').parentElement.clientWidth;
|
|
||||||
function showProgress() {
|
|
||||||
$('#update-progress').style.width = Math.round(checked / total * maxWidth) + 'px';
|
|
||||||
$('#updates-installed').dataset.value = updated || '';
|
|
||||||
}
|
|
||||||
function done() {
|
|
||||||
document.body.classList.remove('update-in-progress');
|
|
||||||
}
|
|
||||||
BG.update.perform((cmd, value) => {
|
|
||||||
switch (cmd) {
|
|
||||||
case 'count':
|
|
||||||
total = value;
|
|
||||||
if (!total) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'single-updated':
|
|
||||||
updated++;
|
|
||||||
// fallthrough
|
|
||||||
case 'single-skipped':
|
|
||||||
checked++;
|
|
||||||
if (total && checked === total) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
showProgress();
|
|
||||||
});
|
|
||||||
// notify the automatic updater to reset the next automatic update accordingly
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
method: 'resetInterval'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (target.dataset.cmd) {
|
switch (target.dataset.cmd) {
|
||||||
case 'open-manage':
|
case 'open-manage':
|
||||||
openURL({url: '/manage.html'});
|
openURL({url: '/manage.html'});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'check-updates':
|
case 'check-updates':
|
||||||
check();
|
checkUpdates();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'open-keyboard':
|
case 'open-keyboard':
|
||||||
|
@ -84,3 +43,32 @@ document.onclick = e => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function checkUpdates() {
|
||||||
|
let total = 0;
|
||||||
|
let checked = 0;
|
||||||
|
let updated = 0;
|
||||||
|
const installed = $('#updates-installed');
|
||||||
|
const progress = $('#update-progress');
|
||||||
|
const maxWidth = progress.parentElement.clientWidth;
|
||||||
|
progress.style.width = 0;
|
||||||
|
installed.dataset.value = '';
|
||||||
|
document.body.classList.add('update-in-progress');
|
||||||
|
BG.updater.checkAllStyles((state, value) => {
|
||||||
|
switch (state) {
|
||||||
|
case BG.updater.COUNT:
|
||||||
|
total = value;
|
||||||
|
break;
|
||||||
|
case BG.updater.UPDATED:
|
||||||
|
updated++;
|
||||||
|
// fallthrough
|
||||||
|
case BG.updater.SKIPPED:
|
||||||
|
checked++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
progress.style.width = Math.round(checked / total * maxWidth) + 'px';
|
||||||
|
installed.dataset.value = updated || '';
|
||||||
|
}).then(() => {
|
||||||
|
document.body.classList.remove('update-in-progress');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
38
prefs.js
38
prefs.js
|
@ -60,6 +60,11 @@ var prefs = new function Prefs() {
|
||||||
'badgeNormal',
|
'badgeNormal',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const onChange = {
|
||||||
|
any: new Set(),
|
||||||
|
specific: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
// coalesce multiple pref changes in broadcast
|
// coalesce multiple pref changes in broadcast
|
||||||
let broadcastPrefs = {};
|
let broadcastPrefs = {};
|
||||||
|
|
||||||
|
@ -101,16 +106,26 @@ var prefs = new function Prefs() {
|
||||||
}
|
}
|
||||||
values[key] = value;
|
values[key] = value;
|
||||||
defineReadonlyProperty(this.readOnlyValues, key, value);
|
defineReadonlyProperty(this.readOnlyValues, key, value);
|
||||||
|
const hasChanged = !equal(value, oldValue);
|
||||||
if (BG && BG != window) {
|
if (BG && BG != window) {
|
||||||
BG.prefs.set(key, BG.deepCopy(value), {noBroadcast, noSync});
|
BG.prefs.set(key, BG.deepCopy(value), {noBroadcast, noSync});
|
||||||
} else {
|
} else {
|
||||||
localStorage[key] = typeof defaults[key] == 'object'
|
localStorage[key] = typeof defaults[key] == 'object'
|
||||||
? JSON.stringify(value)
|
? JSON.stringify(value)
|
||||||
: value;
|
: value;
|
||||||
if (!noBroadcast && !equal(value, oldValue)) {
|
if (!noBroadcast && hasChanged) {
|
||||||
this.broadcast(key, value, {noSync});
|
this.broadcast(key, value, {noSync});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (hasChanged) {
|
||||||
|
const listener = onChange.specific.get(key);
|
||||||
|
if (listener) {
|
||||||
|
listener(key, value);
|
||||||
|
}
|
||||||
|
for (const listener of onChange.any.values()) {
|
||||||
|
listener(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: key => this.set(key, undefined),
|
remove: key => this.set(key, undefined),
|
||||||
|
@ -124,6 +139,16 @@ var prefs = new function Prefs() {
|
||||||
debounce(doSyncSet);
|
debounce(doSyncSet);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
subscribe(listener, keys) {
|
||||||
|
if (keys) {
|
||||||
|
for (const key of keys) {
|
||||||
|
onChange.specific.set(key, listener);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onChange.any.add(listener);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unlike sync, HTML5 localStorage is ready at browser startup
|
// Unlike sync, HTML5 localStorage is ready at browser startup
|
||||||
|
@ -289,15 +314,8 @@ function setupLivePrefs(IDs) {
|
||||||
updateElement({id, element, force: true});
|
updateElement({id, element, force: true});
|
||||||
element.addEventListener('change', onChange);
|
element.addEventListener('change', onChange);
|
||||||
}
|
}
|
||||||
chrome.runtime.onMessage.addListener(msg => {
|
prefs.subscribe((id, value) => updateElement({id, value}), IDs);
|
||||||
if (msg.prefs) {
|
|
||||||
for (const id in msg.prefs) {
|
|
||||||
if (id in checkedProps) {
|
|
||||||
updateElement({id, value: msg.prefs[id]});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
function onChange() {
|
function onChange() {
|
||||||
const value = this[checkedProps[this.id]];
|
const value = this[checkedProps[this.id]];
|
||||||
if (prefs.get(this.id) != value) {
|
if (prefs.get(this.id) != value) {
|
||||||
|
|
186
update.js
186
update.js
|
@ -1,118 +1,80 @@
|
||||||
/* eslint brace-style: 1, arrow-parens: 1, space-before-function-paren: 1, arrow-body-style: 1 */
|
/* globals getStyles, saveStyle, styleSectionsEqual */
|
||||||
/* globals getStyles, saveStyle */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// TODO: refactor to make usable in manage::Updater
|
// eslint-disable-next-line no-var
|
||||||
var update = {
|
var updater = {
|
||||||
fetch: (resource, callback) => {
|
|
||||||
let req = new XMLHttpRequest();
|
COUNT: 'count',
|
||||||
let [url, data] = resource.split('?');
|
UPDATED: 'updated',
|
||||||
req.open('POST', url, true);
|
SKIPPED: 'skipped',
|
||||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
SKIPPED_SAME_MD5: 'up-to-date: MD5 is unchanged',
|
||||||
req.onload = () => callback(req.responseText);
|
SKIPPED_SAME_CODE: 'up-to-date: code sections are unchanged',
|
||||||
req.onerror = req.ontimeout = () => callback();
|
SKIPPED_ERROR_MD5: 'error: MD5 is invalid',
|
||||||
req.send(data);
|
SKIPPED_ERROR_JSON: 'error: JSON is invalid',
|
||||||
},
|
DONE: 'done',
|
||||||
md5Check: (style, callback, skipped) => {
|
|
||||||
let req = new XMLHttpRequest();
|
lastUpdateTime: parseInt(localStorage.lastUpdateTime) || Date.now(),
|
||||||
req.open('GET', style.md5Url, true);
|
|
||||||
req.onload = () => {
|
checkAllStyles(observe = () => {}) {
|
||||||
let md5 = req.responseText;
|
updater.resetInterval();
|
||||||
if (md5 && md5 !== style.originalMd5) {
|
return new Promise(resolve => {
|
||||||
callback(style);
|
getStyles({}, styles => {
|
||||||
}
|
styles = styles.filter(style => style.updateUrl);
|
||||||
else {
|
observe(updater.COUNT, styles.length);
|
||||||
skipped(`"${style.name}" style is up-to-date`);
|
Promise.all(styles.map(style =>
|
||||||
}
|
updater.checkStyle(style)
|
||||||
};
|
.then(saveStyle)
|
||||||
req.onerror = req.ontimeout = () => skipped('Error validating MD5 checksum');
|
.then(saved => observe(updater.UPDATED, saved))
|
||||||
req.send();
|
.catch(err => observe(updater.SKIPPED, style, err))
|
||||||
},
|
)).then(() => {
|
||||||
list: (callback) => {
|
observe(updater.DONE);
|
||||||
getStyles({}, (styles) => callback(styles.filter(style => style.updateUrl)));
|
resolve();
|
||||||
},
|
});
|
||||||
perform: (observe = function () {}) => {
|
|
||||||
// TODO: use sectionsAreEqual
|
|
||||||
// from install.js
|
|
||||||
function arraysAreEqual (a, b) {
|
|
||||||
// treat empty array and undefined as equivalent
|
|
||||||
if (typeof a === 'undefined') {
|
|
||||||
return (typeof b === 'undefined') || (b.length === 0);
|
|
||||||
}
|
|
||||||
if (typeof b === 'undefined') {
|
|
||||||
return (typeof a === 'undefined') || (a.length === 0);
|
|
||||||
}
|
|
||||||
if (a.length !== b.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return a.every(function (entry) {
|
|
||||||
return b.indexOf(entry) !== -1;
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
// from install.js
|
|
||||||
function sectionsAreEqual(a, b) {
|
|
||||||
if (a.code !== b.code) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ['urls', 'urlPrefixes', 'domains', 'regexps'].every(function (attribute) {
|
|
||||||
return arraysAreEqual(a[attribute], b[attribute]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update.list(styles => {
|
|
||||||
observe('count', styles.length);
|
|
||||||
styles.forEach(style => update.md5Check(style, style => update.fetch(style.updateUrl, response => {
|
|
||||||
if (response) {
|
|
||||||
let json = JSON.parse(response);
|
|
||||||
|
|
||||||
if (json.sections.length === style.sections.length) {
|
|
||||||
if (json.sections.every((section) => {
|
|
||||||
return style.sections.some(installedSection => sectionsAreEqual(section, installedSection));
|
|
||||||
})) {
|
|
||||||
return observe('single-skipped', '2'); // everything is the same
|
|
||||||
}
|
|
||||||
json.method = 'saveStyle';
|
|
||||||
json.id = style.id;
|
|
||||||
|
|
||||||
saveStyle(json).then(style => {
|
|
||||||
observe('single-updated', style.name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return observe('single-skipped', '3'); // style sections mismatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), () => observe('single-skipped', '1')));
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
|
||||||
// automatically update all user-styles if "updateInterval" pref is set
|
checkStyle(style) {
|
||||||
window.setTimeout(function () {
|
return download(style.md5Url)
|
||||||
let id;
|
.then(md5 =>
|
||||||
function run () {
|
!md5 || md5.length != 32 ? Promise.reject(updater.SKIPPED_ERROR_MD5) :
|
||||||
update.perform(/*(cmd, value) => console.log(cmd, value)*/);
|
md5 == style.originalMd5 ? Promise.reject(updater.SKIPPED_SAME_MD5) :
|
||||||
reset();
|
style.updateUrl)
|
||||||
}
|
.then(download)
|
||||||
function reset () {
|
.then(text => tryJSONparse(text))
|
||||||
window.clearTimeout(id);
|
.then(json =>
|
||||||
let interval = prefs.get('updateInterval');
|
!updater.styleJSONseemsValid(json) ? Promise.reject(updater.SKIPPED_ERROR_JSON) :
|
||||||
// if interval === 0 => automatic update is disabled
|
styleSectionsEqual(json, style) ? Promise.reject(updater.SKIPPED_SAME_CODE) :
|
||||||
|
// keep the local name as it could've been customized by the user
|
||||||
|
Object.assign(json, {
|
||||||
|
id: style.id,
|
||||||
|
name: null,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
styleJSONseemsValid(json) {
|
||||||
|
return json
|
||||||
|
&& json.sections
|
||||||
|
&& json.sections.length
|
||||||
|
&& typeof json.sections.every == 'function'
|
||||||
|
&& typeof json.sections[0].code == 'string';
|
||||||
|
},
|
||||||
|
|
||||||
|
schedule() {
|
||||||
|
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
|
||||||
if (interval) {
|
if (interval) {
|
||||||
/* console.log('next update', interval); */
|
const elapsed = Math.max(0, Date.now() - updater.lastUpdateTime);
|
||||||
id = window.setTimeout(run, interval * 60 * 60 * 1000);
|
debounce(updater.checkAllStyles, Math.max(10e3, interval - elapsed));
|
||||||
|
} else if (debounce.timers) {
|
||||||
|
debounce.unregister(updater.checkAllStyles);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
if (prefs.get('updateInterval')) {
|
|
||||||
run();
|
resetInterval() {
|
||||||
}
|
localStorage.lastUpdateTime = updater.lastUpdateTime = Date.now();
|
||||||
chrome.runtime.onMessage.addListener(request => {
|
updater.schedule();
|
||||||
// when user has changed the predefined time interval in the settings page
|
},
|
||||||
if (request.method === 'prefChanged' && 'updateInterval' in request.prefs) {
|
};
|
||||||
reset();
|
|
||||||
}
|
updater.schedule();
|
||||||
// when user just manually checked for updates
|
prefs.subscribe(updater.schedule, ['updateInterval']);
|
||||||
if (request.method === 'resetInterval') {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 10000);
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user