Add: broadcast messages with reasons

This commit is contained in:
eight 2018-10-07 01:42:43 +08:00
parent e7ef4948cd
commit b5107b78a5
4 changed files with 162 additions and 191 deletions

View File

@ -9,16 +9,12 @@ global usercss styleManager db msg
'use strict'; 'use strict';
window.API_METHODS = Object.assign(window.API_METHODS || {}, { window.API_METHODS = Object.assign(window.API_METHODS || {}, {
// getStyles,
getSectionsByUrl: styleManager.getSectionsByUrl, getSectionsByUrl: styleManager.getSectionsByUrl,
getSectionsById: styleManager.getSectionsById, getSectionsById: styleManager.getSectionsById,
getStylesInfo: styleManager.getStylesInfo, getStylesInfo: styleManager.getStylesInfo,
toggleStyle: styleManager.toggleStyle, toggleStyle: styleManager.toggleStyle,
deleteStyle: styleManager.deleteStyle, deleteStyle: styleManager.deleteStyle,
getStylesInfoByUrl: styleManager.getStylesInfoByUrl, getStylesInfoByUrl: styleManager.getStylesInfoByUrl,
// saveStyle,
// deleteStyle,
getStyleFromDB: id => getStyleFromDB: id =>
db.exec('get', id).then(event => event.target.result), db.exec('get', id).then(event => event.target.result),

View File

@ -13,10 +13,7 @@ const styleManager = (() => {
const compiledExclusion = createCache(); const compiledExclusion = createCache();
const BAD_MATCHER = {test: () => false}; const BAD_MATCHER = {test: () => false};
// FIXME: do we have to prepare `styles` map for all methods?
return ensurePrepared({ return ensurePrepared({
// styles,
// cachedStyleForUrl,
getStylesInfo, getStylesInfo,
getSectionsByUrl, getSectionsByUrl,
installStyle, installStyle,
@ -26,9 +23,7 @@ const styleManager = (() => {
toggleStyle, toggleStyle,
getAllStyles, // used by import-export getAllStyles, // used by import-export
getStylesInfoByUrl, // used by popup getStylesInfoByUrl, // used by popup
countStyles, countStyles
// TODO: get all styles API?
// TODO: get style by ID?
}); });
function getAllStyles() { function getAllStyles() {
@ -37,25 +32,28 @@ const styleManager = (() => {
function toggleStyle(id, enabled) { function toggleStyle(id, enabled) {
const style = styles.get(id); const style = styles.get(id);
const newData = Object.assign({}, style.data, {enabled}); const data = Object.assign({}, style.data, {enabled});
return saveStyle(newData) return saveStyle(data)
.then(newData => { .then(newData => {
style.data = newData; style.data = newData;
for (const url of style.appliesTo) { for (const url of style.appliesTo) {
const cache = cachedStyleForUrl.get(url); const cache = cachedStyleForUrl.get(url);
if (cache) { if (cache) {
cache[newData.id].enabled = newData.enabled; cache.sections[newData.id].enabled = newData.enabled;
} }
} }
const message = { const message = {
method: 'styleUpdated', method: 'styleUpdated',
reason: 'toggle',
codeIsUpdated: false, codeIsUpdated: false,
style: {id, enabled} style: {id, enabled}
}; };
if ([...style.appliesTo].every(isExtensionUrl)) { if ([...style.appliesTo].every(isExtensionUrl)) {
return msg.broadcastExtension(message, 'both'); return msg.broadcastExtension(message, 'both');
} }
return msg.broadcast(message, tab => style.appliesTo.has(tab.url)); // FIXME: this won't work with iframes
// return msg.broadcast(message, tab => style.appliesTo.has(tab.url));
return msg.broadcast(message);
}) })
.then(() => id); .then(() => id);
} }
@ -69,7 +67,7 @@ const styleManager = (() => {
return [getStyleWithNoCode(styles.get(filter.id).data)]; return [getStyleWithNoCode(styles.get(filter.id).data)];
} }
return [...styles.values()] return [...styles.values()]
.filter(s => !filter || filterMatchStyle(filter, s.data)) .filter(s => !filter || filterMatch(filter, s.data))
.map(s => getStyleWithNoCode(s.data)); .map(s => getStyleWithNoCode(s.data));
} }
@ -81,75 +79,19 @@ const styleManager = (() => {
return styles.has(filter.id) ? 1 : 0; return styles.has(filter.id) ? 1 : 0;
} }
return [...styles.values()] return [...styles.values()]
.filter(s => filterMatchStyle(filter, s.data)) .filter(s => filterMatch(filter, s.data))
.length; .length;
} }
function filterMatchStyle(filter, style) { function filterMatch(filter, target) {
for (const key of Object.keys(filter)) { for (const key of Object.keys(filter)) {
if (filter[key] !== style[key]) { if (filter[key] !== target[key]) {
return false; return false;
} }
} }
return true; return true;
} }
function editSave(data) {
data = Object.assign({}, styles.get(data.id).data, data);
return saveStyle(data)
.then(newData =>
broadcastStyleUpdated(newData)
.then(() => newData)
);
}
function setStyleExclusions(id, exclusions) {
const data = Object.assign({}, styles.get(id), {exclusions});
return saveStyle(data)
.then(newData =>
broadcastStyleUpdated(newData)
.then(() => newData)
);
}
function ensurePrepared(methods) {
for (const [name, fn] in Object.entries(methods)) {
methods[name] = (...args) =>
preparing.then(() => fn(...args));
}
return methods;
}
function deleteStyle(id) {
const style = styles.get(id);
return db.exec('delete', id)
.then(() => {
for (const url of style.appliesTo) {
const cache = cachedStyleForUrl.get(url);
if (cache) {
delete cache[id];
}
}
styles.delete(id);
return msg.broadcast({
method: 'styleDeleted',
style: {id}
}, tab => style.appliesTo.has(tab.url));
})
.then(() => id);
}
function createNewStyle() {
return {
enabled: true,
updateUrl: null,
md5Url: null,
url: null,
originalMd5: null,
installDate: Date.now()
};
}
function installStyle(data) { function installStyle(data) {
const style = styles.get(data.id); const style = styles.get(data.id);
if (!style) { if (!style) {
@ -164,99 +106,130 @@ const styleManager = (() => {
return saveStyle(data); return saveStyle(data);
}) })
.then(newData => .then(newData =>
broadcastStyleUpdated(newData) broadcastStyleUpdated(newData, style ? 'update' : 'install')
.then(() => newData) .then(() => newData)
); );
} }
function broadcastStyleUpdated(newData) { function editSave(data) {
const style = styles.get(newData.id); const style = styles.get(data.id);
if (style) {
data = Object.assign({}, style.data, data);
} else {
data = Object.assign(createNewStyle(), data);
}
return saveStyle(data)
.then(newData =>
broadcastStyleUpdated(newData, 'editSave')
.then(() => newData)
);
}
function setStyleExclusions(id, exclusions) {
const data = Object.assign({}, styles.get(id), {exclusions});
return saveStyle(data)
.then(newData =>
broadcastStyleUpdated(newData, 'exclusions')
.then(() => newData)
);
}
function deleteStyle(id) {
const style = styles.get(id);
return db.exec('delete', id)
.then(() => {
for (const url of style.appliesTo) {
const cache = cachedStyleForUrl.get(url);
if (cache) {
delete cache.sections[id];
}
}
styles.delete(id);
return msg.broadcast({
method: 'styleDeleted',
style: {id}
});
})
.then(() => id);
}
function ensurePrepared(methods) {
for (const [name, fn] in Object.entries(methods)) {
methods[name] = (...args) =>
preparing.then(() => fn(...args));
}
return methods;
}
function createNewStyle() {
return {
enabled: true,
updateUrl: null,
md5Url: null,
url: null,
originalMd5: null,
installDate: Date.now()
};
}
function broadcastStyleUpdated(data, reason) {
const style = styles.get(data.id);
if (!style) { if (!style) {
// new style // new style
const appliesTo = new Set(); const appliesTo = new Set();
styles.set(newData.id, { styles.set(data.id, {
appliesTo, appliesTo,
data: newData data
});
for (const cache of cachedStyleForUrl.values()) {
cache.maybeMatch.add(data.id);
}
return msg.broadcast({
method: 'styleAdded',
style: {id: data.id, enabled: data.enabled},
reason
}); });
return Promise.all([
msg.broadcastExtension({method: 'styleAdded', style: getStyleWithNoCode(newData)}),
msg.broadcastTab(tab => getStyleAddedMessage(tab, newData, appliesTo))
]);
} }
const excluded = new Set(); const excluded = new Set();
const updated = new Map(); const updated = new Set();
for (const url of style.appliesTo) { for (const [url, cache] of cachedStyleForUrl.entries()) {
const code = getAppliedCode(url, newData); if (!style.appliesTo.has(url)) {
const cache = cachedStyleForUrl.get(url); cache.maybeMatch.add(data.id);
continue;
}
const code = getAppliedCode(url, data);
if (!code) { if (!code) {
excluded.add(url); excluded.add(url);
if (cache) { delete cache.sections[data.id];
delete cache[newData.id];
}
} else { } else {
updated.set(url, code); updated.add(url);
if (cache) { cache.sections[data.id] = {
cache[newData.id] = {
id: newData.id,
enabled: newData.enabled,
code
};
}
}
}
style.appliesTo = new Set(updated.keys());
return Promise.all([
msg.broadcastExtension({method: 'styleUpdated', style: getStyleWithNoCode(newData)}),
msg.broadcastTab(tab => {
if (excluded.has(tab.url)) {
return {
method: 'styleDeleted',
style: {id: newData.id}
};
}
if (updated.has(tab.url)) {
return {
method: 'styleUpdated',
style: {id: newData.id, sections: updated.get(tab.url)}
};
}
return getStyleAddedMessage(tab, newData, style.appliesTo);
})
]);
}
function getStyleAddedMessage(tab, data, appliesTo) {
const code = getAppliedCode(tab.url, data);
if (!code) {
return;
}
const cache = cachedStyleForUrl.get(tab.url);
if (cache) {
cache[data.id] = {
id: data.id, id: data.id,
enabled: data.enabled, enabled: data.enabled,
code code
}; };
} }
appliesTo.add(tab.url); }
return { style.data = data;
method: 'styleAdded', style.appliesTo = updated;
return msg.broadcast({
method: 'styleUpdated',
style: { style: {
id: data.id, id: data.id,
enabled: data.enabled, enabled: data.enabled
sections: code },
} reason
}; });
} }
function importStyle(style) { // function importStyle(style) {
// FIXME: move this to importer // FIXME: move this to importer
// style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future // style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
// delete style.styleDigest; // TODO: remove in the future // delete style.styleDigest; // TODO: remove in the future
// if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) { // if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
// delete style.originalDigest; // delete style.originalDigest;
// } // }
} // }
function saveStyle(style) { function saveStyle(style) {
if (!style.name) { if (!style.name) {
@ -280,11 +253,40 @@ const styleManager = (() => {
function getSectionsByUrl(url, filter) { function getSectionsByUrl(url, filter) {
let cache = cachedStyleForUrl.get(url); let cache = cachedStyleForUrl.get(url);
if (!cache) { if (!cache) {
cache = {}; cache = {
for (const {appliesTo, data} of styles.values()) { sections: {},
maybeMatch: new Set()
};
buildCache(styles.values());
cachedStyleForUrl.set(url, cache);
} else if (cache.maybeMatch.size) {
buildCache(
[...cache.maybeMatch]
.filter(i => styles.has(i))
.map(i => styles.get(i))
);
}
// if (filter && filter.id) {
// if (!cache.sections[filter.id]) {
// return {};
// }
// return {[filter.id]: cache.sections[filter.id]};
// }
if (filter) {
return Object.values(cache.sections)
.filter(s => filterMatch(filter, s))
.reduce((o, v) => {
o[v.id] = v;
return o;
}, {});
}
return cache.sections;
function buildCache(styleList) {
for (const {appliesTo, data} of styleList) {
const code = getAppliedCode(url, data); const code = getAppliedCode(url, data);
if (code) { if (code) {
cache[data.id] = { cache.sections[data.id] = {
id: data.id, id: data.id,
enabled: data.enabled, enabled: data.enabled,
code code
@ -292,20 +294,7 @@ const styleManager = (() => {
appliesTo.add(url); appliesTo.add(url);
} }
} }
cachedStyleForUrl.set(url, cache);
} }
if (filter && filter.id) {
return {[filter.id]: cache[filter.id]};
}
if (filter) {
return Object.values(cache)
.filter(s => filterMatchStyle(filter, s))
.reduce((o, v) => {
o[v.id] = v;
return o;
}, {});
}
return cache;
} }
function getAppliedCode(url, data) { function getAppliedCode(url, data) {

View File

@ -27,6 +27,7 @@
const pageContextQueue = []; const pageContextQueue = [];
// FIXME: styleViaAPI // FIXME: styleViaAPI
// FIXME: getStylesFallback?
if (!chrome.app && document instanceof XMLDocument) { if (!chrome.app && document instanceof XMLDocument) {
API.styleViaAPI({action: 'styleApply'}); API.styleViaAPI({action: 'styleApply'});
} else { } else {
@ -47,15 +48,6 @@
window.addEventListener(chrome.runtime.id, orphanCheck, true); window.addEventListener(chrome.runtime.id, orphanCheck, true);
} }
// function requestStyles(options, callback = applyStyles) {
// FIXME: options?
// FIXME: getStylesFallback?
// API.getSectionsByUrl(getMatchUrl(), {enabled: true})
// .then(Object.values);
// .then(buildSections)
// .then(callback);
// }
function getMatchUrl() { function getMatchUrl() {
var matchUrl = location.href; var matchUrl = location.href;
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) { if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
@ -97,16 +89,6 @@
} }
function applyOnMessage(request) { function applyOnMessage(request) {
// if (request.styles === 'DIY') {
// Do-It-Yourself tells our built-in pages to fetch the styles directly
// which is faster because IPC messaging JSON-ifies everything internally
// requestStyles({}, styles => {
// request.styles = styles;
// applyOnMessage(request);
// });
// return;
// }
if (!chrome.app && document instanceof XMLDocument && request.method !== 'ping') { if (!chrome.app && document instanceof XMLDocument && request.method !== 'ping') {
request.action = request.method; request.action = request.method;
request.method = null; request.method = null;
@ -124,15 +106,17 @@
break; break;
case 'styleUpdated': case 'styleUpdated':
if (!request.codeIsUpdated) { if (request.codeIsUpdated === false) {
applyStyleState(request.style); applyStyleState(request.style);
break; } else if (request.style.enabled) {
}
if (request.style.enabled) {
removeStyle({id: request.style.id, retire: true});
API.getSectionsByUrl(getMatchUrl(), {id: request.style.id}) API.getSectionsByUrl(getMatchUrl(), {id: request.style.id})
.then(buildSections) .then(sections => {
.then(applyStyles); if (!sections[request.style.id]) {
removeStyle(request.style);
} else {
applyStyles(buildSections(sections));
}
});
} else { } else {
removeStyle(request.style); removeStyle(request.style);
} }

View File

@ -11,6 +11,8 @@ function createCache(size = 1000) {
delete: delete_, delete: delete_,
clear, clear,
has: id => map.has(id), has: id => map.has(id),
entries: () => map.entries(),
values: () => map.values(),
get size() { get size() {
return map.size; return map.size;
} }