|
4671 | 4671 | const parser = new DOMParser();
|
4672 | 4672 | let hiddenElements = new WeakMap();
|
4673 | 4673 | let appliedRules = new Set();
|
| 4674 | + let shouldInjectStyleTag = false; |
4674 | 4675 |
|
4675 | 4676 | /**
|
4676 | 4677 | * Hide DOM element if rule conditions met
|
|
4820 | 4821 | * @param {string} rules[].type
|
4821 | 4822 | */
|
4822 | 4823 | 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 | + |
4823 | 4828 | // several passes are made to hide & unhide elements. this is necessary because we're not using
|
4824 | 4829 | // a mutation observer but we want to hide/unhide elements as soon as possible, and ads
|
4825 | 4830 | // frequently take from several hundred milliseconds to several seconds to load
|
4826 | 4831 | // 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 | + }); |
4848 | 4837 |
|
4849 | 4838 | // check previously hidden ad elements for contents, unhide if content has loaded after hiding.
|
4850 | 4839 | // we do this in order to display non-tracking ads that aren't blocked at the request level
|
4851 | 4840 | // 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 | + }); |
4860 | 4846 |
|
4861 | 4847 | // clear appliedRules and hiddenElements caches once all checks have run
|
4862 |
| - setTimeout(function () { |
| 4848 | + setTimeout(() => { |
4863 | 4849 | appliedRules = new Set();
|
4864 | 4850 | hiddenElements = new WeakMap();
|
4865 | 4851 | }, 3100);
|
4866 | 4852 | }
|
4867 | 4853 |
|
| 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 | + |
4868 | 4904 | /**
|
4869 | 4905 | * Apply list of active element hiding rules to page
|
4870 | 4906 | * @param {Object[]} rules
|
|
4905 | 4941 | const domain = args.site.domain;
|
4906 | 4942 | const domainRules = getFeatureSetting(featureName, args, 'domains');
|
4907 | 4943 | const globalRules = getFeatureSetting(featureName, args, 'rules');
|
| 4944 | + const styleTagExceptions = getFeatureSetting(featureName, args, 'styleTagExceptions'); |
4908 | 4945 | 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 | + } |
4909 | 4954 |
|
4910 | 4955 | // collect all matching rules for domain
|
4911 | 4956 | const activeDomainRules = domainRules.filter((rule) => {
|
|
0 commit comments