Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collapse empty ad spaces (element hiding) #155

Merged
merged 8 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 142 additions & 5 deletions Sources/ContentScopeScripts/dist/contentScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,7 @@
function __variableDynamicImportRuntime0__(path) {
switch (path) {
case './features/cookie.js': return Promise.resolve().then(function () { return cookie; });
case './features/element-hiding.js': return Promise.resolve().then(function () { return elementHiding; });
case './features/fingerprinting-audio.js': return Promise.resolve().then(function () { return fingerprintingAudio; });
case './features/fingerprinting-battery.js': return Promise.resolve().then(function () { return fingerprintingBattery; });
case './features/fingerprinting-canvas.js': return Promise.resolve().then(function () { return fingerprintingCanvas; });
Expand Down Expand Up @@ -1863,7 +1864,8 @@
'referrer',
'fingerprintingScreenSize',
'fingerprintingTemporaryStorage',
'navigatorInterface'
'navigatorInterface',
'elementHiding'
];

for (const featureName of featureNames) {
Expand All @@ -1878,7 +1880,7 @@
}
}

async function init$d (args) {
async function init$e (args) {
initArgs = args;
if (!shouldRun()) {
return
Expand Down Expand Up @@ -2234,7 +2236,7 @@
});
}

function init$c (args) {
function init$d (args) {
args.cookie.debug = args.debug;
cookiePolicy = args.cookie;

Expand All @@ -2258,10 +2260,145 @@
var cookie = /*#__PURE__*/Object.freeze({
__proto__: null,
load: load,
init: init$c,
init: init$d,
update: update
});

let adLabelStrings = [];

function collapseDomNode (element, type) {
if (!element) {
return
}

switch (type) {
case 'hide':
if (!element.hidden) {
hideNode(element);
}
break
case 'hide-empty':
if (!element.hidden && isDomNodeEmpty(element)) {
hideNode(element);
}
break
case 'closest-empty':
// if element already hidden, continue onto parent element
if (element.hidden) {
collapseDomNode(element.parentNode, type);
break
}

if (isDomNodeEmpty(element)) {
hideNode(element);
collapseDomNode(element.parentNode, type);
}
break
default:
console.log(`Unsupported rule: ${type}`);
}
}

function hideNode (element) {
element.style.setProperty('display', 'none', 'important');
element.hidden = true;
}

function isDomNodeEmpty (node) {
const visibleText = node.innerText.trim().toLocaleLowerCase();
const mediaContent = node.querySelector('video,canvas');
const frameElements = [...node.querySelectorAll('iframe')];
// about:blank iframes don't count as content, return true if:
// - node doesn't contain any iframes
// - node contains iframes, all of which are hidden or have src='about:blank'
const noFramesWithContent = frameElements.every((frame) => {
return (frame.hidden || frame.src === 'about:blank')
});
if ((visibleText === '' || adLabelStrings.includes(visibleText)) &&
noFramesWithContent && mediaContent === null) {
return true
}
return false
}

function hideMatchingDomNodes (rules) {
const document = globalThis.document;

function hideMatchingNodesInner () {
rules.forEach((rule) => {
const matchingElementArray = [...document.querySelectorAll(rule.selector)];
matchingElementArray.forEach((element) => {
collapseDomNode(element, rule.type);
});
});
}
// wait 300ms before hiding ad containers so ads have a chance to load
setTimeout(hideMatchingNodesInner, 300);

// handle any ad containers that weren't added to the page within 300ms of page load
setTimeout(hideMatchingNodesInner, 1000);
}

function init$c (args) {
if (isBeingFramed()) {
return
}

const featureName = 'elementHiding';
const domain = args.site.domain;
const domainRules = getFeatureSetting(featureName, args, 'domains');
const globalRules = getFeatureSetting(featureName, args, 'rules');
adLabelStrings = getFeatureSetting(featureName, args, 'adLabelStrings');

// collect all matching rules for domain
const activeDomainRules = domainRules.filter((rule) => {
return matchHostname(domain, rule.domain)
}).flatMap((item) => item.rules);

const overrideRules = activeDomainRules.filter((rule) => {
return rule.type === 'override'
});

let activeRules = activeDomainRules.concat(globalRules);

// remove overrides and rules that match overrides from array of rules to be applied to page
overrideRules.forEach((override) => {
activeRules = activeRules.filter((rule) => {
return rule.selector !== override.selector
});
});

// now have the final list of rules to apply, so we apply them when document is loaded
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', (event) => {
hideMatchingDomNodes(activeRules);
});
} else {
hideMatchingDomNodes(activeRules);
}
// single page applications don't have a DOMContentLoaded event on navigations, so
// we use proxy/reflect on history.pushState and history.replaceState to call hideMatchingDomNodes
// on page navigations, and listen for popstate events that indicate a back/forward navigation
const methods = ['pushState', 'replaceState'];
for (const methodName of methods) {
const historyMethodProxy = new DDGProxy(featureName, History.prototype, methodName, {
apply (target, thisArg, args) {
hideMatchingDomNodes(activeRules);
return DDGReflect.apply(target, thisArg, args)
}
});
historyMethodProxy.overload();
}
window.addEventListener('popstate', (event) => {
hideMatchingDomNodes(activeRules);
});
}

var elementHiding = /*#__PURE__*/Object.freeze({
__proto__: null,
init: init$c
});

function init$b (args) {
const { sessionKey, site } = args;
const domainKey = site.domain;
Expand Down Expand Up @@ -4459,7 +4596,7 @@
init: init
});

exports.init = init$d;
exports.init = init$e;
exports.load = load$1;
exports.update = update$1;

Expand Down
147 changes: 142 additions & 5 deletions build/android/contentScope.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading