Skip to content

Instantly share code, notes, and snippets.

@curegit
Last active February 25, 2025 13:25
Show Gist options
  • Save curegit/6bf83f4b225cd85da467d08531160981 to your computer and use it in GitHub Desktop.
Save curegit/6bf83f4b225cd85da467d08531160981 to your computer and use it in GitHub Desktop.
画像と CSS を HTML に埋め込んでダウンロードするブックマークレット
javascript: (async function() {
async function stringifyRule(rule, contextStyleSheet) {
return rule instanceof CSSImportRule ? (rule.stylesheet ? await stringifyStyleSheet(rule.stylesheet) : "") : await inlinizeStyleImages(rule.cssText || "", (contextStyleSheet.href ?? contextStyleSheet.ownerNode?.baseURI ?? document.baseURI));
}
async function stringifyStyleSheet(style) {
try {
return style.cssRules ? (await Promise.all([...style.cssRules].map(r => stringifyRule(r, style)))).join("\n") : "";
} catch (e) {
console.warn(e);
return "";
}
}
function imageToDataURL(image) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
try {
canvas.width = image.naturalWidth ? image.naturalWidth : image.width;
canvas.height = image.naturalHeight ? image.naturalHeight : image.height;
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL();
} catch (e) {
console.warn(e);
return false;
}
}
async function inlinizeStyleImages(ruleText, basePath) {
/* The URL extraction here is not perfect, but it is left as is for simplicity */
const url = ruleText.match(/url\(\s*?['"]?\s*?(.+?)\s*?["']?\s*?\)/i)?.[1];
if (!url || url.startsWith("data:")) return ruleText;
const image = new Image();
const loadPromise = new Promise(r => image.addEventListener("load", () => r()));
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1000));
image.src = "" + new URL(url, basePath);
try {
await Promise.race([loadPromise, timeoutPromise]);
const data = imageToDataURL(image);
if (data) {
return ruleText.replace(url, data);
} else {
return ruleText;
}
} catch (e) {
console.warn(e);
return ruleText;
}
}
function inlinizeImageElements() {
const images = [...document.images];
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (!image.src?.startsWith("data:")) {
const data = imageToDataURL(image);
if (data) {
image.src = data;
image.removeAttribute("srcset");
}
}
}
}
async function inlinizeFavicon() {
const link = document.querySelector("link[rel~='icon']");
if (link) {
const image = new Image();
const loadPromise = new Promise(r => image.addEventListener("load", () => r()));
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), 1000));
image.src = link.href;
try {
await Promise.race([loadPromise, timeoutPromise]);
const data = imageToDataURL(image);
if (data) {
link.href = data;
}
} catch (e) {
console.warn(e);
}
}
}
async function inlinizeStyleAttributes() {
const elements = [...document.querySelectorAll("*[style]")];
for (let element of elements) {
const style = element.getAttribute("style");
const newStyle = await inlinizeStyleImages(style, element.baseURI ?? document.baseURI);
element.setAttribute("style", newStyle);
}
}
async function inlinizeStyleSheets() {
const promises = [...document.styleSheets].map(async x => {
const styleElement = document.createElement("style");
styleElement.appendChild(document.createTextNode(await stringifyStyleSheet(x)));
x.ownerNode?.replaceWith?.(styleElement);
});
await Promise.all(promises);
}
function removeScripts(all = true) {
const selector = all ? "script" : "script[src]";
const scripts = document.querySelectorAll(selector);
scripts.forEach(script => script.remove());
}
let del = () => null;
if (confirm("すべてのスクリプトを削除しますか?")) {
del = () => removeScripts(true);
} else if (confirm("外部スクリプトを削除しますか?")) {
del = () => removeScripts(false);
}
inlinizeImageElements();
await inlinizeFavicon();
await inlinizeStyleAttributes();
await inlinizeStyleSheets();
del();
const originalURL = `<!-- Original URL: ${document.location.href} -->`;
const html = `<!DOCTYPE html>\n${originalURL}\n${document.body.parentNode.outerHTML}`;
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([html], { type: "text/html" }));
a.download = `${document.title}.html`;
a.dispatchEvent(new MouseEvent("click"));
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment