|
|
@@ -23,7 +23,8 @@
|
|
|
this.RulesMatcher = this.RulesMatcher || (() => {
|
|
|
|
|
|
const MEDIA_ALL = "all";
|
|
|
- const IGNORED_PSEUDOS = [":focus", ":focus-within", ":hover", ":link", ":visited", ":active", "::after", "::before", "::first-line", "::first-letter", "::placeholder", "::selection", "::marker", "::cue"];
|
|
|
+ const SEPARATOR_TYPES = ["descendant", "child", "sibling", "adjacent"];
|
|
|
+ const PSEUDOS_CLASSES = [":focus", ":focus-within", ":hover", ":link", ":visited", ":active"];
|
|
|
const SELECTOR_TOKEN_TYPE_TAG = "tag";
|
|
|
const SELECTOR_TOKEN_TYPE_ATTRIBUTE = "attribute";
|
|
|
const SELECTOR_TOKEN_TYPE_PSEUDO = "pseudo";
|
|
|
@@ -109,7 +110,12 @@ this.RulesMatcher = this.RulesMatcher || (() => {
|
|
|
}
|
|
|
if (selectors) {
|
|
|
const selectorsText = selectors.map(selector => cssWhat.stringify([selector]));
|
|
|
- selectors.forEach((selector, selectorIndex) => getMatchedElementsSelector(doc, cssRule, selector, selectorsText[selectorIndex], selectorsText, mediaInfo, ruleIndex, sheetIndex, matchedElementsCache));
|
|
|
+ selectors.forEach((selector, selectorIndex) => {
|
|
|
+ const selectorText = selectorsText[selectorIndex];
|
|
|
+ getMatchedElementsSelector(doc,
|
|
|
+ { cssRule, mediaInfo, ruleIndex, sheetIndex, selectors, selectorsText, selector, selectorText },
|
|
|
+ matchedElementsCache);
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -119,60 +125,53 @@ this.RulesMatcher = this.RulesMatcher || (() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function getMatchedElementsSelector(doc, cssRule, selector, selectorText, selectorsText, mediaInfo, ruleIndex, sheetIndex, matchedElementsCache) {
|
|
|
+ function getMatchedElementsSelector(doc, ruleData, matchedElementsCache) {
|
|
|
+ let selectorText;
|
|
|
+ const selectorContainsPseudo = containsPseudo(ruleData.selectorText);
|
|
|
+ if (selectorContainsPseudo) {
|
|
|
+ selectorText = getFilteredSelector(ruleData.selectorText);
|
|
|
+ } else {
|
|
|
+ selectorText = ruleData.selectorText;
|
|
|
+ }
|
|
|
const cachedMatchedElements = matchedElementsCache.get(selectorText);
|
|
|
const matchedElements = cachedMatchedElements || doc.querySelectorAll(selectorText);
|
|
|
if (!cachedMatchedElements) {
|
|
|
matchedElementsCache.set(selectorText, matchedElements);
|
|
|
}
|
|
|
if (matchedElements.length) {
|
|
|
- matchedElements.forEach(element => addRule(element, cssRule, selector, selectorText, selectorsText, mediaInfo, ruleIndex, sheetIndex));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function addRule(element, cssRule, selector, selectorText, selectorsText, mediaInfo, ruleIndex, sheetIndex) {
|
|
|
- const info = getInfo(element, cssRule, selector, mediaInfo, ruleIndex, sheetIndex);
|
|
|
- const { elementInfo, ruleInfo, specificity } = info;
|
|
|
- if (ruleInfo) {
|
|
|
- if (compareSpecificity(ruleInfo.specificity, specificity) == 1) {
|
|
|
- let pseudoIndex = 0, pseudoLength = IGNORED_PSEUDOS.length, ignoredPseudo;
|
|
|
- while (pseudoIndex < pseudoLength && !(ignoredPseudo = selectorText.includes(IGNORED_PSEUDOS[pseudoIndex]))) {
|
|
|
- pseudoIndex++;
|
|
|
- }
|
|
|
- if (!ignoredPseudo || pseudoIndex < pseudoLength) {
|
|
|
- ruleInfo.specificity = specificity;
|
|
|
- ruleInfo.selectorText = selectorText;
|
|
|
- }
|
|
|
+ if (selectorContainsPseudo) {
|
|
|
+ ruleData.mediaInfo.pseudoSelectors.add(ruleData.cssRule.selectorText);
|
|
|
+ matchedElements.forEach(element => addPseudoRule(element, ruleData));
|
|
|
+ } else {
|
|
|
+ matchedElements.forEach(element => addRule(element, ruleData));
|
|
|
}
|
|
|
- } else {
|
|
|
- elementInfo.push({ cssRule, specificity, selectorText, selectorsText });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- function getInfo(element, cssRule, selector, mediaInfo, ruleIndex, sheetIndex) {
|
|
|
- let elementInfo = mediaInfo.elements.get(element);
|
|
|
+ function addRule(element, ruleData) {
|
|
|
+ let elementInfo = ruleData.mediaInfo.elements.get(element);
|
|
|
if (!elementInfo) {
|
|
|
elementInfo = [];
|
|
|
const elementStyle = element.style;
|
|
|
if (elementStyle && elementStyle.length) {
|
|
|
elementInfo.push({ cssStyle: elementStyle });
|
|
|
}
|
|
|
- mediaInfo.elements.set(element, elementInfo);
|
|
|
+ ruleData.mediaInfo.elements.set(element, elementInfo);
|
|
|
}
|
|
|
- const specificity = computeSpecificity(selector);
|
|
|
- specificity.ruleIndex = ruleIndex;
|
|
|
- specificity.sheetIndex = sheetIndex;
|
|
|
- let ruleInfo;
|
|
|
- if (elementInfo.length) {
|
|
|
- let elementRuleIndex = 0, elementRulesLength = elementInfo.length, cssRuleFound;
|
|
|
- while (elementRuleIndex < elementRulesLength && !(cssRuleFound = elementInfo[elementRuleIndex].cssRule == cssRule)) {
|
|
|
- elementRuleIndex++;
|
|
|
- }
|
|
|
- if (elementRuleIndex < elementRulesLength && cssRuleFound) {
|
|
|
- ruleInfo = elementInfo[elementRuleIndex];
|
|
|
- }
|
|
|
+ const specificity = computeSpecificity(ruleData.selector);
|
|
|
+ specificity.ruleIndex = ruleData.ruleIndex;
|
|
|
+ specificity.sheetIndex = ruleData.sheetIndex;
|
|
|
+ ruleData.specificity = specificity;
|
|
|
+ elementInfo.push(ruleData);
|
|
|
+ }
|
|
|
+
|
|
|
+ function addPseudoRule(element, ruleData) {
|
|
|
+ let elementInfo = ruleData.mediaInfo.pseudos.get(element);
|
|
|
+ if (!elementInfo) {
|
|
|
+ elementInfo = [];
|
|
|
+ ruleData.mediaInfo.pseudos.set(element, elementInfo);
|
|
|
}
|
|
|
- return { elementInfo, ruleInfo, specificity };
|
|
|
+ elementInfo.push(ruleData);
|
|
|
}
|
|
|
|
|
|
function computeCascade(mediaInfo, parentMediaInfos, mediaAllInfo) {
|
|
|
@@ -232,7 +231,7 @@ this.RulesMatcher = this.RulesMatcher || (() => {
|
|
|
}
|
|
|
|
|
|
function createMediaInfo(media) {
|
|
|
- const mediaInfo = { media: media, elements: new Map(), medias: new Map(), rules: new Map() };
|
|
|
+ const mediaInfo = { media: media, elements: new Map(), pseudos: new Map(), medias: new Map(), rules: new Map(), pseudoSelectors: new Set() };
|
|
|
if (media == MEDIA_ALL) {
|
|
|
mediaInfo.styles = new Map();
|
|
|
}
|
|
|
@@ -295,6 +294,53 @@ this.RulesMatcher = this.RulesMatcher || (() => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ function getFilteredSelector(selector) {
|
|
|
+ const selectors = cssWhat.parse(selector);
|
|
|
+ return cssWhat.stringify(selectors.map(selector => filterPseudoClasses(selector)));
|
|
|
+
|
|
|
+ function filterPseudoClasses(selector) {
|
|
|
+ const tokens = selector.filter(token => {
|
|
|
+ if (token.data) {
|
|
|
+ if (Array.isArray(token.data)) {
|
|
|
+ token.data = token.data.map(selector => filterPseudoClasses(selector));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const test = ((token.type != "pseudo" || !PSEUDOS_CLASSES.includes(":" + token.name))
|
|
|
+ && (token.type != "pseudo-element"));
|
|
|
+ return test;
|
|
|
+ });
|
|
|
+ let insertedTokens = 0;
|
|
|
+ tokens.forEach((token, index) => {
|
|
|
+ if (SEPARATOR_TYPES.includes(token.type)) {
|
|
|
+ if (!tokens[index - 1] || SEPARATOR_TYPES.includes(tokens[index - 1].type)) {
|
|
|
+ tokens.splice(index + insertedTokens, 0, { type: "universal" });
|
|
|
+ insertedTokens++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (!tokens.length || SEPARATOR_TYPES.includes(tokens[tokens.length - 1].type)) {
|
|
|
+ tokens.push({ type: "universal" });
|
|
|
+ }
|
|
|
+ return tokens;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function containsPseudo(selectorText) {
|
|
|
+ let ignoredPseudo;
|
|
|
+ if (selectorText.includes("::")) {
|
|
|
+ ignoredPseudo = true;
|
|
|
+ } else {
|
|
|
+ let pseudoIndex = 0;
|
|
|
+ while (pseudoIndex < PSEUDOS_CLASSES.length && !ignoredPseudo) {
|
|
|
+ ignoredPseudo = selectorText.includes(PSEUDOS_CLASSES[pseudoIndex]);
|
|
|
+ if (!ignoredPseudo) {
|
|
|
+ pseudoIndex++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ignoredPseudo;
|
|
|
+ }
|
|
|
+
|
|
|
function log(...args) {
|
|
|
console.log("S-File <css-mat>", ...args); // eslint-disable-line no-console
|
|
|
}
|