diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index a259331377..8ad2890a0d 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -1,6 +1,52 @@ import ContentFeature from '../content-feature'; import { isBeingFramed, injectGlobalStyles } from '../utils'; +/** + * @typedef {Object} ElementHidingValue + * @property {string} property + * @property {string} value + */ + +/** + * @typedef {Object} ElementHidingRuleWithSelector + * @property {string} selector + * @property {'hide-empty' | 'hide' | 'closest-empty' | 'override' | 'modify-style' | 'modify-attr'} type + * @property {ElementHidingValue[]} [values] + */ + +/** + * @typedef {Object} ElementHidingRuleWithoutSelector + * @property {'disable-default'} type + */ + +/** + * @typedef {ElementHidingRuleWithSelector | ElementHidingRuleWithoutSelector} ElementHidingRule + */ + +/** + * @typedef {Object} ElementHidingDomain + * @property {string | string[]} domain + * @property {ElementHidingRule[]} rules + */ + +/** + * @typedef {Object} StyleTagException + * @property {string} domain + * @property {string} reason + */ + +/** + * @typedef {Object} ElementHidingConfiguration + * @property {boolean} [useStrictHideStyleTag] + * @property {ElementHidingRule[]} rules + * @property {ElementHidingDomain[]} domains + * @property {number[]} [hideTimeouts] + * @property {number[]} [unhideTimeouts] + * @property {string} [mediaAndFormSelectors] + * @property {string[]} [adLabelStrings] + * @property {StyleTagException[]} [styleTagExceptions] + */ + let adLabelStrings = []; const parser = new DOMParser(); let hiddenElements = new WeakMap(); @@ -18,7 +64,7 @@ let featureInstance; /** * Hide DOM element if rule conditions met * @param {HTMLElement} element - * @param {Object} rule + * @param {ElementHidingRule} rule * @param {HTMLElement} [previousElement] */ function collapseDomNode(element, rule, previousElement) { @@ -54,10 +100,14 @@ function collapseDomNode(element, rule, previousElement) { } break; case 'modify-attr': - modifyAttribute(element, rule.values); + if (rule.values) { + modifyAttribute(element, rule.values); + } break; case 'modify-style': - modifyStyle(element, rule.values); + if (rule.values) { + modifyStyle(element, rule.values); + } break; default: break; @@ -67,7 +117,7 @@ function collapseDomNode(element, rule, previousElement) { /** * Unhide previously hidden DOM element if content loaded into it * @param {HTMLElement} element - * @param {Object} rule + * @param {ElementHidingRule} rule */ function expandNonEmptyDomNode(element, rule) { if (!element) { @@ -185,9 +235,7 @@ function isDomNodeEmpty(node) { /** * Modify specified attribute(s) on element * @param {HTMLElement} element - * @param {Object[]} values - * @param {string} values[].property - * @param {string} values[].value + * @param {ElementHidingValue[]} values */ function modifyAttribute(element, values) { values.forEach((item) => { @@ -199,9 +247,7 @@ function modifyAttribute(element, values) { /** * Modify specified style(s) on element * @param {HTMLElement} element - * @param {Object[]} values - * @param {string} values[].property - * @param {string} values[].value + * @param {ElementHidingValue[]} values */ function modifyStyle(element, values) { values.forEach((item) => { @@ -212,9 +258,7 @@ function modifyStyle(element, values) { /** * Separate strict hide rules to inject as style tag if enabled - * @param {Object[]} rules - * @param {string} rules[].selector - * @param {string} rules[].type + * @param {ElementHidingRule[]} rules */ function extractTimeoutRules(rules) { if (!shouldInjectStyleTag) { @@ -238,9 +282,7 @@ function extractTimeoutRules(rules) { /** * Create styletag for strict hide rules and append it to the document - * @param {Object[]} rules - * @param {string} rules[].selector - * @param {string} rules[].type + * @param {ElementHidingRule[]} rules */ function injectStyleTag(rules) { // if style tag already injected on SPA url change, don't inject again @@ -252,10 +294,12 @@ function injectStyleTag(rules) { let selector = ''; rules.forEach((rule, i) => { - if (i !== rules.length - 1) { - selector = selector.concat(rule.selector, ','); - } else { - selector = selector.concat(rule.selector); + if ('selector' in rule) { + if (i !== rules.length - 1) { + selector = selector.concat(rule.selector, ','); + } else { + selector = selector.concat(rule.selector); + } } }); const styleTagProperties = 'display:none!important;min-height:0!important;height:0!important;'; @@ -267,20 +311,20 @@ function injectStyleTag(rules) { /** * Apply list of active element hiding rules to page - * @param {Object[]} rules - * @param {string} rules[].selector - * @param {string} rules[].type + * @param {ElementHidingRule[]} rules */ function hideAdNodes(rules) { const document = globalThis.document; rules.forEach((rule) => { - const selector = forgivingSelector(rule.selector); - const matchingElementArray = [...document.querySelectorAll(selector)]; - matchingElementArray.forEach((element) => { - // @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f - collapseDomNode(element, rule); - }); + if ('selector' in rule) { + const selector = forgivingSelector(rule.selector); + const matchingElementArray = [...document.querySelectorAll(selector)]; + matchingElementArray.forEach((element) => { + // @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f + collapseDomNode(element, rule); + }); + } }); } @@ -317,14 +361,19 @@ export default class ElementHiding extends ContentFeature { } let activeRules; - const globalRules = this.getFeatureSetting('rules'); - adLabelStrings = this.getFeatureSetting('adLabelStrings'); - shouldInjectStyleTag = this.getFeatureSetting('useStrictHideStyleTag'); + /** @type {ElementHidingRule[]} */ + const globalRules = this.getFeatureSetting('rules') || []; + /** @type {string[]} */ + adLabelStrings = this.getFeatureSetting('adLabelStrings') || []; + /** @type {boolean} */ + shouldInjectStyleTag = this.getFeatureSetting('useStrictHideStyleTag') || false; + /** @type {number[]} */ hideTimeouts = this.getFeatureSetting('hideTimeouts') || hideTimeouts; + /** @type {number[]} */ unhideTimeouts = this.getFeatureSetting('unhideTimeouts') || unhideTimeouts; + /** @type {string} */ mediaAndFormSelectors = this.getFeatureSetting('mediaAndFormSelectors') || mediaAndFormSelectors; - // determine whether strict hide rules should be injected as a style tag if (shouldInjectStyleTag) { shouldInjectStyleTag = this.matchConditionalFeatureSetting('styleTagExceptions').length === 0; } @@ -377,9 +426,7 @@ export default class ElementHiding extends ContentFeature { /** * Apply relevant hiding rules to page at set intervals - * @param {Object[]} rules - * @param {string} rules[].selector - * @param {string} rules[].type + * @param {ElementHidingRule[]} rules */ applyRules(rules) { const timeoutRules = extractTimeoutRules(rules);