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-class-assign: [2]
|
||||
no-cond-assign: [2, except-parens]
|
||||
no-confusing-arrow: [2, {allowParens: true}]
|
||||
no-confusing-arrow: [1, {allowParens: true}]
|
||||
no-const-assign: [2]
|
||||
no-constant-condition: [0]
|
||||
no-continue: [0]
|
||||
|
|
|
@ -137,17 +137,9 @@ for (const id of Object.keys(contextMenus)) {
|
|||
chrome.contextMenus.create(item, ignoreChromeError);
|
||||
}
|
||||
|
||||
Object.defineProperty(contextMenus, 'updateOnPrefChanged', {
|
||||
value: changedPrefs => {
|
||||
for (const id in changedPrefs) {
|
||||
if (id in contextMenus) {
|
||||
chrome.contextMenus.update(id, {
|
||||
checked: changedPrefs[id],
|
||||
}, ignoreChromeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
prefs.subscribe((id, checked) => {
|
||||
chrome.contextMenus.update(id, {checked}, ignoreChromeError);
|
||||
}, Object.keys(contextMenus));
|
||||
|
||||
// *************************************************************************
|
||||
// [re]inject content scripts
|
||||
|
@ -282,10 +274,6 @@ function onRuntimeMessage(request, sender, sendResponse) {
|
|||
() => sendResponse(false));
|
||||
return KEEP_CHANNEL_OPEN;
|
||||
|
||||
case 'prefChanged':
|
||||
contextMenus.updateOnPrefChanged(request.prefs);
|
||||
break;
|
||||
|
||||
case 'download':
|
||||
download(request.url)
|
||||
.then(sendResponse)
|
||||
|
|
|
@ -499,10 +499,7 @@ function checkUpdateAll() {
|
|||
let updatesFound = false;
|
||||
let checked = 0;
|
||||
processQueue();
|
||||
// notify the automatic updater to reset the next automatic update accordingly
|
||||
chrome.runtime.sendMessage({
|
||||
method: 'resetInterval'
|
||||
});
|
||||
BG.updater.resetInterval();
|
||||
|
||||
function processQueue(status) {
|
||||
if (status === true) {
|
||||
|
|
|
@ -203,6 +203,10 @@ function debounce(fn, delay, ...args) {
|
|||
timers.delete(fn);
|
||||
fn(...args);
|
||||
});
|
||||
debounce.unregister = debounce.unregister || (fn => {
|
||||
clearTimeout(timers.get(fn));
|
||||
timers.delete(fn);
|
||||
});
|
||||
clearTimeout(timers.get(fn));
|
||||
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
|
||||
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) {
|
||||
case 'open-manage':
|
||||
openURL({url: '/manage.html'});
|
||||
break;
|
||||
|
||||
case 'check-updates':
|
||||
check();
|
||||
checkUpdates();
|
||||
break;
|
||||
|
||||
case 'open-keyboard':
|
||||
|
@ -84,3 +43,32 @@ document.onclick = e => {
|
|||
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',
|
||||
];
|
||||
|
||||
const onChange = {
|
||||
any: new Set(),
|
||||
specific: new Map(),
|
||||
};
|
||||
|
||||
// coalesce multiple pref changes in broadcast
|
||||
let broadcastPrefs = {};
|
||||
|
||||
|
@ -101,16 +106,26 @@ var prefs = new function Prefs() {
|
|||
}
|
||||
values[key] = value;
|
||||
defineReadonlyProperty(this.readOnlyValues, key, value);
|
||||
const hasChanged = !equal(value, oldValue);
|
||||
if (BG && BG != window) {
|
||||
BG.prefs.set(key, BG.deepCopy(value), {noBroadcast, noSync});
|
||||
} else {
|
||||
localStorage[key] = typeof defaults[key] == 'object'
|
||||
? JSON.stringify(value)
|
||||
: value;
|
||||
if (!noBroadcast && !equal(value, oldValue)) {
|
||||
if (!noBroadcast && hasChanged) {
|
||||
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),
|
||||
|
@ -124,6 +139,16 @@ var prefs = new function Prefs() {
|
|||
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
|
||||
|
@ -289,15 +314,8 @@ function setupLivePrefs(IDs) {
|
|||
updateElement({id, element, force: true});
|
||||
element.addEventListener('change', onChange);
|
||||
}
|
||||
chrome.runtime.onMessage.addListener(msg => {
|
||||
if (msg.prefs) {
|
||||
for (const id in msg.prefs) {
|
||||
if (id in checkedProps) {
|
||||
updateElement({id, value: msg.prefs[id]});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
prefs.subscribe((id, value) => updateElement({id, value}), IDs);
|
||||
|
||||
function onChange() {
|
||||
const value = this[checkedProps[this.id]];
|
||||
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 */
|
||||
/* globals getStyles, saveStyle, styleSectionsEqual */
|
||||
'use strict';
|
||||
|
||||
// TODO: refactor to make usable in manage::Updater
|
||||
var update = {
|
||||
fetch: (resource, callback) => {
|
||||
let req = new XMLHttpRequest();
|
||||
let [url, data] = resource.split('?');
|
||||
req.open('POST', url, true);
|
||||
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
req.onload = () => callback(req.responseText);
|
||||
req.onerror = req.ontimeout = () => callback();
|
||||
req.send(data);
|
||||
},
|
||||
md5Check: (style, callback, skipped) => {
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('GET', style.md5Url, true);
|
||||
req.onload = () => {
|
||||
let md5 = req.responseText;
|
||||
if (md5 && md5 !== style.originalMd5) {
|
||||
callback(style);
|
||||
}
|
||||
else {
|
||||
skipped(`"${style.name}" style is up-to-date`);
|
||||
}
|
||||
};
|
||||
req.onerror = req.ontimeout = () => skipped('Error validating MD5 checksum');
|
||||
req.send();
|
||||
},
|
||||
list: (callback) => {
|
||||
getStyles({}, (styles) => callback(styles.filter(style => style.updateUrl)));
|
||||
},
|
||||
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;
|
||||
// eslint-disable-next-line no-var
|
||||
var updater = {
|
||||
|
||||
COUNT: 'count',
|
||||
UPDATED: 'updated',
|
||||
SKIPPED: 'skipped',
|
||||
SKIPPED_SAME_MD5: 'up-to-date: MD5 is unchanged',
|
||||
SKIPPED_SAME_CODE: 'up-to-date: code sections are unchanged',
|
||||
SKIPPED_ERROR_MD5: 'error: MD5 is invalid',
|
||||
SKIPPED_ERROR_JSON: 'error: JSON is invalid',
|
||||
DONE: 'done',
|
||||
|
||||
lastUpdateTime: parseInt(localStorage.lastUpdateTime) || Date.now(),
|
||||
|
||||
checkAllStyles(observe = () => {}) {
|
||||
updater.resetInterval();
|
||||
return new Promise(resolve => {
|
||||
getStyles({}, styles => {
|
||||
styles = styles.filter(style => style.updateUrl);
|
||||
observe(updater.COUNT, styles.length);
|
||||
Promise.all(styles.map(style =>
|
||||
updater.checkStyle(style)
|
||||
.then(saveStyle)
|
||||
.then(saved => observe(updater.UPDATED, saved))
|
||||
.catch(err => observe(updater.SKIPPED, style, err))
|
||||
)).then(() => {
|
||||
observe(updater.DONE);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
// 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
|
||||
window.setTimeout(function () {
|
||||
let id;
|
||||
function run () {
|
||||
update.perform(/*(cmd, value) => console.log(cmd, value)*/);
|
||||
reset();
|
||||
}
|
||||
function reset () {
|
||||
window.clearTimeout(id);
|
||||
let interval = prefs.get('updateInterval');
|
||||
// if interval === 0 => automatic update is disabled
|
||||
},
|
||||
|
||||
checkStyle(style) {
|
||||
return download(style.md5Url)
|
||||
.then(md5 =>
|
||||
!md5 || md5.length != 32 ? Promise.reject(updater.SKIPPED_ERROR_MD5) :
|
||||
md5 == style.originalMd5 ? Promise.reject(updater.SKIPPED_SAME_MD5) :
|
||||
style.updateUrl)
|
||||
.then(download)
|
||||
.then(text => tryJSONparse(text))
|
||||
.then(json =>
|
||||
!updater.styleJSONseemsValid(json) ? Promise.reject(updater.SKIPPED_ERROR_JSON) :
|
||||
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) {
|
||||
/* console.log('next update', interval); */
|
||||
id = window.setTimeout(run, interval * 60 * 60 * 1000);
|
||||
const elapsed = Math.max(0, Date.now() - updater.lastUpdateTime);
|
||||
debounce(updater.checkAllStyles, Math.max(10e3, interval - elapsed));
|
||||
} else if (debounce.timers) {
|
||||
debounce.unregister(updater.checkAllStyles);
|
||||
}
|
||||
}
|
||||
if (prefs.get('updateInterval')) {
|
||||
run();
|
||||
}
|
||||
chrome.runtime.onMessage.addListener(request => {
|
||||
// when user has changed the predefined time interval in the settings page
|
||||
if (request.method === 'prefChanged' && 'updateInterval' in request.prefs) {
|
||||
reset();
|
||||
}
|
||||
// when user just manually checked for updates
|
||||
if (request.method === 'resetInterval') {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
}, 10000);
|
||||
},
|
||||
|
||||
resetInterval() {
|
||||
localStorage.lastUpdateTime = updater.lastUpdateTime = Date.now();
|
||||
updater.schedule();
|
||||
},
|
||||
};
|
||||
|
||||
updater.schedule();
|
||||
prefs.subscribe(updater.schedule, ['updateInterval']);
|
||||
|
|
Loading…
Reference in New Issue
Block a user