YoutubeAdblock: fix lag/high memory usage after clicking videos (#4317)

This commit is contained in:
Suffocate 2026-06-25 09:52:41 +01:00 • committed by GitHub
parent 70d41b0584
commit 84adcc7002
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -18,199 +18,203 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with AdGuard's Block YouTube Ads. If not, see <http://www.gnu.org/licenses/>. * along with AdGuard's Block YouTube Ads. If not, see <http://www.gnu.org/licenses/>.
*/ */
(() => {
if (window.adguardInjected) return;
window.adguardInjected = true;
const hiddenCSS = [ const hiddenCSS = [
"#__ffYoutube1", "#__ffYoutube1",
"#__ffYoutube2", "#__ffYoutube2",
"#__ffYoutube3", "#__ffYoutube3",
"#__ffYoutube4", "#__ffYoutube4",
"#feed-pyv-container", "#feed-pyv-container",
"#feedmodule-PRO", "#feedmodule-PRO",
"#homepage-chrome-side-promo", "#homepage-chrome-side-promo",
"#merch-shelf", "#merch-shelf",
"#offer-module", "#offer-module",
'#pla-shelf > ytd-pla-shelf-renderer[class="style-scope ytd-watch"]', '#pla-shelf > ytd-pla-shelf-renderer[class="style-scope ytd-watch"]',
"#pla-shelf", "#pla-shelf",
"#premium-yva", "#premium-yva",
"#promo-info", "#promo-info",
"#promo-list", "#promo-list",
"#promotion-shelf", "#promotion-shelf",
"#related > ytd-watch-next-secondary-results-renderer > #items > ytd-compact-promoted-video-renderer.ytd-watch-next-secondary-results-renderer", "#related > ytd-watch-next-secondary-results-renderer > #items > ytd-compact-promoted-video-renderer.ytd-watch-next-secondary-results-renderer",
"#search-pva", "#search-pva",
"#shelf-pyv-container", "#shelf-pyv-container",
"#video-masthead", "#video-masthead",
"#watch-branded-actions", "#watch-branded-actions",
"#watch-buy-urls", "#watch-buy-urls",
"#watch-channel-brand-div", "#watch-channel-brand-div",
"#watch7-branded-banner", "#watch7-branded-banner",
"#YtKevlarVisibilityIdentifier", "#YtKevlarVisibilityIdentifier",
"#YtSparklesVisibilityIdentifier", "#YtSparklesVisibilityIdentifier",
".carousel-offer-url-container", ".carousel-offer-url-container",
".companion-ad-container", ".companion-ad-container",
".GoogleActiveViewElement", ".GoogleActiveViewElement",
'.list-view[style="margin: 7px 0pt;"]', '.list-view[style="margin: 7px 0pt;"]',
".promoted-sparkles-text-search-root-container", ".promoted-sparkles-text-search-root-container",
".promoted-videos", ".promoted-videos",
".searchView.list-view", ".searchView.list-view",
".sparkles-light-cta", ".sparkles-light-cta",
".watch-extra-info-column", ".watch-extra-info-column",
".watch-extra-info-right", ".watch-extra-info-right",
".ytd-carousel-ad-renderer", ".ytd-carousel-ad-renderer",
".ytd-compact-promoted-video-renderer", ".ytd-compact-promoted-video-renderer",
".ytd-companion-slot-renderer", ".ytd-companion-slot-renderer",
".ytd-merch-shelf-renderer", ".ytd-merch-shelf-renderer",
".ytd-player-legacy-desktop-watch-ads-renderer", ".ytd-player-legacy-desktop-watch-ads-renderer",
".ytd-promoted-sparkles-text-search-renderer", ".ytd-promoted-sparkles-text-search-renderer",
".ytd-promoted-video-renderer", ".ytd-promoted-video-renderer",
".ytd-search-pyv-renderer", ".ytd-search-pyv-renderer",
".ytd-video-masthead-ad-v3-renderer", ".ytd-video-masthead-ad-v3-renderer",
".ytp-ad-action-interstitial-background-container", ".ytp-ad-action-interstitial-background-container",
".ytp-ad-action-interstitial-slot", ".ytp-ad-action-interstitial-slot",
".ytp-ad-image-overlay", ".ytp-ad-image-overlay",
".ytp-ad-overlay-container", ".ytp-ad-overlay-container",
".ytp-ad-progress", ".ytp-ad-progress",
".ytp-ad-progress-list", ".ytp-ad-progress-list",
'[class*="ytd-display-ad-"]', '[class*="ytd-display-ad-"]',
'[layout*="display-ad-"]', '[layout*="display-ad-"]',
'a[href^="http://www.youtube.com/cthru?"]', 'a[href^="http://www.youtube.com/cthru?"]',
'a[href^="https://www.youtube.com/cthru?"]', 'a[href^="https://www.youtube.com/cthru?"]',
"ytd-action-companion-ad-renderer", "ytd-action-companion-ad-renderer",
"ytd-banner-promo-renderer", "ytd-banner-promo-renderer",
"ytd-compact-promoted-video-renderer", "ytd-compact-promoted-video-renderer",
"ytd-companion-slot-renderer", "ytd-companion-slot-renderer",
"ytd-display-ad-renderer", "ytd-display-ad-renderer",
"ytd-promoted-sparkles-text-search-renderer", "ytd-promoted-sparkles-text-search-renderer",
"ytd-promoted-sparkles-web-renderer", "ytd-promoted-sparkles-web-renderer",
"ytd-search-pyv-renderer", "ytd-search-pyv-renderer",
"ytd-single-option-survey-renderer", "ytd-single-option-survey-renderer",
"ytd-video-masthead-ad-advertiser-info-renderer", "ytd-video-masthead-ad-advertiser-info-renderer",
"ytd-video-masthead-ad-v3-renderer", "ytd-video-masthead-ad-v3-renderer",
"YTM-PROMOTED-VIDEO-RENDERER", "YTM-PROMOTED-VIDEO-RENDERER",
]; ];
/** /**
* Adds CSS to the page * Adds CSS to the page
*/ */
const hideElements = () => { const hideElements = () => {
const selectors = hiddenCSS; const selectors = hiddenCSS;
if (!selectors) { if (!selectors) {
return; return;
}
const rule = selectors.join(", ") + " { display: none!important; }";
const style = document.createElement("style");
style.textContent = rule;
document.head.appendChild(style);
};
/**
* Calls the "callback" function on every DOM change, but not for the tracked events
* @param {Function} callback callback function
*/
const observeDomChanges = callback => {
const domMutationObserver = new MutationObserver(mutations => {
callback(mutations);
});
domMutationObserver.observe(document.documentElement, {
childList: true,
subtree: true,
});
};
/**
* This function is supposed to be called on every DOM change
*/
const hideDynamicAds = () => {
const elements = document.querySelectorAll("#contents > ytd-rich-item-renderer ytd-display-ad-renderer");
if (elements.length === 0) {
return;
}
elements.forEach(el => {
if (el.parentNode && el.parentNode.parentNode) {
const parent = el.parentNode.parentNode;
if (parent.localName === "ytd-rich-item-renderer") {
parent.style.display = "none";
}
} }
}); const rule = selectors.join(", ") + " { display: none!important; }";
}; const style = document.createElement("style");
/** style.textContent = rule;
* This function checks if the video ads are currently running document.head.appendChild(style);
* and auto-clicks the skip button.
*/
const autoSkipAds = () => {
// If there's a video that plays the ad at this moment, scroll this ad
if (document.querySelector(".ad-showing")) {
const video = document.querySelector("video");
if (video && video.duration) {
video.currentTime = video.duration;
// Skip button should appear after that,
// now simply click it automatically
setTimeout(() => {
const skipBtn = document.querySelector("button.ytp-ad-skip-button");
if (skipBtn) {
skipBtn.click();
}
}, 100);
}
}
};
/**
* This function overrides a property on the specified object.
*
* @param {object} obj object to look for properties in
* @param {string} propertyName property to override
* @param {*} overrideValue value to set
*/
const overrideObject = (obj, propertyName, overrideValue) => {
if (!obj) {
return false;
}
let overriden = false;
for (const key in obj) {
if (obj.hasOwnProperty(key) && key === propertyName) {
obj[key] = overrideValue;
overriden = true;
} else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") {
if (overrideObject(obj[key], propertyName, overrideValue)) {
overriden = true;
}
}
}
return overriden;
};
/**
* Overrides JSON.parse and Response.json functions.
* Examines these functions arguments, looks for properties with the specified name there
* and if it exists, changes it's value to what was specified.
*
* @param {string} propertyName name of the property
* @param {*} overrideValue new value for the property
*/
const jsonOverride = (propertyName, overrideValue) => {
const nativeJSONParse = JSON.parse;
JSON.parse = (...args) => {
const obj = nativeJSONParse.apply(this, args);
// Override it's props and return back to the caller
overrideObject(obj, propertyName, overrideValue);
return obj;
}; };
// Override Response.prototype.json /**
Response.prototype.json = new Proxy(Response.prototype.json, { * Calls the "callback" function on every DOM change, but not for the tracked events
async apply(...args) { * @param {Function} callback callback function
// Call the target function, get the original Promise */
const result = await Reflect.apply(...args); const observeDomChanges = callback => {
// Create a new one and override the JSON inside const domMutationObserver = new MutationObserver(mutations => {
overrideObject(result, propertyName, overrideValue); callback(mutations);
return result; });
}, domMutationObserver.observe(document.documentElement, {
}); childList: true,
}; subtree: true,
// Removes ads metadata from YouTube XHR requests });
jsonOverride("adPlacements", []); };
jsonOverride("playerAds", []); /**
// Applies CSS that hides YouTube ad elements * This function is supposed to be called on every DOM change
hideElements(); */
// Some changes should be re-evaluated on every page change const hideDynamicAds = () => {
hideDynamicAds(); const elements = document.querySelectorAll("#contents > ytd-rich-item-renderer ytd-display-ad-renderer");
autoSkipAds(); if (elements.length === 0) {
observeDomChanges(() => { return;
}
elements.forEach(el => {
if (el.parentNode && el.parentNode.parentNode) {
const parent = el.parentNode.parentNode;
if (parent.localName === "ytd-rich-item-renderer") {
parent.style.display = "none";
}
}
});
};
/**
* This function checks if the video ads are currently running
* and auto-clicks the skip button.
*/
const autoSkipAds = () => {
// If there's a video that plays the ad at this moment, scroll this ad
if (document.querySelector(".ad-showing")) {
const video = document.querySelector("video");
if (video && video.duration) {
video.currentTime = video.duration;
// Skip button should appear after that,
// now simply click it automatically
setTimeout(() => {
const skipBtn = document.querySelector("button.ytp-ad-skip-button");
if (skipBtn) {
skipBtn.click();
}
}, 100);
}
}
};
/**
* This function overrides a property on the specified object.
*
* @param {object} obj object to look for properties in
* @param {string} propertyName property to override
* @param {*} overrideValue value to set
*/
const overrideObject = (obj, propertyName, overrideValue) => {
if (!obj) {
return false;
}
let overriden = false;
for (const key in obj) {
if (obj.hasOwnProperty(key) && key === propertyName) {
obj[key] = overrideValue;
overriden = true;
} else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") {
if (overrideObject(obj[key], propertyName, overrideValue)) {
overriden = true;
}
}
}
return overriden;
};
/**
* Overrides JSON.parse and Response.json functions.
* Examines these functions arguments, looks for properties with the specified name there
* and if it exists, changes it's value to what was specified.
*
* @param {string} propertyName name of the property
* @param {*} overrideValue new value for the property
*/
const jsonOverride = (propertyName, overrideValue) => {
const nativeJSONParse = JSON.parse;
JSON.parse = (...args) => {
const obj = nativeJSONParse.apply(this, args);
// Override it's props and return back to the caller
overrideObject(obj, propertyName, overrideValue);
return obj;
};
// Override Response.prototype.json
Response.prototype.json = new Proxy(Response.prototype.json, {
async apply(...args) {
// Call the target function, get the original Promise
const result = await Reflect.apply(...args);
// Create a new one and override the JSON inside
overrideObject(result, propertyName, overrideValue);
return result;
},
});
};
// Removes ads metadata from YouTube XHR requests
jsonOverride("adPlacements", []);
jsonOverride("playerAds", []);
// Applies CSS that hides YouTube ad elements
hideElements();
// Some changes should be re-evaluated on every page change
hideDynamicAds(); hideDynamicAds();
autoSkipAds(); autoSkipAds();
}); observeDomChanges(() => {
hideDynamicAds();
autoSkipAds();
});
})();