From 9f7d33fddd94baf10efe071c28871cc45e28f919 Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Fri, 17 Oct 2025 15:59:57 +0200 Subject: [PATCH 1/7] Add TypeScript types to element-hiding feature - Add JSDoc type definitions matching element-hiding.ts schema - Cast getFeatureSetting return values to proper types with fallbacks - Update function parameter types to use new JSDoc types - Add type guards for union types (ElementHidingRule) - Preserve all original comments from codebase - Fix TypeScript compilation errors with proper null checks This integrates the refactored schema types from privacy-configuration into the content-scope-scripts element-hiding feature for better type safety and maintainability. --- injected/src/features/element-hiding.js | 119 +++++++++++++++++------- 1 file changed, 83 insertions(+), 36 deletions(-) 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); From ce9b439e8a24775d425ade2c37d9836bd41a903f Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Mon, 20 Oct 2025 16:50:03 +0200 Subject: [PATCH 2/7] Restore missing comment --- injected/src/features/element-hiding.js | 1 + 1 file changed, 1 insertion(+) diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index 8ad2890a0d..18eb9cf230 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -374,6 +374,7 @@ export default class ElementHiding extends ContentFeature { /** @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; } From 768fb2eec599a6f8b5b0facbdfc96dd851ee679b Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Thu, 23 Oct 2025 19:04:17 +0200 Subject: [PATCH 3/7] Update element-hiding types and remove unnecessary selector guards - Update TypeScript definitions to match new privacy configuration schema - Split ElementHidingRuleWithSelector into ElementHidingRuleHide and ElementHidingRuleModify - Make values required for modify rules, remove from hide rules - Remove unnecessary 'selector' in rule guards from injectStyleTag() and hideAdNodes() - Add type assertions to clarify that these functions only receive rules with selectors - Improve code consistency and efficiency --- injected/src/features/element-hiding.js | 39 +++++++++++++------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index 18eb9cf230..fcefb43b58 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -8,10 +8,16 @@ import { isBeingFramed, injectGlobalStyles } from '../utils'; */ /** - * @typedef {Object} ElementHidingRuleWithSelector + * @typedef {Object} ElementHidingRuleHide * @property {string} selector - * @property {'hide-empty' | 'hide' | 'closest-empty' | 'override' | 'modify-style' | 'modify-attr'} type - * @property {ElementHidingValue[]} [values] + * @property {'hide-empty' | 'hide' | 'closest-empty' | 'override'} type + */ + +/** + * @typedef {Object} ElementHidingRuleModify + * @property {string} selector + * @property {'modify-style' | 'modify-attr'} type + * @property {ElementHidingValue[]} values */ /** @@ -20,7 +26,7 @@ import { isBeingFramed, injectGlobalStyles } from '../utils'; */ /** - * @typedef {ElementHidingRuleWithSelector | ElementHidingRuleWithoutSelector} ElementHidingRule + * @typedef {ElementHidingRuleHide | ElementHidingRuleModify | ElementHidingRuleWithoutSelector} ElementHidingRule */ /** @@ -294,12 +300,10 @@ function injectStyleTag(rules) { let selector = ''; rules.forEach((rule, i) => { - if ('selector' in rule) { - if (i !== rules.length - 1) { - selector = selector.concat(rule.selector, ','); - } else { - selector = selector.concat(rule.selector); - } + if (i !== rules.length - 1) { + selector = selector.concat(/** @type {ElementHidingRuleHide | ElementHidingRuleModify} */ (rule).selector, ','); + } else { + selector = selector.concat(/** @type {ElementHidingRuleHide | ElementHidingRuleModify} */ (rule).selector); } }); const styleTagProperties = 'display:none!important;min-height:0!important;height:0!important;'; @@ -317,14 +321,12 @@ function hideAdNodes(rules) { const document = globalThis.document; rules.forEach((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); - }); - } + const selector = forgivingSelector(/** @type {ElementHidingRuleHide | ElementHidingRuleModify} */ (rule).selector); + const matchingElementArray = [...document.querySelectorAll(selector)]; + matchingElementArray.forEach((element) => { + // @ts-expect-error https://app.asana.com/0/1201614831475344/1203979574128023/f + collapseDomNode(element, rule); + }); }); } @@ -374,7 +376,6 @@ export default class ElementHiding extends ContentFeature { /** @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; } From 689728484640626c0244c006464525a6b8f2ec9f Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Thu, 23 Oct 2025 20:01:54 +0200 Subject: [PATCH 4/7] resolve fallback inconsistency over typedef on line 367 --- injected/src/features/element-hiding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index fcefb43b58..ad7cbb2d46 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -364,7 +364,7 @@ export default class ElementHiding extends ContentFeature { let activeRules; /** @type {ElementHidingRule[]} */ - const globalRules = this.getFeatureSetting('rules') || []; + const globalRules = this.getFeatureSetting('rules'); /** @type {string[]} */ adLabelStrings = this.getFeatureSetting('adLabelStrings') || []; /** @type {boolean} */ From 491976ef53130bfca6a23a7cc8be5df2e1719a00 Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Thu, 23 Oct 2025 20:24:40 +0200 Subject: [PATCH 5/7] Clarify mediaAndFormSelectors fallback logic - Replace confusing self-referential fallback with explicit conditional - Add comment explaining fallback to default value when setting is missing - Make intent clear: fallback to hardcoded default, not preserve existing value - Addresses feedback about unclear fallback pattern --- injected/src/features/element-hiding.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index ad7cbb2d46..49b3007ba0 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -374,7 +374,11 @@ export default class ElementHiding extends ContentFeature { /** @type {number[]} */ unhideTimeouts = this.getFeatureSetting('unhideTimeouts') || unhideTimeouts; /** @type {string} */ - mediaAndFormSelectors = this.getFeatureSetting('mediaAndFormSelectors') || mediaAndFormSelectors; + mediaAndFormSelectors = this.getFeatureSetting('mediaAndFormSelectors'); + // Fall back to default value if setting is missing or malformed + if (typeof mediaAndFormSelectors === 'undefined') { + mediaAndFormSelectors = 'video,canvas,embed,object,audio,map,form,input,textarea,select,option,button'; + } if (shouldInjectStyleTag) { shouldInjectStyleTag = this.matchConditionalFeatureSetting('styleTagExceptions').length === 0; From f2365639b803e6658296a41221be866f7ef50a0c Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Thu, 23 Oct 2025 20:31:26 +0200 Subject: [PATCH 6/7] Remove null checks for modify rule values to surface configuration errors - Remove if (rule.values) checks from modify-attr and modify-style cases - Values property is required according to ElementHidingRuleModify typedef - Null checks were masking configuration errors by silently ignoring malformed rules - Now fails fast when values is missing, helping developers catch config issues early - Fixes JSDoc type mismatch between required values property and null checks --- injected/src/features/element-hiding.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index 49b3007ba0..60e2c73b34 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -106,14 +106,10 @@ function collapseDomNode(element, rule, previousElement) { } break; case 'modify-attr': - if (rule.values) { - modifyAttribute(element, rule.values); - } + modifyAttribute(element, rule.values); break; case 'modify-style': - if (rule.values) { - modifyStyle(element, rule.values); - } + modifyStyle(element, rule.values); break; default: break; From 119827ec332307ecc8d169e2850174896077bdee Mon Sep 17 00:00:00 2001 From: euw-arasolofotsara1 Date: Thu, 23 Oct 2025 20:50:32 +0200 Subject: [PATCH 7/7] Fix mediaAndFormSelectors fallback to handle all falsy values --- injected/src/features/element-hiding.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/injected/src/features/element-hiding.js b/injected/src/features/element-hiding.js index 60e2c73b34..25629f583e 100644 --- a/injected/src/features/element-hiding.js +++ b/injected/src/features/element-hiding.js @@ -371,8 +371,8 @@ export default class ElementHiding extends ContentFeature { unhideTimeouts = this.getFeatureSetting('unhideTimeouts') || unhideTimeouts; /** @type {string} */ mediaAndFormSelectors = this.getFeatureSetting('mediaAndFormSelectors'); - // Fall back to default value if setting is missing or malformed - if (typeof mediaAndFormSelectors === 'undefined') { + // Fall back to default value if setting is missing, null, empty, or other falsy values + if (!mediaAndFormSelectors) { mediaAndFormSelectors = 'video,canvas,embed,object,audio,map,form,input,textarea,select,option,button'; }