// ==UserScript== // @name Image Loader Placeholder Remover // @namespace blankie-scripts // @match http*://*/* // @exclude-match http*://solar.lowtechmagazine.com/* // @grant none // @version 1.23.3 // @author blankie // @run-at document-end // @description Removes image loading placeholders // ==/UserScript== "use strict"; const URL_RE_STR = "(?:https?://|\\.{0,2}/)\\S+"; const URL_RE = new RegExp(`^${URL_RE_STR}$`); const SRCSET_COMPONENT = `${URL_RE_STR}(?:\\s+\\d+w|\\s+\\d+(?:\\.\\d+)?x)?`; // Example of a trailing comma: // - https://www.chaobunnyarts.com/product/shipping-upgrade-tracked-sticker-shipping const SRCSET_RE = new RegExp(`^${SRCSET_COMPONENT}(?:,\\s*${SRCSET_COMPONENT})*,?$`); let cachedCSSProperties = new Map(); function isUrlLike(url) { // Example of ./path: // - https://projects.apnews.com/features/2023/missing-students-chronic-absenteeism/index.html return url && URL_RE.test(url); } function getUrlAttributes(element) { let attributes = []; // https://blog.google/ // https://blog.google/threat-analysis-group/active-north-korean-campaign-targeting-security-researchers/ if (window.location.host === "blog.google" && element.hasAttribute("data-loading")) { attributes.push(element.getAttributeNode("data-loading")); } // Examples of data-src: // - https://closeronline.co.uk // - https://closeronline.co.uk/real-life/news/ever-used-excuses-documented-spreadsheet-man-used-expose-wife-s-lack-sex // - https://daramiblog.com // - https://daramiblog.com/how-to-group-irregular-verbs-for-more-efficient-learning/ // - https://www.bleepingcomputer.com/ // - https://www.bleepingcomputer.com/news/microsoft/windows-11-snipping-tool-privacy-bug-exposes-cropped-image-content/ // - https://blog.joshumax.me/general/2021/08/11/running-doom-on-captioncall.html // - https://legendsoflocalization.com/common-problems-when-translating-games-into-japanese/ // - https://knowyourmeme.com/ // - https://knowyourmeme.com/memes/saddam-husseins-hiding-place if (isUrlLike(element.dataset.src)) { attributes.push(element.getAttributeNode("data-src")); } for (let attr of element.attributes) { // Examples of lazy: // - https://daramiblog.com/ // - https://daramiblog.com/how-to-group-irregular-verbs-for-more-efficient-learning/ // - https://www.theautopian.com/ // - https://www.theautopian.com/nobody-wants-touch-screen-glove-box-latches-and-it-needs-to-stop-now/ // - https://vulcan.io/blog/ // - https://vulcan.io/blog/ai-hallucinations-package-risk // Examples of src: // - https://vulcan.io/blog/ // - https://vulcan.io/blog/ai-hallucinations-package-risk // Examples of loading: // - https://closeronline.co.uk // - https://closeronline.co.uk/real-life/news/ever-used-excuses-documented-spreadsheet-man-used-expose-wife-s-lack-sex // Examples of original: // - https://www.pcgamer.com/ // - https://www.pcgamer.com/windows-95-theme-for-windows-10/ // Example of img-url: // - https://www.makeuseof.com/windows-11-resize-taskbar/ if (!/lazy|src|loading|original|img-url/.test(attr.name) || attr.name === "src") { continue; } if (!isUrlLike(attr.value)) { continue; } attributes.push(attr); } return attributes; } function findUrl(element) { if (window.location.host === "www.vice.com") { let picture = element.parentElement; let source = picture && picture.localName === "picture" ? picture.querySelector("source") : null; if (source) { return source.srcset; } } // https://blog.google/ // https://blog.google/threat-analysis-group/active-north-korean-campaign-targeting-security-researchers/ if (window.location.host === "blog.google" && element.hasAttribute("data-loading")) { let data = JSON.parse(element.getAttribute("data-loading")); return data.desktop || data.mobile; } let urlAttributes = getUrlAttributes(element); if (urlAttributes.length !== 0) { return urlAttributes[0].value; } return null; } function findSrcset(element) { for (let attr of element.attributes) { // Examples of data-srcset: // - https://www.cbsnews.com/ // - https://www.cbsnews.com/news/leonard-mack-exonerated-47-years-after-wrongful-rape-conviction/ // - https://www.chaobunnyarts.com/ // - https://www.chaobunnyarts.com/product/shipping-upgrade-tracked-sticker-shipping if (/srcset/.test(attr.name) && attr.name !== "srcset" && SRCSET_RE.test(attr.value.trim())) { return attr.value; } } return null; } function getCachedCSSProperties(element, key) { if (cachedCSSProperties.has(key)) { return cachedCSSProperties.get(key); } let {opacity, display, visibility, filter} = getComputedStyle(element); let ret = {opacity, display, visibility, filter}; cachedCSSProperties.set(key, ret); return ret; } function getLazyloaderClasses(element) { let classes = []; for (let className of element.classList) { // Examples of loading: // - https://closeronline.co.uk // - https://closeronline.co.uk/real-life/news/ever-used-excuses-documented-spreadsheet-man-used-expose-wife-s-lack-sex // Examples of lazy: // - https://www.vice.com/en/article/dy73n7/ehallpass-1000-thousand-schools-monitor-bathroom // - https://www.pcgamer.com/ // - https://www.pcgamer.com/windows-95-theme-for-windows-10/ // - https://www.theautopian.com/ // - https://www.theautopian.com/nobody-wants-touch-screen-glove-box-latches-and-it-needs-to-stop-now/ // - https://vulcan.io/blog/ // - https://vulcan.io/blog/ai-hallucinations-package-risk // Examples of responsive: // - https://www.vice.com/en/article/dy73n7/ehallpass-1000-thousand-schools-monitor-bathroom // - https://www.wired.com (it's a parent of lazyloaded s) // - https://www.wired.com/story/researcher-fooled-a-google-ai-into-thinking-a-rifle-was-a-helicopter/ (it's a parent of lazyloaded s) // Examples of preload: // - https://restofworld.org/ // - https://restofworld.org/2023/parent-facing-matchmaking-apps-china/ // Examples of placeholder: // - https://theconversation.com // - https://theconversation.com/yall-that-most-southern-of-southernisms-is-going-mainstream-and-its-about-time-193265 // Examples of image (though admittedly a hack for cloneLazyloaderTree): // - https://restofworld.org/ // - https://restofworld.org/2023/parent-facing-matchmaking-apps-china/ // Examples of img: // - https://www.bloomberg.com/news/features/2023-05-11/the-plot-to-steal-the-secret-coke-can-liner-formula // - https://www.bloomberg.com/news/features/2023-09-28/google-user-data-is-police-s-top-shortcut-for-solving-crimes // Examples of blur; // - https://www.chaobunnyarts.com/ // - https://www.chaobunnyarts.com/product/shipping-upgrade-tracked-sticker-shipping if (/loading|lazy|responsive|preload|placeholder|image|blur/.test(className)) { classes.push(className); } } return classes; } function getLazyloaderAttributes(element) { let attributes = []; for (let attr of element.attributes) { if (attr.name === "class") { continue; } // Examples of loading in name: // - https://blog.google/ // - https://blog.google/threat-analysis-group/active-north-korean-campaign-targeting-security-researchers/ // Examples of img in value: // - https://www.bloomberg.com/news/features/2023-05-11/the-plot-to-steal-the-secret-coke-can-liner-formula // - https://www.bloomberg.com/news/features/2023-09-28/google-user-data-is-police-s-top-shortcut-for-solving-crimes // Examples of image in value: // - https://www.edinburghlive.co.uk/ // - https://www.edinburghlive.co.uk/news/edinburgh-news/edinburgh-couple-fume-handed-17k-27906242 if (/loading/.test(attr.name) || /img|image/.test(attr.value)) { attributes.push(attr); } } return attributes; } function cloneLazyloaderTree(element, elementLazyLoaderClasses) { let topMostParent = null; let followingTopMostParent = element.parentElement; let bottomMostChild = null; while (followingTopMostParent && followingTopMostParent.localName !== "body") { let clone = document.createElement(followingTopMostParent.localName); clone.classList.add(...getLazyloaderClasses(followingTopMostParent)); for (let attr of getLazyloaderAttributes(followingTopMostParent)) { clone.setAttribute(attr.name, attr.value); } if (topMostParent) { clone.append(topMostParent); } topMostParent = clone; if (!bottomMostChild) { bottomMostChild = clone; } followingTopMostParent = followingTopMostParent.parentElement; } let clone = document.createElement(element.localName); clone.classList.add(...elementLazyLoaderClasses); let urlAttributeNames = new Set(["src", "href"]); for (let attr of getUrlAttributes(element)) { urlAttributeNames.add(attr.name); } for (let attr of getLazyloaderAttributes(element)) { clone.setAttribute(attr.name, !urlAttributeNames.has(attr.name) ? attr.value : ""); } if (bottomMostChild) { bottomMostChild.append(clone); bottomMostChild = clone; } else { topMostParent = bottomMostChild = clone; } return [topMostParent, bottomMostChild]; } function unhideElement(element, lazyLoaderClasses) { // We no longer/don't check the styles of each element individually, but rather their "generified" version // (i.e., only lazy classes), because checking each actual element causes a lot of cache busts // We check if the classes discriminate lazyloaded images, just in case // We can't remove the lazyload classes from the image, because: // - https://legendsoflocalization.com/japans-mysterious-love-of-the-word-lets/#why-lets-is-so-common // - https://www.wired.com/story/why-do-printers-still-suck/ // However, some sites use the classes to blur and/or change the opacity of images let [toRemove, toExamine] = cloneLazyloaderTree(element, lazyLoaderClasses); document.body.append(toRemove); let classStyle = getCachedCSSProperties(toExamine, toRemove.outerHTML); // Examples of opacity === "0": // - https://closeronline.co.uk // - https://closeronline.co.uk/real-life/news/ever-used-excuses-documented-spreadsheet-man-used-expose-wife-s-lack-sex // - https://www.vice.com/en/article/dy73n7/ehallpass-1000-thousand-schools-monitor-bathroom // - https://daramiblog.com/ // - https://daramiblog.com/how-to-group-irregular-verbs-for-more-efficient-learning/ // - https://www.wired.com/ // - https://www.wired.com/story/researcher-fooled-a-google-ai-into-thinking-a-rifle-was-a-helicopter/ // Example of opacity === "0.75": // https://www.404media.co/welcome-to-404-media/ if (classStyle.opacity !== "1") { element.style.opacity = 1; } // Examples of display: // - https://closeronline.co.uk // - https://closeronline.co.uk/real-life/news/ever-used-excuses-documented-spreadsheet-man-used-expose-wife-s-lack-sex if (classStyle.display === "none") { element.style.display = "block"; } // Example: https://www.makeuseof.com/windows-11-resize-taskbar/ if (window.location.host === "www.makeuseof.com") { element.style.paddingBottom = 0; } // Examples of visibility: // - https://www.edinburghlive.co.uk/ // - https://www.edinburghlive.co.uk/news/edinburgh-news/edinburgh-couple-fume-handed-17k-27906242 if (classStyle.visibility === "hidden" || classStyle.visibility === "collapse") { element.style.visibility = "visible"; } // Examples of blur: // - https://www.404media.co/welcome-to-404-media/ // - https://restofworld.org/ // - https://restofworld.org/2023/parent-facing-matchmaking-apps-china/ // - https://blog.google/ // - https://blog.google/threat-analysis-group/active-north-korean-campaign-targeting-security-researchers/ // - https://www.bloomberg.com/news/features/2023-05-11/the-plot-to-steal-the-secret-coke-can-liner-formula // - https://www.bloomberg.com/news/features/2023-09-28/google-user-data-is-police-s-top-shortcut-for-solving-crimes // - https://www.washingtonpost.com/nation/2023/07/21/ocean-color-changing-climate-change/ if (classStyle.filter.includes("blur(")) { element.style.filter = classStyle.filter.replaceAll(/blur\(.+?\)/g, "blur(0px)"); } toRemove.remove(); } function wrapImage(img, url) { if (img.closest("a") || !url) { return; } let wrapper = document.createElement("a"); img.replaceWith(wrapper); wrapper.href = url; wrapper.append(img); } function removePlaceholder(img) { let parentElement = img.parentElement; while (parentElement) { // Examples of hidden parents: // - https://www.wired.com/ // - https://www.wired.com/story/researcher-fooled-a-google-ai-into-thinking-a-rifle-was-a-helicopter/ let lazyLoaderClasses = getLazyloaderClasses(parentElement); if (lazyLoaderClasses.length !== 0) { unhideElement(parentElement, lazyLoaderClasses); } parentElement = parentElement.parentElement; } let picture = img.closest("picture"); unhideElement(img, getLazyloaderClasses(img)); let srcset = findSrcset(img); let url = findUrl(img); if (!url && !srcset) { return; } let originalUrl = !srcset ? url : null; if (url) { if (window.location.host === "www.vice.com") { img.style.filter = "none"; picture.style.opacity = 1; let urlObject = new URL(url, window.location); urlObject.search = ""; originalUrl = urlObject.href; urlObject.search = "?resize=1024:*"; url = urlObject.href; } else if (window.location.host === "closeronline.co.uk") { let urlObject = new URL(url, window.location); urlObject.search = ""; originalUrl = urlObject.href; img.closest(".image-container").querySelector(".image-loading").remove(); } else if (window.location.host === "theconversation.com") { let urlObject = new URL(url, window.location); urlObject.search = ""; originalUrl = urlObject.href; } } if (srcset) { img.srcset = srcset; } if (url) { img.src = url; } // Example of an empty srcset: // https://blog.jwf.io/2020/04/open-source-minecraft-bukkit-gpl/ if (img.srcset && !srcset) { img.srcset = ""; } // apparently, remaining s can fuck up image rendering (firefox still thinks that the contains an empty image?) // https://gizmodo.com/reddit-news-blackout-protest-is-finally-over-reddit-won-1850707509 // also, the above vice.com-specific condition had the below code if (picture) { for (let source of picture.querySelectorAll("source")) { source.remove(); } } wrapImage(img, originalUrl); } for (let img of document.querySelectorAll("img")) { removePlaceholder(img); } // the reason we reunhide images after 1s after the page loads is because of