[Code Health] Format components/cosmetic_filters/**/*.{ts,js} (#32555)

Format components/cosmetic_filters/**/*.{ts,js}
This commit is contained in:
Mikhail
2025-12-03 01:04:25 +04:00
committed by GitHub
parent f164d87981
commit 596d26319f
5 changed files with 1730 additions and 1515 deletions
-1
View File
@@ -62,7 +62,6 @@ components/brave_wallet/resources/solana_web3_script.js
/components/playlist/
/components/speedreader/
/components/common/
/components/cosmetic_filters/
/components/definitions/
/components/new_tab_takeover/
/components/skus/
@@ -9,7 +9,10 @@
// - for cosmetic filters work with CSS and stylesheet. That work itself
// could call the script several times.
import { applyCompiledSelector, compileProceduralSelector } from './procedural_filters'
import {
applyCompiledSelector,
compileProceduralSelector,
} from './procedural_filters'
// Start looking for things to unhide before at most this long after
// the backend script is up and connected (eg backgroundReady = true),
@@ -52,9 +55,10 @@ const styleAttrMap = new Map<string, string>()
const queriedIds = new Set<string>()
const queriedClasses = new Set<string>()
const notYetQueriedElements: Array<(Element[] | NodeListOf<Element>)> = []
const notYetQueriedElements: Array<Element[] | NodeListOf<Element>> = []
const classIdWithoutHtmlOrBody = '[id]:not(html):not(body),[class]:not(html):not(body)'
const classIdWithoutHtmlOrBody =
'[id]:not(html):not(body),[class]:not(html):not(body)'
// Each of these get setup once the mutation observer starts running.
let notYetQueriedClasses: string[] = []
@@ -74,7 +78,10 @@ CC.secondRunQueue = CC.secondRunQueue || new Set<string>()
// more time.
CC.finalRunQueue = CC.finalRunQueue || new Set<string>()
CC.allQueues = CC.allQueues || [
CC.firstRunQueue, CC.secondRunQueue, CC.finalRunQueue]
CC.firstRunQueue,
CC.secondRunQueue,
CC.finalRunQueue,
]
CC.numQueues = CC.numQueues || CC.allQueues.length
CC.alreadyUnhiddenSelectors = CC.alreadyUnhiddenSelectors || new Set<string>()
CC.alreadyKnownFirstPartySubtrees =
@@ -96,35 +103,38 @@ CC.fetchNewClassIdRulesThrottlingMs =
*/
const idleize = (onIdle: Function, timeout: number) => {
let idleId: number | undefined
return function WillRunOnIdle () {
return function WillRunOnIdle() {
if (idleId !== undefined) {
return
}
idleId = window.requestIdleCallback(() => {
idleId = undefined
onIdle()
}, { timeout })
idleId = window.requestIdleCallback(
() => {
idleId = undefined
onIdle()
},
{ timeout },
)
}
}
const isRelativeUrl = (url: string): boolean => {
return (
!url.startsWith('//') &&
!url.startsWith('http://') &&
!url.startsWith('https://')
!url.startsWith('//')
&& !url.startsWith('http://')
&& !url.startsWith('https://')
)
}
const isElement = (node: Node): boolean => {
return (node.nodeType === 1)
return node.nodeType === 1
}
const asElement = (node: Node): Element | null => {
return isElement(node) ? node as Element : null
return isElement(node) ? (node as Element) : null
}
const isHTMLElement = (node: Node): boolean => {
return ('innerText' in node)
return 'innerText' in node
}
// The fetchNewClassIdRules() can be called of each MutationObserver event.
@@ -144,18 +154,14 @@ const ShouldThrottleFetchNewClassIdsRules = (): boolean => {
const msToWait = nextFetchNewClassIdRulesCall - now
if (msToWait > 0) {
// Schedule the call in |msToWait| ms and return.
fetchNewClassIdRulesTimeoutId =
window.setTimeout(
() => {
fetchNewClassIdRulesTimeoutId = undefined
fetchNewClassIdRules()
}
, msToWait)
fetchNewClassIdRulesTimeoutId = window.setTimeout(() => {
fetchNewClassIdRulesTimeoutId = undefined
fetchNewClassIdRules()
}, msToWait)
return true
}
nextFetchNewClassIdRulesCall =
now + CC.fetchNewClassIdRulesThrottlingMs
nextFetchNewClassIdRulesCall = now + CC.fetchNewClassIdRulesThrottlingMs
return false
}
@@ -180,16 +186,20 @@ const fetchNewClassIdRules = () => {
}
}
notYetQueriedElements.length = 0
if ((!notYetQueriedClasses || notYetQueriedClasses.length === 0) &&
(!notYetQueriedIds || notYetQueriedIds.length === 0)) {
if (
(!notYetQueriedClasses || notYetQueriedClasses.length === 0)
&& (!notYetQueriedIds || notYetQueriedIds.length === 0)
) {
return
}
// Callback to c++ renderer process
// @ts-expect-error
cf_worker.hiddenClassIdSelectors(
JSON.stringify({
classes: notYetQueriedClasses, ids: notYetQueriedIds
}))
JSON.stringify({
classes: notYetQueriedClasses,
ids: notYetQueriedIds,
}),
)
notYetQueriedClasses = []
notYetQueriedIds = []
}
@@ -204,7 +214,7 @@ const useMutationObserver = () => {
const observerConfig = {
subtree: true,
childList: true,
attributeFilter: ['id', 'class']
attributeFilter: ['id', 'class'],
}
observer.observe(document.documentElement, observerConfig)
}
@@ -216,11 +226,15 @@ const usePolling = (observer?: MutationObserver) => {
}
const futureTimeMs = window.Date.now() + returnToMutationObserverIntervalMs
const queryAttrsFromDocumentBound = queryAttrsFromDocument.bind(undefined,
futureTimeMs)
const queryAttrsFromDocumentBound = queryAttrsFromDocument.bind(
undefined,
futureTimeMs,
)
selectorsPollingIntervalId = window.setInterval(queryAttrsFromDocumentBound,
selectorsPollingIntervalMs)
selectorsPollingIntervalId = window.setInterval(
queryAttrsFromDocumentBound,
selectorsPollingIntervalMs,
)
}
const queueAttrsFromMutations = (mutations: MutationRecord[]): number => {
@@ -269,7 +283,10 @@ const queueAttrsFromMutations = (mutations: MutationRecord[]): number => {
return mutationScore
}
const onMutations = (mutations: MutationRecord[], observer: MutationObserver) => {
const onMutations = (
mutations: MutationRecord[],
observer: MutationObserver,
) => {
// Callback to c++ renderer process
// @ts-expect-error
const eventId: number | undefined = cf_worker.onHandleMutationsBegin?.()
@@ -297,20 +314,23 @@ const onMutations = (mutations: MutationRecord[], observer: MutationObserver) =>
}
if (CC.hasProceduralActions) {
const addedElements : Element[] = [];
mutations.forEach(mutation =>
mutation.addedNodes.length !== 0 && mutation.addedNodes.forEach(n => {
if (n.nodeType === Node.ELEMENT_NODE) {
addedElements.push(n as Element)
const childNodes = (n as Element).querySelectorAll('*')
childNodes.length !== 0 && childNodes.forEach(c => {
c.nodeType === Node.ELEMENT_NODE && addedElements.push(c)
})
}
})
const addedElements: Element[] = []
mutations.forEach(
(mutation) =>
mutation.addedNodes.length !== 0
&& mutation.addedNodes.forEach((n) => {
if (n.nodeType === Node.ELEMENT_NODE) {
addedElements.push(n as Element)
const childNodes = (n as Element).querySelectorAll('*')
childNodes.length !== 0
&& childNodes.forEach((c) => {
c.nodeType === Node.ELEMENT_NODE && addedElements.push(c)
})
}
}),
)
if (addedElements.length !== 0) {
executeProceduralActions(addedElements);
executeProceduralActions(addedElements)
}
}
@@ -331,8 +351,14 @@ const isFirstPartyUrl = (url: string): boolean => {
return cf_worker.isFirstPartyUrl(url)
}
const stripChildTagsFromText = (elm: HTMLElement, tagName: string, text: string): string => {
const childElms = Array.from(elm.getElementsByTagName(tagName)) as HTMLElement[]
const stripChildTagsFromText = (
elm: HTMLElement,
tagName: string,
text: string,
): string => {
const childElms = Array.from(
elm.getElementsByTagName(tagName),
) as HTMLElement[]
let localText = text
for (const anElm of childElms) {
localText = localText.replaceAll(anElm.innerText, '')
@@ -403,7 +429,10 @@ interface IsFirstPartyQueryResult {
*
* Finally, special case some ids we know are used only for third party ads.
*/
const isSubTreeFirstParty = (elm: Element, possibleQueryResult?: IsFirstPartyQueryResult): boolean => {
const isSubTreeFirstParty = (
elm: Element,
possibleQueryResult?: IsFirstPartyQueryResult,
): boolean => {
let queryResult: IsFirstPartyQueryResult
let isTopLevel: boolean
@@ -414,7 +443,7 @@ const isSubTreeFirstParty = (elm: Element, possibleQueryResult?: IsFirstPartyQue
queryResult = {
foundFirstPartyResource: false,
foundThirdPartyResource: false,
foundKnownThirdPartyAd: false
foundKnownThirdPartyAd: false,
}
isTopLevel = true
}
@@ -422,9 +451,11 @@ const isSubTreeFirstParty = (elm: Element, possibleQueryResult?: IsFirstPartyQue
if (elm.getAttribute) {
if (elm.hasAttribute('id')) {
const elmId = elm.getAttribute('id')!
if (elmId.startsWith('google_ads_iframe_') ||
elmId.startsWith('div-gpt-ad') ||
elmId.startsWith('adfox_')) {
if (
elmId.startsWith('google_ads_iframe_')
|| elmId.startsWith('div-gpt-ad')
|| elmId.startsWith('adfox_')
) {
queryResult.foundKnownThirdPartyAd = true
return false
}
@@ -442,8 +473,7 @@ const isSubTreeFirstParty = (elm: Element, possibleQueryResult?: IsFirstPartyQue
if (elm.hasAttribute('style')) {
const elmStyle = elm.getAttribute('style')!
if (elmStyle.includes('url(') ||
elmStyle.includes('//')) {
if (elmStyle.includes('url(') || elmStyle.includes('//')) {
queryResult.foundThirdPartyResource = true
}
}
@@ -477,7 +507,7 @@ const isSubTreeFirstParty = (elm: Element, possibleQueryResult?: IsFirstPartyQue
}
if (!isTopLevel) {
return (!queryResult.foundThirdPartyResource)
return !queryResult.foundThirdPartyResource
}
if (queryResult.foundThirdPartyResource) {
@@ -492,8 +522,8 @@ const unhideSelectors = (selectors: Set<string>) => {
}
// Find selectors we have a rule index for
const rulesToRemove = Array.from(selectors)
.map(selector => CC.allSelectorsToRules.get(selector))
.filter(i => i !== undefined)
.map((selector) => CC.allSelectorsToRules.get(selector))
.filter((i) => i !== undefined)
.sort()
.reverse()
// Delete the rules
@@ -520,7 +550,11 @@ const unhideSelectors = (selectors: Set<string>) => {
}
if (oldIdx !== i) {
// Probably out of sync
console.error('Cosmetic Filters: old index did not match lookup index', { selector, oldIdx, i })
console.error('Cosmetic Filters: old index did not match lookup index', {
selector,
oldIdx,
i,
})
}
CC.allSelectorsToRules.set(selector, oldIdx - countAtLastHighest)
}
@@ -555,7 +589,10 @@ const pumpCosmeticFilterQueues = () => {
continue
}
const currentWorkLoad = Array.from(currentQueue.values()).slice(0, maxWorkSize)
const currentWorkLoad = Array.from(currentQueue.values()).slice(
0,
maxWorkSize,
)
const comboSelector = currentWorkLoad.join(',')
const matchingElms = document.querySelectorAll(comboSelector)
// Will hold selectors identified by _this_ queue pumping, that were
@@ -610,7 +647,8 @@ const pumpCosmeticFilterQueues = () => {
for (const aUsedSelector of currentWorkLoad) {
currentQueue.delete(aUsedSelector)
// Don't requeue selectors we know identify first party content.
const selectorMatchedFirstParty = newlyIdentifiedFirstPartySelectors.has(aUsedSelector)
const selectorMatchedFirstParty =
newlyIdentifiedFirstPartySelectors.has(aUsedSelector)
if (nextQueue && !selectorMatchedFirstParty) {
nextQueue.add(aUsedSelector)
}
@@ -635,7 +673,7 @@ const pumpCosmeticFilterQueues = () => {
const pumpCosmeticFilterQueuesOnIdle = idleize(
pumpCosmeticFilterQueues,
pumpIntervalMaxMs
pumpIntervalMaxMs,
)
const queryAttrsFromDocument = (switchToMutationObserverAtTime?: number) => {
@@ -652,7 +690,7 @@ const queryAttrsFromDocument = (switchToMutationObserverAtTime?: number) => {
fetchNewClassIdRules()
}
if (CC.hasProceduralActions) executeProceduralActions();
if (CC.hasProceduralActions) executeProceduralActions()
if (eventId) {
// Callback to c++ renderer process
@@ -660,8 +698,10 @@ const queryAttrsFromDocument = (switchToMutationObserverAtTime?: number) => {
cf_worker.onQuerySelectorsEnd(eventId)
}
if (switchToMutationObserverAtTime !== undefined &&
window.Date.now() >= switchToMutationObserverAtTime) {
if (
switchToMutationObserverAtTime !== undefined
&& window.Date.now() >= switchToMutationObserverAtTime
) {
useMutationObserver()
}
}
@@ -690,19 +730,22 @@ const scheduleQueuePump = (hide1pContent: boolean, genericHide: boolean) => {
}
// Third / final possibility, this is this the first time this has been
// called, in which case set up a timer and quit
CC._startCheckingId = window.requestIdleCallback(_ => {
CC._hasDelayOcurred = true
if (!genericHide || CC.hasProceduralActions) {
if (CC.firstSelectorsPollingDelayMs === undefined) {
startObserving()
} else {
window.setTimeout(startObserving, CC.firstSelectorsPollingDelayMs)
CC._startCheckingId = window.requestIdleCallback(
(_) => {
CC._hasDelayOcurred = true
if (!genericHide || CC.hasProceduralActions) {
if (CC.firstSelectorsPollingDelayMs === undefined) {
startObserving()
} else {
window.setTimeout(startObserving, CC.firstSelectorsPollingDelayMs)
}
}
}
if (!hide1pContent) {
pumpCosmeticFilterQueuesOnIdle()
}
}, { timeout: maxTimeMSBeforeStart })
if (!hide1pContent) {
pumpCosmeticFilterQueuesOnIdle()
}
},
{ timeout: maxTimeMSBeforeStart },
)
}
const tryScheduleQueuePump = () => {
@@ -755,40 +798,48 @@ const executeProceduralActions = (added?: Element[]) => {
// classList.remove(tokens...) always triggers another mutation
// even if nothing was removed.
if (element.classList.contains(action.arg)) {
element.classList.remove(action.arg);
element.classList.remove(action.arg)
}
}
}
for (const { selector, action } of CC.proceduralActionFilters) {
try {
let matchingElements: Element[] | NodeListOf<any>;
let startOperator: number;
let matchingElements: Element[] | NodeListOf<any>
let startOperator: number
if (selector[0].type === 'css-selector' && added === undefined) {
matchingElements = document.querySelectorAll(selector[0].arg);
startOperator = 1;
matchingElements = document.querySelectorAll(selector[0].arg)
startOperator = 1
} else if (added === undefined) {
matchingElements = document.querySelectorAll('*');
startOperator = 0;
matchingElements = document.querySelectorAll('*')
startOperator = 0
} else {
matchingElements = added;
startOperator = 0;
matchingElements = added
startOperator = 0
}
if (startOperator === selector.length) {
// First `css-selector` was already handled, and no more elements remain
matchingElements.forEach(elem => performAction(elem, action))
matchingElements.forEach((elem) => performAction(elem, action))
} else {
const filter = compileProceduralSelector(selector.slice(startOperator));
applyCompiledSelector(filter, matchingElements as HTMLElement[]).forEach(elem => performAction(elem, action))
const filter = compileProceduralSelector(selector.slice(startOperator))
applyCompiledSelector(
filter,
matchingElements as HTMLElement[],
).forEach((elem) => performAction(elem, action))
}
} catch (e) {
console.error('Failed to apply filter ' + JSON.stringify(selector)
+ ' ' + JSON.stringify(action) + ': ');
console.error(e.message);
console.error(e.stack);
console.error(
'Failed to apply filter '
+ JSON.stringify(selector)
+ ' '
+ JSON.stringify(action)
+ ': ',
)
console.error(e.message)
console.error(e.stack)
}
}
};
}
if (CC.hasProceduralActions) executeProceduralActions();
if (CC.hasProceduralActions) executeProceduralActions()
File diff suppressed because it is too large Load Diff
@@ -19,40 +19,50 @@ const api = {
cosmeticFilterManage: () => {
cf_worker.manageCustomFilters()
},
getElementPickerThemeInfo: (callback: (
isDarkModeEnabled: boolean, bgcolor: number) => void) => {
cf_worker.getElementPickerThemeInfo().then(
(val: { isDarkModeEnabled: boolean; bgcolor: number }) => {
getElementPickerThemeInfo: (
callback: (isDarkModeEnabled: boolean, bgcolor: number) => void,
) => {
cf_worker
.getElementPickerThemeInfo()
.then((val: { isDarkModeEnabled: boolean; bgcolor: number }) => {
callback(val.isDarkModeEnabled, val.bgcolor)
})
},
getLocalizedTexts: (callback: (
btnCreateDisabledText: string,
btnCreateEnabledText: string,
btnManageText: string,
btnShowRulesBoxText: string,
btnHideRulesBoxText: string,
btnQuitText: string) => void) => {
cf_worker.getLocalizedTexts().then(
(val: {
btnCreateDisabledText: string;
btnCreateEnabledText: string;
btnManageText: string;
btnShowRulesBoxText: string;
btnHideRulesBoxText: string;
btnQuitText: string
}) => {
callback(val.btnCreateDisabledText,
val.btnCreateEnabledText,
val.btnManageText,
val.btnShowRulesBoxText,
val.btnHideRulesBoxText,
val.btnQuitText)
})
getLocalizedTexts: (
callback: (
btnCreateDisabledText: string,
btnCreateEnabledText: string,
btnManageText: string,
btnShowRulesBoxText: string,
btnHideRulesBoxText: string,
btnQuitText: string,
) => void,
) => {
cf_worker
.getLocalizedTexts()
.then(
(val: {
btnCreateDisabledText: string
btnCreateEnabledText: string
btnManageText: string
btnShowRulesBoxText: string
btnHideRulesBoxText: string
btnQuitText: string
}) => {
callback(
val.btnCreateDisabledText,
val.btnCreateEnabledText,
val.btnManageText,
val.btnShowRulesBoxText,
val.btnHideRulesBoxText,
val.btnQuitText,
)
},
)
},
getPlatform: (): string => {
return cf_worker.getPlatform()
}
},
}
// When the picker is activated, it eats all pointer events and takes up the
@@ -70,11 +80,11 @@ const elementFromFrameCoords = (x: number, y: number): Element | null => {
}
enum SpecificityFlags {
Id = (1 << 0),
Hierarchy = (1 << 1),
Attributes = (1 << 2),
Class = (1 << 3),
NthOfType = (1 << 4)
Id = 1 << 0,
Hierarchy = 1 << 1,
Attributes = 1 << 2,
Class = 1 << 3,
NthOfType = 1 << 4,
}
const mostSpecificMask = 0b11111
@@ -83,7 +93,7 @@ enum Selector {
Id,
Class,
Attributes,
NthOfType
NthOfType,
}
interface Rule {
@@ -112,7 +122,9 @@ class ElementSelectorBuilder {
if (Array.isArray(rule.value) && rule.value.length === 0) {
return
}
if (rule.type === Selector.Id) { this.hasId = true }
if (rule.type === Selector.Id) {
this.hasId = true
}
this.rules.push(rule)
}
@@ -134,21 +146,21 @@ class ElementSelectorBuilder {
continue
}
if (
!(mask & SpecificityFlags.Attributes) &&
rule.type === Selector.Attributes
!(mask & SpecificityFlags.Attributes)
&& rule.type === Selector.Attributes
) {
continue
}
if (
!(mask & SpecificityFlags.NthOfType) &&
rule.type === Selector.NthOfType
!(mask & SpecificityFlags.NthOfType)
&& rule.type === Selector.NthOfType
) {
continue
}
if (
this.hasId &&
mask & SpecificityFlags.Id &&
rule.type === Selector.Class
this.hasId
&& mask & SpecificityFlags.Id
&& rule.type === Selector.Class
) {
continue
}
@@ -180,7 +192,9 @@ class ElementSelectorBuilder {
selector += `:nth-of-type(${rule.value})`
break
}
default: { /* Unreachable */ }
default: {
/* Unreachable */
}
}
}
return selector
@@ -193,11 +207,13 @@ const cssSelectorFromElement = (elem: Element): ElementSelectorBuilder => {
const builder = new ElementSelectorBuilder(elem)
// ID
if (elem.id.length > 0 &&
document.querySelectorAll(`#${elem.id}`).length === 1) {
if (
elem.id.length > 0
&& document.querySelectorAll(`#${elem.id}`).length === 1
) {
builder.addRule({
type: Selector.Id,
value: CSS.escape(elem.id)
value: CSS.escape(elem.id),
})
}
@@ -205,7 +221,7 @@ const cssSelectorFromElement = (elem: Element): ElementSelectorBuilder => {
if (elem.classList.length > 0) {
builder.addRule({
type: Selector.Class,
value: Array.from(elem.classList).map((c: string) => CSS.escape(c))
value: Array.from(elem.classList).map((c: string) => CSS.escape(c)),
})
}
@@ -221,7 +237,7 @@ const cssSelectorFromElement = (elem: Element): ElementSelectorBuilder => {
if (url !== undefined && url.length > 0) {
attributes.push({
attr: 'href',
value: url
value: url,
})
}
break
@@ -231,7 +247,7 @@ const cssSelectorFromElement = (elem: Element): ElementSelectorBuilder => {
if (url !== undefined && url.length > 0) {
attributes.push({
attr: 'src',
value: url.slice(0, 256)
value: url.slice(0, 256),
})
}
break
@@ -249,43 +265,47 @@ const cssSelectorFromElement = (elem: Element): ElementSelectorBuilder => {
if (alttext !== undefined && alttext.length > 0) {
attributes.push({
attr: 'alt',
value: alttext
value: alttext,
})
}
} else {
attributes.push({
attr: 'src',
value: data
value: data,
})
}
break
}
default: { break }
default: {
break
}
}
if (attributes.length > 0) {
builder.addRule({
type: Selector.Attributes,
value: attributes
value: attributes,
})
}
}
const querySelectorNoExcept = (
node: Element | null,
selector: string
selector: string,
): Element[] => {
if (node !== null) {
try {
const r = node.querySelectorAll(selector)
return Array.from(r)
} catch { /* Deliberately left empty */ }
} catch {
/* Deliberately left empty */
}
}
return []
}
if (
builder.size() === 0 ||
querySelectorNoExcept(elem.parentElement, builder.toString()).length > 1
builder.size() === 0
|| querySelectorNoExcept(elem.parentElement, builder.toString()).length > 1
) {
builder.addTag(tag)
if (
@@ -294,12 +314,14 @@ const cssSelectorFromElement = (elem: Element): ElementSelectorBuilder => {
let index = 1
let sibling: Element | null = elem.previousElementSibling
while (sibling !== null) {
if (sibling.localName === tag) { index++ }
if (sibling.localName === tag) {
index++
}
sibling = sibling.previousElementSibling
}
builder.addRule({
type: Selector.NthOfType,
value: index
value: index,
})
}
}
@@ -406,14 +428,15 @@ class TargetsCollection {
this.targets.length = 0
elems.forEach((elem: Element) => {
this.targets.push(new Target(elem))
});
})
}
forceRecalcCoords() {
this.targets.forEach(t => t.forceRecalcCoords())
this.targets.forEach((t) => t.forceRecalcCoords())
// for case when element no longer in the DOM
this.targets = this.targets.filter(item =>
item.coord.height !== 0 && item.coord.width !== 0)
this.targets = this.targets.filter(
(item) => item.coord.height !== 0 && item.coord.width !== 0,
)
if (this.targets.length === 0 && this.togglePicker) {
this.togglePicker(false)
}
@@ -430,12 +453,12 @@ const targetRectFromElement = (elem: Element): TargetRect => {
x: rect.left,
y: rect.top,
width: rect.right - rect.left,
height: rect.bottom - rect.top
height: rect.bottom - rect.top,
}
}
let lastHoveredElem: HTMLElement | null = null
const targetedElems = new TargetsCollection
const targetedElems = new TargetsCollection()
const recalculateAndSendTargets = (elems: Element[] | null) => {
if (elems) {
@@ -459,17 +482,19 @@ const hideByCssSelector = (selector: string) => {
}
interface SliderOptions {
onChange?: (value: number) => void;
onChange?: (value: number) => void
}
interface SliderAPI {
getValue: () => number;
min: number;
max: number;
getValue: () => number
min: number
max: number
}
const onTargetSelected = (selected: Element | null, index: number): string => {
if (lastHoveredElem === null) { return '' }
if (lastHoveredElem === null) {
return ''
}
let elem: Element | null = selected
const selectorBuilders = []
@@ -478,7 +503,7 @@ const onTargetSelected = (selected: Element | null, index: number): string => {
0b11101, // No DOM hierarchy
0b01011, // No nth-of-type, no attributes
0b10011, // No attributes, no class names
0b11111 // All selector rules (default)
0b11111, // All selector rules (default)
]
const mask: number = specificityMasks[index]
@@ -496,8 +521,10 @@ const onTargetSelected = (selected: Element | null, index: number): string => {
for (; i < selectorBuilders.length; i++) {
const b = selectorBuilders[i]
try {
if ((mask & SpecificityFlags.Id) && b.hasId ||
document.querySelectorAll(b.toString(mask)).length === 1) {
if (
(mask & SpecificityFlags.Id && b.hasId)
|| document.querySelectorAll(b.toString(mask)).length === 1
) {
break
}
} catch {
@@ -521,9 +548,9 @@ const elementPickerHoverCoordsChanged = (x: number, y: number) => {
}
const getElementBySelector = (selector: string) => {
let elements: Element[] | null;
const nodeList = document.querySelectorAll(selector);
elements = nodeList.length > 0 ? Array.from(nodeList) : null;
let elements: Element[] | null
const nodeList = document.querySelectorAll(selector)
elements = nodeList.length > 0 ? Array.from(nodeList) : null
return elements
}
@@ -533,7 +560,7 @@ const elementPickerUserSelectedTarget = (specificity: number) => {
if (selector !== '') {
try {
recalculateAndSendTargets(getElementBySelector(selector))
} catch { }
} catch {}
}
return {
isValid: selector !== '',
@@ -550,12 +577,14 @@ const elementPickerUserModifiedRule = (selector: string) => {
if (selector.length > 0) {
try {
recalculateAndSendTargets(Array.from(document.querySelectorAll(selector)))
} catch { }
} catch {}
}
}
const setShowRulesHiddenBtnState = (
showRulesButton: HTMLElement | null, show: boolean) => {
showRulesButton: HTMLElement | null,
show: boolean,
) => {
if (!showRulesButton) return
showRulesButton.textContent = show ? btnHideRulesBoxText : btnShowRulesBoxText
}
@@ -565,49 +594,50 @@ const setMinimizeState = (minimized: boolean) => {
pickerDiv.classList.toggle('minimized', minimized)
}
function initSlider(element: HTMLElement
| null, options: SliderOptions = {}): SliderAPI | undefined {
if (!element) return;
function initSlider(
element: HTMLElement | null,
options: SliderOptions = {},
): SliderAPI | undefined {
if (!element) return
const inputElement = element as HTMLInputElement
if (!inputElement) return;
const inputElement = element as HTMLInputElement
if (!inputElement) return
const min = parseInt(inputElement.min ?? '1', 10);
const max = parseInt(inputElement.max ?? '4', 10);
const min = parseInt(inputElement.min ?? '1', 10)
const max = parseInt(inputElement.max ?? '4', 10)
const initialValue = 4
inputElement.tabIndex = 0;
inputElement.tabIndex = 0
let currentValue = initialValue;
let currentValue = initialValue
const updateSlider = (fireEvent: boolean): number => {
const value = parseFloat(inputElement.value)
const currMin = parseFloat(inputElement.min)
const currMax = parseFloat(inputElement.max)
const value = parseFloat(inputElement.value);
const currMin = parseFloat(inputElement.min);
const currMax = parseFloat(inputElement.max);
const percentage = ((value - currMin) / (currMax - currMin)) * 100
const percentage = ((value - currMin) / (currMax - currMin)) * 100;
inputElement.style.setProperty('--value', `${percentage}%`)
inputElement.style.setProperty('--value', `${percentage}%`);
currentValue = value;
currentValue = value
if (fireEvent && options.onChange) {
options.onChange(currentValue);
options.onChange(currentValue)
}
return value;
};
return value
}
inputElement.addEventListener('input', () => updateSlider(true));
inputElement.addEventListener('input', () => updateSlider(true))
// Initial update
updateSlider(false);
updateSlider(false)
// Return API for external control
return {
getValue: () => currentValue,
min,
max
};
max,
}
}
const launchElementPicker = (root: ShadowRoot) => {
@@ -635,15 +665,15 @@ const launchElementPicker = (root: ShadowRoot) => {
}
const maximizeButton = root.getElementById('desktop-min-icon-container')!
maximizeButton.addEventListener('click', () => {
setMinimizeState(false);
setMinimizeState(false)
})
const sliderElement = root.getElementById('custom-slider');
const sliderElement = root.getElementById('custom-slider')
const slider = initSlider(sliderElement, {
onChange: () => {
dispatchSelect()
}
});
},
})
root.addEventListener(
'keydown',
@@ -658,8 +688,10 @@ const launchElementPicker = (root: ShadowRoot) => {
)
const svg = root.getElementById('picker-ui')!
if (window.matchMedia("(pointer: fine)").matches &&
window.matchMedia("(hover: hover)").matches) {
if (
window.matchMedia('(pointer: fine)').matches
&& window.matchMedia('(hover: hover)').matches
) {
svg.addEventListener(
'mousemove',
(event) => {
@@ -692,14 +724,14 @@ const launchElementPicker = (root: ShadowRoot) => {
const section = root.getElementById('main-section')!
const enableButtons = (isDisabled: boolean) => {
const elements = root.querySelectorAll('.button');
elements.forEach(element => {
const elements = root.querySelectorAll('.button')
elements.forEach((element) => {
if (isDisabled) {
element.classList.add('disabled')
} else {
element.classList.remove('disabled');
element.classList.remove('disabled')
}
});
})
}
if (!isAndroid) {
@@ -725,8 +757,7 @@ const launchElementPicker = (root: ShadowRoot) => {
}
const setTitleBarColor = (bgcolor: number) => {
const section = root.host as HTMLElement
if (section)
{
if (section) {
const r = (bgcolor >> 16) & 0xff
const g = (bgcolor >> 8) & 0xff
const b = bgcolor & 0xff
@@ -736,23 +767,24 @@ const launchElementPicker = (root: ShadowRoot) => {
const retrieveTheme = () => {
api.getElementPickerThemeInfo(
(isDarkModeEnabled: boolean, bgcolor: number) => {
const bgcolorMaskOut = bgcolor & 0xFFFFFF
const bgcolorMaskOut = bgcolor & 0xffffff
const colorHex = `#${bgcolorMaskOut.toString(16).padStart(6, '0')}`
section.style.setProperty('--theme-background-color', colorHex)
setTitleBarColor(bgcolor)
dispatchSelect()
})
},
)
}
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)')
const handleColorSchemeChange = (event: MediaQueryListEvent) => {
retrieveTheme()
};
prefersDarkScheme.addEventListener('change', handleColorSchemeChange);
}
prefersDarkScheme.addEventListener('change', handleColorSchemeChange)
retrieveTheme()
const dispatchSelect = () => {
const { isValid, selector } = elementPickerUserSelectedTarget(
slider?.getValue() ?? 4
slider?.getValue() ?? 4,
)
hasSelectedTarget = isValid
@@ -763,12 +795,12 @@ const launchElementPicker = (root: ShadowRoot) => {
const oneClickEventHandler = (event: MouseEvent | TouchEvent) => {
let elem: Element | null = null
setMinimizeState(false);
setMinimizeState(false)
if (event instanceof MouseEvent) {
elem = elementFromFrameCoords(event.clientX, event.clientY)
} else if (event instanceof TouchEvent) {
const touch = event.touches[0];
const touch = event.touches[0]
elem = elementFromFrameCoords(touch.clientX, touch.clientY)
}
@@ -802,15 +834,17 @@ const launchElementPicker = (root: ShadowRoot) => {
const manageButton = root.getElementById('btn-manage')!
manageButton.addEventListener('click', () => {
api.cosmeticFilterManage();
api.cosmeticFilterManage()
})
const toggleDisplay = (target: HTMLElement | null,
trigger: HTMLElement | null) => {
const toggleDisplay = (
target: HTMLElement | null,
trigger: HTMLElement | null,
) => {
if (!target || !trigger) {
return
}
trigger.addEventListener('click', e => {
trigger.addEventListener('click', (e) => {
if (target.style.display !== 'block') {
target.style.display = 'block'
setShowRulesHiddenBtnState(trigger, true)
@@ -830,10 +864,10 @@ const highlightElements = () => {
const svg = shadowRoot.getElementById('picker-ui')!
const svgMask = shadowRoot.getElementById('highlight-mask')!
svg.querySelectorAll('.mask').forEach(el => el.remove());
svg.querySelectorAll('.mask').forEach((el) => el.remove())
const svgMaskFragment = document.createDocumentFragment();
const svgFragment = document.createDocumentFragment();
const svgMaskFragment = document.createDocumentFragment()
const svgFragment = document.createDocumentFragment()
const createMaskElement = (): SVGRectElement => {
const mask = document.createElementNS(NSSVG, 'rect')
@@ -847,11 +881,11 @@ const highlightElements = () => {
for (const target of targetedElems.targets) {
// Add the mask to the SVG definition so the dark background is removed
const mask = createMaskElement()
mask.x.baseVal.value = target.coord.x;
mask.y.baseVal.value = target.coord.y;
mask.width.baseVal.value = target.coord.width;
mask.height.baseVal.value = target.coord.height;
mask.rx.baseVal.value = 10;
mask.x.baseVal.value = target.coord.x
mask.y.baseVal.value = target.coord.y
mask.width.baseVal.value = target.coord.width
mask.height.baseVal.value = target.coord.height
mask.rx.baseVal.value = 10
svgMaskFragment.appendChild(mask)
// Use the same element, but add the target class which turns the
@@ -866,13 +900,15 @@ const highlightElements = () => {
svg.appendChild(svgFragment)
}
const localizeTextData = (root: ShadowRoot,
const localizeTextData = (
root: ShadowRoot,
btnCrDisText: string,
btnCrEnblText: string,
btnManageText: string,
btnShowRulesText: string,
btnHideRulesText: string,
btnQuitText: string) => {
btnQuitText: string,
) => {
btnCreateDisabledText = btnCrDisText
btnCreateEnabledText = btnCrEnblText
btnShowRulesBoxText = btnShowRulesText
@@ -900,18 +936,26 @@ if (!active) {
isAndroid = api.getPlatform() === 'android'
const root = attachElementPicker()
api.getLocalizedTexts(
(btnCreateDisabledText: string,
(
btnCreateDisabledText: string,
btnCreateEnabledText: string,
btnManageText: string,
btnShowRulesBoxText: string,
btnHideRulesBoxText: string,
btnQuitText: string) => {
localizeTextData(root, btnCreateDisabledText,
btnCreateEnabledText, btnManageText,
btnShowRulesBoxText, btnHideRulesBoxText,
btnQuitText)
btnQuitText: string,
) => {
localizeTextData(
root,
btnCreateDisabledText,
btnCreateEnabledText,
btnManageText,
btnShowRulesBoxText,
btnHideRulesBoxText,
btnQuitText,
)
launchElementPicker(root)
});
},
)
} else {
active.classList.toggle('minimized', false)
}
@@ -23,7 +23,8 @@ type OperatorResult = HTMLElement[]
type UnboundStringFunc = (arg: string, element: HTMLElement) => OperatorResult
type UnboundChildRuleOrStringFunc = (
arg: string | ProceduralSelector,
element: HTMLElement) => OperatorResult
element: HTMLElement,
) => OperatorResult
type UnboundOperatorFunc = UnboundStringFunc | UnboundChildRuleOrStringFunc
type OperatorFunc = (element: HTMLElement) => OperatorResult
@@ -51,7 +52,7 @@ type KeyValueMatchRules = [
const W = window
const _asHTMLElement = (node: Node): HTMLElement | null => {
return (node instanceof HTMLElement) ? node : null
return node instanceof HTMLElement ? node : null
}
const _compileRegEx = (regexText: string): RegExp => {
@@ -78,7 +79,11 @@ const _compileRegEx = (regexText: string): RegExp => {
//
// If `exact` is true, then the string case it tested
// for an exact match (the regex case is not affected).
const _testMatches = (test: string, value: string, exact: boolean = false): boolean => {
const _testMatches = (
test: string,
value: string,
exact: boolean = false,
): boolean => {
if (test[0] === '/') {
return value.match(_compileRegEx(test)) !== null
}
@@ -110,7 +115,7 @@ const _extractKeyFromStr = (text: string): [string, number?] => {
let key = text
if (isQuotedCase) {
if (!text.endsWith('"')) {
throw new Error(`Quoted value '${text}' does not terminate with quote`);
throw new Error(`Quoted value '${text}' does not terminate with quote`)
}
key = text.slice(1, text.length - 1)
}
@@ -123,17 +128,21 @@ const _extractKeyFromStr = (text: string): [string, number?] => {
return [testCaseStr, finalNeedlePosition]
}
const _extractValueMatchRuleFromStr = (text: string,
uriEncode = false,
needlePosition = 0): TextMatchRule => {
const _extractValueMatchRuleFromStr = (
text: string,
uriEncode = false,
needlePosition = 0,
): TextMatchRule => {
const testCaseStr = _extractValueFromStr(text, uriEncode, needlePosition)
const testCaseFunc = _testMatches.bind(undefined, testCaseStr)
return testCaseFunc
}
const _extractValueFromStr = (text: string,
uriEncode = false,
needlePosition = 0): string => {
const _extractValueFromStr = (
text: string,
uriEncode = false,
needlePosition = 0,
): string => {
const isQuotedCase = text[needlePosition] === '"'
let endIndex: number
@@ -141,18 +150,20 @@ const _extractValueFromStr = (text: string,
if (text.at(-1) !== '"') {
throw new Error(
`Unable to parse value rule from ${text}. Value rule starts with `
+ '" but doesn\'t end with "')
+ '" but doesn\'t end with "',
)
}
needlePosition += 1
endIndex = text.length - 1
}
else {
} else {
endIndex = text.length
}
let testCaseStr = text.slice(needlePosition, endIndex)
if (uriEncode) {
testCaseStr = testCaseStr.replace(/\P{ASCII}/gu, c => encodeURIComponent(c))
testCaseStr = testCaseStr.replace(/\P{ASCII}/gu, (c) =>
encodeURIComponent(c),
)
}
return testCaseStr
@@ -222,27 +233,30 @@ const _nextSiblingElement = (element: HTMLElement): HTMLElement | null => {
const _allChildren = (element: HTMLElement): HTMLElement[] => {
return W.Array.from(element.children)
.map(e => _asHTMLElement(e))
.filter(e => e !== null)
.map((e) => _asHTMLElement(e))
.filter((e) => e !== null)
}
const _allChildrenRecursive = (element: HTMLElement): HTMLElement[] => {
return W.Array.from(element.querySelectorAll(':scope *'))
.map(e => _asHTMLElement(e))
.filter(e => e !== null)
.map((e) => _asHTMLElement(e))
.filter((e) => e !== null)
}
const _stripCssOperator = (operator: string, selector: string) => {
if (selector[0] !== operator) {
throw new Error(
`Expected to find ${operator} in initial position of "${selector}`)
`Expected to find ${operator} in initial position of "${selector}`,
)
}
return selector.replace(operator, '').trimStart()
}
// Implementation of ":css-selector" rule
const operatorCssSelector = (selector: CSSSelector,
element: HTMLElement): OperatorResult => {
const operatorCssSelector = (
selector: CSSSelector,
element: HTMLElement,
): OperatorResult => {
const trimmedSelector = selector.trimStart()
if (trimmedSelector.startsWith('+')) {
const subOperator = _stripCssOperator('+', trimmedSelector)
@@ -254,24 +268,21 @@ const operatorCssSelector = (selector: CSSSelector,
return []
}
return nextSibNode.matches(subOperator) ? [nextSibNode] : []
}
else if (trimmedSelector.startsWith('~')) {
} else if (trimmedSelector.startsWith('~')) {
const subOperator = _stripCssOperator('~', trimmedSelector)
if (subOperator === null) {
return []
}
const allSiblingNodes = _allOtherSiblings(element)
return allSiblingNodes.filter(x => x.matches(subOperator))
}
else if (trimmedSelector.startsWith('>')) {
return allSiblingNodes.filter((x) => x.matches(subOperator))
} else if (trimmedSelector.startsWith('>')) {
const subOperator = _stripCssOperator('>', trimmedSelector)
if (subOperator === null) {
return []
}
const allChildNodes = _allChildren(element)
return allChildNodes.filter(x => x.matches(subOperator))
}
else if (selector.startsWith(' ')) {
return allChildNodes.filter((x) => x.matches(subOperator))
} else if (selector.startsWith(' ')) {
return Array.from(element.querySelectorAll(':scope ' + trimmedSelector))
}
@@ -281,13 +292,17 @@ const operatorCssSelector = (selector: CSSSelector,
return []
}
const _hasPlainSelectorCase = (selector: CSSSelector,
element: HTMLElement): OperatorResult => {
const _hasPlainSelectorCase = (
selector: CSSSelector,
element: HTMLElement,
): OperatorResult => {
return element.matches(selector) ? [element] : []
}
const _hasProceduralSelectorCase = (selector: ProceduralSelector,
element: HTMLElement): OperatorResult => {
const _hasProceduralSelectorCase = (
selector: ProceduralSelector,
element: HTMLElement,
): OperatorResult => {
const shouldBeGreedy = selector[0]?.type !== 'css-selector'
const initElements = shouldBeGreedy
? _allChildrenRecursive(element)
@@ -297,49 +312,59 @@ const _hasProceduralSelectorCase = (selector: ProceduralSelector,
}
// Implementation of ":has" rule
const operatorHas = (instruction: CSSSelector | ProceduralSelector,
element: HTMLElement): OperatorResult => {
const operatorHas = (
instruction: CSSSelector | ProceduralSelector,
element: HTMLElement,
): OperatorResult => {
if (W.Array.isArray(instruction)) {
return _hasProceduralSelectorCase(instruction, element)
}
else {
} else {
return _hasPlainSelectorCase(instruction, element)
}
}
// Implementation of ":has-text" rule
const operatorHasText = (instruction: string,
element: HTMLElement): OperatorResult => {
const operatorHasText = (
instruction: string,
element: HTMLElement,
): OperatorResult => {
const text = element.innerText
const valueTest = _extractValueMatchRuleFromStr(instruction)
return valueTest(text) ? [element] : []
}
const _notPlainSelectorCase = (selector: CSSSelector,
element: HTMLElement): OperatorResult => {
const _notPlainSelectorCase = (
selector: CSSSelector,
element: HTMLElement,
): OperatorResult => {
return element.matches(selector) ? [] : [element]
}
const _notProceduralSelectorCase = (selector: ProceduralSelector,
element: HTMLElement): OperatorResult => {
const _notProceduralSelectorCase = (
selector: ProceduralSelector,
element: HTMLElement,
): OperatorResult => {
const matches = compileAndApplyProceduralSelector(selector, [element])
return matches.length === 0 ? [element] : []
}
// Implementation of ":not" rule
const operatorNot = (instruction: CSSSelector | ProceduralSelector,
element: HTMLElement): OperatorResult => {
const operatorNot = (
instruction: CSSSelector | ProceduralSelector,
element: HTMLElement,
): OperatorResult => {
if (Array.isArray(instruction)) {
return _notProceduralSelectorCase(instruction, element)
}
else {
} else {
return _notPlainSelectorCase(instruction, element)
}
}
// Implementation of ":matches-property" rule
const operatorMatchesProperty = (instruction: string,
element: HTMLElement): OperatorResult => {
const operatorMatchesProperty = (
instruction: string,
element: HTMLElement,
): OperatorResult => {
const [keyTest, valueTest] = _parseKeyValueMatchRules(instruction)
for (const [propName, propValue] of Object.entries(element)) {
if (!keyTest(propName)) {
@@ -354,8 +379,10 @@ const operatorMatchesProperty = (instruction: string,
}
// Implementation of ":min-text-length" rule
const operatorMinTextLength = (instruction: string,
element: HTMLElement): OperatorResult => {
const operatorMinTextLength = (
instruction: string,
element: HTMLElement,
): OperatorResult => {
const minLength = +instruction
if (minLength === W.NaN) {
throw new Error(`min-text-length: Invalid arg, ${instruction}`)
@@ -364,15 +391,20 @@ const operatorMinTextLength = (instruction: string,
}
// Implementation of ":matches-attr" rule
const operatorMatchesAttr = (instruction: string,
element: HTMLElement): OperatorResult => {
const operatorMatchesAttr = (
instruction: string,
element: HTMLElement,
): OperatorResult => {
const [keyTest, valueTest] = _parseKeyValueMatchRules(instruction)
for (const attrName of element.getAttributeNames()) {
if (!keyTest(attrName)) {
continue
}
const attrValue = element.getAttribute(attrName)
if (attrValue === null || (valueTest !== undefined && !valueTest(attrValue))) {
if (
attrValue === null
|| (valueTest !== undefined && !valueTest(attrValue))
) {
continue
}
return [element]
@@ -381,9 +413,11 @@ const operatorMatchesAttr = (instruction: string,
}
// Implementation of ":matches-css-*" rules
const operatorMatchesCSS = (beforeOrAfter: string | null,
cssInstruction: string,
element: HTMLElement): OperatorResult => {
const operatorMatchesCSS = (
beforeOrAfter: string | null,
cssInstruction: string,
element: HTMLElement,
): OperatorResult => {
const [cssKey, expectedVal] = _parseCSSInstruction(cssInstruction)
const elmStyle = W.getComputedStyle(element, beforeOrAfter)
const styleValue = elmStyle.getPropertyValue(cssKey)
@@ -402,21 +436,27 @@ const operatorMatchesCSS = (beforeOrAfter: string | null,
}
// Implementation of ":matches-media" rule
const operatorMatchesMedia = (instruction: string,
element: HTMLElement): OperatorResult => {
const operatorMatchesMedia = (
instruction: string,
element: HTMLElement,
): OperatorResult => {
return W.matchMedia(instruction).matches ? [element] : []
}
// Implementation of ":matches-path" rule
const operatorMatchesPath = (instruction: string,
element: HTMLElement): OperatorResult => {
const operatorMatchesPath = (
instruction: string,
element: HTMLElement,
): OperatorResult => {
const pathAndQuery = W.location.pathname + W.location.search
const matchRule = _extractValueMatchRuleFromStr(instruction, true)
return matchRule(pathAndQuery) ? [element] : []
}
const _upwardIntCase = (intNeedle: NeedlePosition,
element: HTMLElement): OperatorResult => {
const _upwardIntCase = (
intNeedle: NeedlePosition,
element: HTMLElement,
): OperatorResult => {
if (intNeedle < 1 || intNeedle >= 256) {
throw new Error(`upward: invalid arg, ${intNeedle}`)
}
@@ -427,15 +467,16 @@ const _upwardIntCase = (intNeedle: NeedlePosition,
}
if (currentElement === null) {
return []
}
else {
} else {
const htmlElement = _asHTMLElement(currentElement)
return (htmlElement === null) ? [] : [htmlElement]
return htmlElement === null ? [] : [htmlElement]
}
}
const _upwardProceduralSelectorCase = (selector: ProceduralSelector,
element: HTMLElement): OperatorResult => {
const _upwardProceduralSelectorCase = (
selector: ProceduralSelector,
element: HTMLElement,
): OperatorResult => {
const childFilter = compileProceduralSelector(selector)
let needle: ParentNode | HTMLElement | null = element
while (needle !== null) {
@@ -452,8 +493,10 @@ const _upwardProceduralSelectorCase = (selector: ProceduralSelector,
return []
}
const _upwardPlainSelectorCase = (selector: CSSSelector,
element: HTMLElement): OperatorResult => {
const _upwardPlainSelectorCase = (
selector: CSSSelector,
element: HTMLElement,
): OperatorResult => {
let needle: ParentNode | HTMLDocument | null = element
while (needle !== null) {
const currentElement = _asHTMLElement(needle)
@@ -469,25 +512,31 @@ const _upwardPlainSelectorCase = (selector: CSSSelector,
}
// Implementation of ":upward" rule
const operatorUpward = (instruction: string | ProceduralSelector,
element: HTMLElement): OperatorResult => {
const operatorUpward = (
instruction: string | ProceduralSelector,
element: HTMLElement,
): OperatorResult => {
if (W.Number.isInteger(+instruction)) {
return _upwardIntCase(+instruction, element)
}
else if (W.Array.isArray(instruction)) {
} else if (W.Array.isArray(instruction)) {
return _upwardProceduralSelectorCase(instruction, element)
}
else {
} else {
return _upwardPlainSelectorCase(instruction, element)
}
}
// Implementation of ":xpath" rule
const operatorXPath = (instruction: string,
element: HTMLElement): HTMLElement[] => {
const result = W.document.evaluate(instruction, element, null,
W.XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
null)
const operatorXPath = (
instruction: string,
element: HTMLElement,
): HTMLElement[] => {
const result = W.document.evaluate(
instruction,
element,
null,
W.XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
null,
)
const matches: HTMLElement[] = []
let currentNode: Node | null
while ((currentNode = result.iterateNext())) {
@@ -517,13 +566,17 @@ const ruleTypeToFuncMap: Record<OperatorType, UnboundOperatorFunc> = {
'xpath': operatorXPath,
}
const compileProceduralSelector = (operators: ProceduralSelector): CompiledProceduralSelector => {
const compileProceduralSelector = (
operators: ProceduralSelector,
): CompiledProceduralSelector => {
const outputOperatorList = []
for (const operator of operators) {
const anOperatorFunc = ruleTypeToFuncMap[operator.type]
const args = [operator.arg]
if (anOperatorFunc === undefined) {
throw new Error(`Not sure what to do with operator of type ${operator.type}`)
throw new Error(
`Not sure what to do with operator of type ${operator.type}`,
)
}
outputOperatorList.push({
@@ -540,13 +593,12 @@ const compileProceduralSelector = (operators: ProceduralSelector): CompiledProce
// independent of the passed element. We use this list to optimize
// applying each operator (i.e., we just check the first element, and then
// accept or reject all elements in the consideration set accordingly).
const fastPathOperatorTypes: OperatorType[] = [
'matches-media',
'matches-path',
]
const fastPathOperatorTypes: OperatorType[] = ['matches-media', 'matches-path']
const _determineInitNodesAndIndex = (selector: CompiledProceduralSelector,
initNodes?: HTMLElement[]): [number, HTMLElement[]] => {
const _determineInitNodesAndIndex = (
selector: CompiledProceduralSelector,
initNodes?: HTMLElement[],
): [number, HTMLElement[]] => {
let nodesToConsider: HTMLElement[] = []
let index = 0
@@ -561,21 +613,18 @@ const _determineInitNodesAndIndex = (selector: CompiledProceduralSelector,
if (initNodes !== undefined) {
nodesToConsider = W.Array.from(initNodes)
}
else if (firstOperatorType === 'css-selector') {
} else if (firstOperatorType === 'css-selector') {
const selector = firstArg as CSSSelector
// Case two: we're considering the entire document, and the first operator
// is a 'css-selector'. Here, we just special case using querySelectorAll
// instead of starting with the full set of possible nodes.
nodesToConsider = W.Array.from(W.document.querySelectorAll(selector))
index += 1
}
else if (firstOperatorType === 'xpath') {
} else if (firstOperatorType === 'xpath') {
const xpath = firstArg as string
nodesToConsider = operatorXPath(xpath, W.document.documentElement)
index += 1
}
else {
} else {
// Case three: we gotta apply the first operator to the entire document.
// Yuck but un-avoidable.
const allNodes = W.Array.from(W.document.all)
@@ -584,8 +633,10 @@ const _determineInitNodesAndIndex = (selector: CompiledProceduralSelector,
return [index, nodesToConsider]
}
const applyCompiledSelector = (selector: CompiledProceduralSelector,
initNodes?: HTMLElement[]): HTMLElement[] => {
const applyCompiledSelector = (
selector: CompiledProceduralSelector,
initNodes?: HTMLElement[],
): HTMLElement[] => {
const initState = _determineInitNodesAndIndex(selector, initNodes)
let [index, nodesToConsider] = initState
const numOperators = selector.length
@@ -618,8 +669,10 @@ const applyCompiledSelector = (selector: CompiledProceduralSelector,
return nodesToConsider
}
const compileAndApplyProceduralSelector = (selector: ProceduralSelector,
initElements: HTMLElement[]): HTMLElement[] => {
const compileAndApplyProceduralSelector = (
selector: ProceduralSelector,
initElements: HTMLElement[],
): HTMLElement[] => {
const compiled = compileProceduralSelector(selector)
return applyCompiledSelector(compiled, initElements)
}