453 lines
17 KiB
JavaScript
453 lines
17 KiB
JavaScript
// ==UserScript==
|
|
// @name Image Loader Placeholder Remover
|
|
// @namespace blankie-scripts
|
|
// @match http*://*/*
|
|
// @exclude-match http*://solar.lowtechmagazine.com/*
|
|
// @grant none
|
|
// @version 1.23.2
|
|
// @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)?`;
|
|
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/
|
|
if (/srcset/.test(attr.name) && attr.name !== "srcset" && SRCSET_RE.test(attr.value)) {
|
|
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 <img>s)
|
|
// - https://www.wired.com/story/researcher-fooled-a-google-ai-into-thinking-a-rifle-was-a-helicopter/ (it's a parent of lazyloaded <img>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/
|
|
// Example 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
|
|
if (/loading|lazy|responsive|preload|placeholder|image/.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 <source>s can fuck up image rendering (firefox still thinks that the <img> 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 <noscript> elements being faked by umatrix
|
|
setTimeout(function() {
|
|
for (let img of document.querySelectorAll("img")) {
|
|
removePlaceholder(img);
|
|
}
|
|
cachedCSSProperties.clear();
|
|
}, 1000);
|
|
|
|
|
|
|
|
// https://www.wikihow.com/Tie-Your-Shoes
|
|
if (window.location.host === "www.wikihow.com") {
|
|
let style = document.createElement("style");
|
|
style.textContent = ".image {display: block !important}";
|
|
document.head.appendChild(style);
|
|
|
|
for (let videoPlayer of document.querySelectorAll(".video-player")) {
|
|
videoPlayer.querySelector(".m-video-controls").remove();
|
|
|
|
let video = videoPlayer.querySelector("video");
|
|
video.controls = 1;
|
|
video.src = `/video${video.dataset.src}`;
|
|
}
|
|
}
|
|
|
|
// https://www.forbes.com/
|
|
// https://www.forbes.com/sites/ewanspence/2023/08/30/fairphone-5-preview-android-sustainable-repairable-modular-android/
|
|
if (window.location.host === "www.forbes.com") {
|
|
for (let progressiveImage of document.querySelectorAll("progressive-image")) {
|
|
let url = progressiveImage.getAttribute("src") || progressiveImage.getAttribute("background-image");;
|
|
|
|
let img = document.createElement("img");
|
|
for (let attr of progressiveImage.attributes) {
|
|
img.setAttribute(attr.name, attr.value);
|
|
}
|
|
img.src = url;
|
|
|
|
let placeholder = progressiveImage.closest(".image-embed__placeholder");
|
|
if (placeholder) {
|
|
placeholder.style.height = "auto";
|
|
placeholder.style.paddingTop = 0;
|
|
}
|
|
|
|
progressiveImage.replaceWith(img);
|
|
wrapImage(img, url);
|
|
}
|
|
}
|
|
|
|
// https://www.independent.co.uk/news/world/americas/abortion-lawsuit-tennessee-idaho-oklahoma-b2410109.html
|
|
if (window.location.host === "www.independent.co.uk") {
|
|
for (let ampImg of document.querySelectorAll("amp-img")) {
|
|
let img = document.createElement("img");
|
|
for (let attr of ampImg.attributes) {
|
|
img.setAttribute(attr.name, attr.value);
|
|
}
|
|
|
|
// fix some images blowing past the viewport
|
|
if (!img.style.width && !img.style.height) {
|
|
img.style.width = "100%";
|
|
img.style.height = "auto";
|
|
}
|
|
|
|
ampImg.replaceWith(img);
|
|
wrapImage(img, img.src);
|
|
}
|
|
} |