simplify/modularize styleSectionsEqual

+ 1.5x speedup thanks to checkedInB memoization
+ more strict type comparison
+ two-way array comparison (more correct, even if there's no practical difference)
This commit is contained in:
tophf 2017-04-14 00:49:18 +03:00
parent db1dc0df7b
commit dcfb8ad356

View File

@ -333,69 +333,52 @@ function styleCodeEmpty(code) {
} }
function styleSectionsEqual(styleA, styleB) { function styleSectionsEqual({sections: a}, {sections: b}) {
if (!styleA.sections || !styleB.sections) { if (!a || !b) {
return undefined; return undefined;
} }
if (styleA.sections.length != styleB.sections.length) { if (a.length != b.length) {
return false; return false;
} }
const propNames = ['code', 'urlPrefixes', 'urls', 'domains', 'regexps']; const checkedInB = [];
const typeBcaches = []; return a.every(sectionA => b.some(sectionB => {
checkingEveryInA: if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) {
for (const sectionA of styleA.sections) { checkedInB.push(sectionB);
const typeAcache = new Map(); return true;
for (const name of propNames) {
typeAcache.set(name, getType(sectionA[name]));
} }
lookingForDupeInB: }));
for (let i = 0, sectionB; (sectionB = styleB.sections[i]); i++) {
const typeBcache = typeBcaches[i] = typeBcaches[i] || new Map(); function propertiesEqual(secA, secB) {
comparingProps: for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
for (const name of propNames) { if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
const propA = sectionA[name]; return false;
const typeA = typeAcache.get(name);
const propB = sectionB[name];
let typeB = typeBcache.get(name);
if (!typeB) {
typeB = getType(propB);
typeBcache.set(name, typeB);
}
if (typeA != typeB) {
const bothEmptyOrUndefined =
(typeA == 'undefined' || (typeA == 'array' && propA.length == 0)) &&
(typeB == 'undefined' || (typeB == 'array' && propB.length == 0));
if (bothEmptyOrUndefined) {
continue comparingProps;
} else {
continue lookingForDupeInB;
}
}
if (typeA == 'undefined') {
continue comparingProps;
}
if (typeA == 'array') {
if (propA.length != propB.length) {
continue lookingForDupeInB;
}
for (const item of propA) {
if (propB.indexOf(item) < 0) {
continue lookingForDupeInB;
}
}
continue comparingProps;
}
if (typeA == 'string' && propA != propB) {
continue lookingForDupeInB;
}
} }
// dupe found
continue checkingEveryInA;
} }
// dupe not found return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b);
return false; }
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) {
for (const el of array1) {
if (array2.indexOf(el) < 0) {
return false;
}
}
for (const el of array2) {
if (array1.indexOf(el) < 0) {
return false;
}
}
return true;
} }
return true;
} }
@ -515,18 +498,3 @@ function getDomains(url) {
} }
return domains; return domains;
} }
function getType(o) {
if (typeof o == 'undefined' || typeof o == 'string') {
return typeof o;
}
// with the persistent cachedStyles the Array reference is usually different
// so let's check for e.g. type of 'every' which is only present on arrays
// (in the context of our extension)
if (o instanceof Array || typeof o.every == 'function') {
return 'array';
}
console.warn('Unsupported type:', o);
return 'undefined';
}