don't apply global section to Stylus pages

unless it was intentionally targeted via url(),  url-prefix(), or regexp(). The regexp must contain the word "extension" without quotes.
This commit is contained in:
tophf 2022-08-31 15:15:21 +03:00
parent 3489b513c9
commit bf3dd0318d
2 changed files with 65 additions and 67 deletions

View File

@ -1,7 +1,7 @@
/* global API msg */// msg.js /* global API msg */// msg.js
/* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js /* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js
/* global bgReady createCache uuidIndex */// common.js /* global bgReady createCache uuidIndex */// common.js
/* global calcStyleDigest styleCodeEmpty styleSectionGlobal */// sections-util.js /* global calcStyleDigest styleCodeEmpty */// sections-util.js
/* global db */ /* global db */
/* global prefs */ /* global prefs */
/* global tabMan */ /* global tabMan */
@ -74,6 +74,29 @@ const styleMan = (() => {
_rev: 0, _rev: 0,
}; };
uuidIndex.addCustomId(orderWrap, {set: setOrder}); uuidIndex.addCustomId(orderWrap, {set: setOrder});
class MatchQuery {
constructor(url) {
this.url = url;
}
get urlWithoutHash() {
return this._set('urlWithoutHash', this.url.split('#', 1)[0]);
}
get urlWithoutParams() {
return this._set('urlWithoutParams', this.url.split(/[?#]/, 1)[0]);
}
get domain() {
return this._set('domain', tryURL(this.url).hostname);
}
get isOwnPage() {
return this._set('isOwnPage', this.url.startsWith(URLS.ownOrigin));
}
_set(name, value) {
Object.defineProperty(this, name, {value});
return value;
}
}
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */ /** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = Promise.all([init(), prefs.ready]); let ready = Promise.all([init(), prefs.ready]);
@ -226,7 +249,7 @@ const styleMan = (() => {
const styles = id const styles = id
? [id2style(id)].filter(Boolean) ? [id2style(id)].filter(Boolean)
: getAllAsArray(); : getAllAsArray();
const query = createMatchQuery(url); const query = new MatchQuery(url);
for (const style of styles) { for (const style of styles) {
let excluded = false; let excluded = false;
let excludedScheme = false; let excludedScheme = false;
@ -248,10 +271,7 @@ const styleMan = (() => {
excludedScheme = true; excludedScheme = true;
} }
for (const section of style.sections) { for (const section of style.sections) {
if (styleSectionGlobal(section) && styleCodeEmpty(section.code)) { const match = urlMatchSection(query, section, true);
continue;
}
const match = urlMatchSection(query, section);
if (match) { if (match) {
if (match === 'sloppy') { if (match === 'sloppy') {
sloppy = true; sloppy = true;
@ -431,7 +451,7 @@ const styleMan = (() => {
cache.maybeMatch.add(id); cache.maybeMatch.add(id);
continue; continue;
} }
const code = getAppliedCode(createMatchQuery(url), style); const code = getAppliedCode(new MatchQuery(url), style);
if (code) { if (code) {
updated.add(url); updated.add(url);
buildCacheEntry(cache, style, code); buildCacheEntry(cache, style, code);
@ -601,40 +621,56 @@ const styleMan = (() => {
return true; return true;
} }
function urlMatchSection(query, section) { function urlMatchSection(query, section, skipEmptyGlobal) {
let dd, ddL, pp, ppL, rr, rrL, uu, uuL;
if ( if (
section.domains && (dd = section.domains) && (ddL = dd.length) && dd.some(urlMatchDomain, query) ||
section.domains.some(d => d === query.domain || query.domain.endsWith(`.${d}`)) (pp = section.urlPrefixes) && (ppL = pp.length) && pp.some(urlMatchPrefix, query) ||
/* Per the specification the fragment portion is ignored in @-moz-document:
https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
but the spec is outdated and doesn't account for SPA sites,
so we only respect it for `url()` function */
(uu = section.urls) && (uuL = uu.length) && (
uu.includes(query.url) ||
uu.includes(query.urlWithoutHash)
) ||
(rr = section.regexps) && (rrL = rr.length) && rr.some(urlMatchRegexp, query)
) { ) {
return true; return true;
} }
if (section.urlPrefixes && section.urlPrefixes.some(p => p && query.url.startsWith(p))) {
return true;
}
// as per spec the fragment portion is ignored in @-moz-document:
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
// but the spec is outdated and doesn't account for SPA sites
// so we only respect it for `url()` function
if (section.urls && (
section.urls.includes(query.url) ||
section.urls.includes(query.urlWithoutHash)
)) {
return true;
}
if (section.regexps && section.regexps.some(r => compileRe(r).test(query.url))) {
return true;
}
/* /*
According to CSS4 @document specification the entire URL must match. According to CSS4 @document specification the entire URL must match.
Stylish-for-Chrome implemented it incorrectly since the very beginning. Stylish-for-Chrome implemented it incorrectly since the very beginning.
We'll detect styles that abuse the bug by finding the sections that We'll detect styles that abuse the bug by finding the sections that
would have been applied by Stylish but not by us as we follow the spec. would have been applied by Stylish but not by us as we follow the spec.
*/ */
if (section.regexps && section.regexps.some(r => compileSloppyRe(r).test(query.url))) { if (rrL && rr.some(urlMatchRegexpSloppy, query)) {
return 'sloppy'; return 'sloppy';
} }
// TODO: check for invalid regexps? // TODO: check for invalid regexps?
return styleSectionGlobal(section); return !rrL && !ppL && !uuL && !ddL &&
!query.isOwnPage && // We allow only intentionally targeted sections for own pages
(!skipEmptyGlobal || !styleCodeEmpty(section.code));
}
/** @this {MatchQuery} */
function urlMatchDomain(d) {
const _d = this.domain;
return d === _d ||
_d[_d.length - d.length - 1] === '.' && _d.endsWith(d);
}
/** @this {MatchQuery} */
function urlMatchPrefix(p) {
return p && this.url.startsWith(p);
}
/** @this {MatchQuery} */
function urlMatchRegexp(r) {
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
compileRe(r).test(this.url);
}
/** @this {MatchQuery} */
function urlMatchRegexpSloppy(r) {
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
compileSloppyRe(r).test(this.url);
} }
function createCompiler(compile) { function createCompiler(compile) {
@ -674,37 +710,8 @@ const styleMan = (() => {
'$'; '$';
} }
function createMatchQuery(url) {
let urlWithoutHash;
let urlWithoutParams;
let domain;
return {
url,
get urlWithoutHash() {
if (!urlWithoutHash) {
urlWithoutHash = url.split('#')[0];
}
return urlWithoutHash;
},
get urlWithoutParams() {
if (!urlWithoutParams) {
const u = tryURL(url);
urlWithoutParams = u.origin + u.pathname;
}
return urlWithoutParams;
},
get domain() {
if (!domain) {
const u = tryURL(url);
domain = u.hostname;
}
return domain;
},
};
}
function buildCache(cache, url, styleList) { function buildCache(cache, url, styleList) {
const query = createMatchQuery(url); const query = new MatchQuery(url);
for (const {style, appliesTo, preview} of styleList) { for (const {style, appliesTo, preview} of styleList) {
const code = getAppliedCode(query, preview || style); const code = getAppliedCode(query, preview || style);
if (code) { if (code) {

View File

@ -5,7 +5,6 @@
MozDocMapper MozDocMapper
styleCodeEmpty styleCodeEmpty
styleJSONseemsValid styleJSONseemsValid
styleSectionGlobal
styleSectionsEqual styleSectionsEqual
*/ */
@ -83,14 +82,6 @@ function styleCodeEmpty(code) {
return false; return false;
} }
/** Checks if section is global i.e. has no targets at all */
function styleSectionGlobal(section) {
return (!section.regexps || !section.regexps.length) &&
(!section.urlPrefixes || !section.urlPrefixes.length) &&
(!section.urls || !section.urls.length) &&
(!section.domains || !section.domains.length);
}
/** /**
* The sections are checked in successive order because it matters when many sections * The sections are checked in successive order because it matters when many sections
* match the same URL and they have rules with the same CSS specificity * match the same URL and they have rules with the same CSS specificity