Skip to content

Commit 69dfe3e

Browse files
authored
Element hiding: inject strict hide rules as style tag if enabled by privacy config (#229)
* inject strict hide rules as style tag if feature flag enabled * lint * insert style tag node using appendChild * regenerate artifacts after rebase
1 parent 35631f4 commit 69dfe3e

File tree

8 files changed

+526
-211
lines changed

8 files changed

+526
-211
lines changed

Sources/ContentScopeScripts/dist/contentScope.js

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4671,6 +4671,7 @@
46714671
const parser = new DOMParser();
46724672
let hiddenElements = new WeakMap();
46734673
let appliedRules = new Set();
4674+
let shouldInjectStyleTag = false;
46744675

46754676
/**
46764677
* Hide DOM element if rule conditions met
@@ -4820,51 +4821,86 @@
48204821
* @param {string} rules[].type
48214822
*/
48224823
function applyRules (rules) {
4824+
const hideTimeouts = [0, 100, 200, 300, 400, 500, 1000, 1500, 2000, 2500, 3000];
4825+
const unhideTimeouts = [750, 1500, 2250, 3000];
4826+
const timeoutRules = extractTimeoutRules(rules);
4827+
48234828
// several passes are made to hide & unhide elements. this is necessary because we're not using
48244829
// a mutation observer but we want to hide/unhide elements as soon as possible, and ads
48254830
// frequently take from several hundred milliseconds to several seconds to load
48264831
// check at 0ms, 100ms, 200ms, 300ms, 400ms, 500ms, 1000ms, 1500ms, 2000ms, 2500ms, 3000ms
4827-
setTimeout(function () {
4828-
hideAdNodes(rules);
4829-
}, 0);
4830-
4831-
let immediateHideIterations = 0;
4832-
const immediateHideInterval = setInterval(function () {
4833-
immediateHideIterations += 1;
4834-
if (immediateHideIterations === 4) {
4835-
clearInterval(immediateHideInterval);
4836-
}
4837-
hideAdNodes(rules);
4838-
}, 100);
4839-
4840-
let delayedHideIterations = 0;
4841-
const delayedHideInterval = setInterval(function () {
4842-
delayedHideIterations += 1;
4843-
if (delayedHideIterations === 4) {
4844-
clearInterval(delayedHideInterval);
4845-
}
4846-
hideAdNodes(rules);
4847-
}, 500);
4832+
hideTimeouts.forEach((timeout) => {
4833+
setTimeout(() => {
4834+
hideAdNodes(timeoutRules);
4835+
}, timeout);
4836+
});
48484837

48494838
// check previously hidden ad elements for contents, unhide if content has loaded after hiding.
48504839
// we do this in order to display non-tracking ads that aren't blocked at the request level
48514840
// check at 750ms, 1500ms, 2250ms, 3000ms
4852-
let unhideIterations = 0;
4853-
const unhideInterval = setInterval(function () {
4854-
unhideIterations += 1;
4855-
if (unhideIterations === 3) {
4856-
clearInterval(unhideInterval);
4857-
}
4858-
unhideLoadedAds();
4859-
}, 750);
4841+
unhideTimeouts.forEach((timeout) => {
4842+
setTimeout(() => {
4843+
unhideLoadedAds();
4844+
}, timeout);
4845+
});
48604846

48614847
// clear appliedRules and hiddenElements caches once all checks have run
4862-
setTimeout(function () {
4848+
setTimeout(() => {
48634849
appliedRules = new Set();
48644850
hiddenElements = new WeakMap();
48654851
}, 3100);
48664852
}
48674853

4854+
/**
4855+
* Separate strict hide rules to inject as style tag if enabled
4856+
* @param {Object[]} rules
4857+
* @param {string} rules[].selector
4858+
* @param {string} rules[].type
4859+
*/
4860+
function extractTimeoutRules (rules) {
4861+
if (!shouldInjectStyleTag) {
4862+
return rules
4863+
}
4864+
4865+
const strictHideRules = [];
4866+
const timeoutRules = [];
4867+
4868+
rules.forEach((rule, i) => {
4869+
if (rule.type === 'hide') {
4870+
strictHideRules.push(rule);
4871+
} else {
4872+
timeoutRules.push(rule);
4873+
}
4874+
});
4875+
4876+
injectStyleTag(strictHideRules);
4877+
return timeoutRules
4878+
}
4879+
4880+
/**
4881+
* Create styletag for strict hide rules and append it to document head
4882+
* @param {Object[]} rules
4883+
* @param {string} rules[].selector
4884+
* @param {string} rules[].type
4885+
*/
4886+
function injectStyleTag (rules) {
4887+
const styleTag = document.createElement('style');
4888+
let styleTagContents = '';
4889+
4890+
rules.forEach((rule, i) => {
4891+
if (i !== rules.length - 1) {
4892+
styleTagContents = styleTagContents.concat(rule.selector, ',');
4893+
} else {
4894+
styleTagContents = styleTagContents.concat(rule.selector);
4895+
}
4896+
});
4897+
4898+
styleTagContents = styleTagContents.concat('{display:none!important;min-height:0!important;height:0!important;}');
4899+
styleTag.innerText = styleTagContents;
4900+
4901+
document.head.appendChild(styleTag);
4902+
}
4903+
48684904
/**
48694905
* Apply list of active element hiding rules to page
48704906
* @param {Object[]} rules
@@ -4905,7 +4941,16 @@
49054941
const domain = args.site.domain;
49064942
const domainRules = getFeatureSetting(featureName, args, 'domains');
49074943
const globalRules = getFeatureSetting(featureName, args, 'rules');
4944+
const styleTagExceptions = getFeatureSetting(featureName, args, 'styleTagExceptions');
49084945
adLabelStrings = getFeatureSetting(featureName, args, 'adLabelStrings');
4946+
shouldInjectStyleTag = getFeatureSetting(featureName, args, 'useStrictHideStyleTag');
4947+
4948+
// determine whether strict hide rules should be injected as a style tag
4949+
if (shouldInjectStyleTag) {
4950+
shouldInjectStyleTag = !styleTagExceptions.some((exception) => {
4951+
return matchHostname(domain, exception.domain)
4952+
});
4953+
}
49094954

49104955
// collect all matching rules for domain
49114956
const activeDomainRules = domainRules.filter((rule) => {

build/android/contentScope.js

Lines changed: 75 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)