Selaa lähdekoodia

handle dynamically deleted fonts (fix #755)

Gildas 4 vuotta sitten
vanhempi
sitoutus
b87595208f

+ 41 - 22
lib/single-file/processors/hooks/content/content-hooks-frames-web.js

@@ -39,6 +39,8 @@
 	const LOAD_IMAGE_EVENT = "single-file-load-image";
 	const IMAGE_LOADED_EVENT = "single-file-image-loaded";
 	const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
+	const DELETE_FONT_EVENT = "single-file-delete-font";
+	const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
 	const FONT_STYLE_PROPERTIES = {
 		family: "font-family",
 		style: "font-style",
@@ -280,33 +282,25 @@
 		let warningFontFaceDisplayed;
 		globalThis.FontFace = function () {
 			if (!warningFontFaceDisplayed) {
-				warn("SingleFile is hooking the FontFace constructor to get font URLs.");
+				warn("SingleFile is hooking the FontFace constructor, document.fonts.delete and document.fonts.clear to handle dynamically loaded fonts.");
 				warningFontFaceDisplayed = true;
 			}
-			const detail = {};
-			detail["font-family"] = arguments[0];
-			detail.src = arguments[1];
-			const descriptors = arguments[2];
-			if (descriptors) {
-				Object.keys(descriptors).forEach(descriptor => {
-					if (FONT_STYLE_PROPERTIES[descriptor]) {
-						detail[FONT_STYLE_PROPERTIES[descriptor]] = descriptors[descriptor];
-					}
-				});
-			}
-			if (detail.src instanceof ArrayBuffer) {
-				const reader = new FileReader();
-				reader.readAsDataURL(new Blob([detail.src]));
-				reader.addEventListener("load", () => {
-					detail.src = "url(" + reader.result + ")";
-					dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
-				});
-			} else {
-				dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
-			}
+			getDetailObject(...arguments).then(detail => dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail })));
 			return new FontFace(...arguments);
 		};
 		globalThis.FontFace.toString = function () { return "function FontFace() { [native code] }"; };
+		const deleteFont = document.fonts.delete;
+		document.fonts.delete = function (fontFace) {
+			getDetailObject(fontFace.family).then(detail => dispatchEvent(new CustomEvent(DELETE_FONT_EVENT, { detail })));
+			return deleteFont.call(document.fonts, fontFace);
+		};
+		document.fonts.delete.toString = function () { return "function delete() { [native code] }"; };
+		const clearFonts = document.fonts.clear;
+		document.fonts.clear = function () {
+			dispatchEvent(new CustomEvent(CLEAR_FONTS_EVENT));
+			return clearFonts.call(document.fonts);
+		};
+		document.fonts.clear.toString = function () { return "function clear() { [native code] }"; };
 	}
 
 	if (globalThis.IntersectionObserver) {
@@ -355,6 +349,31 @@
 		globalThis.IntersectionObserver.toString = function () { return "function IntersectionObserver() { [native code] }"; };
 	}
 
+	async function getDetailObject(fontFamily, src, descriptors) {
+		const detail = {};
+		detail["font-family"] = fontFamily;
+		detail.src = src;
+		if (descriptors) {
+			Object.keys(descriptors).forEach(descriptor => {
+				if (FONT_STYLE_PROPERTIES[descriptor]) {
+					detail[FONT_STYLE_PROPERTIES[descriptor]] = descriptors[descriptor];
+				}
+			});
+		}
+		return new Promise(resolve => {
+			if (detail.src instanceof ArrayBuffer) {
+				const reader = new FileReader();
+				reader.readAsDataURL(new Blob([detail.src]));
+				reader.addEventListener("load", () => {
+					detail.src = "url(" + reader.result + ")";
+					resolve(detail);
+				});
+			} else {
+				resolve(detail);
+			}
+		});
+	}
+
 	function dispatchResizeEvent() {
 		try {
 			dispatchEvent(new UIEvent("resize"));

+ 46 - 17
lib/single-file/processors/hooks/content/content-hooks-frames.js

@@ -36,6 +36,8 @@ const BLOCK_STORAGE_END_EVENT = "single-file-block-storage-end";
 const LOAD_IMAGE_EVENT = "single-file-load-image";
 const IMAGE_LOADED_EVENT = "single-file-image-loaded";
 const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
+const DELETE_FONT_EVENT = "single-file-delete-font";
+const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
 
 const browser = globalThis.browser;
 const addEventListener = (type, listener, options) => globalThis.addEventListener(type, listener, options);
@@ -59,6 +61,14 @@ if (document instanceof HTMLDocument) {
 				fontFaces.push(event.detail);
 			}
 		});
+		addEventListener(DELETE_FONT_EVENT, event => {
+			const detail = event.detail;
+			const indexFontFace = fontFaces.findIndex(fontFace => fontFace["family-name"] == detail["family-name"]);
+			if (indexFontFace != -1) {
+				fontFaces.splice(indexFontFace, 1);
+			}
+		});
+		addEventListener(CLEAR_FONTS_EVENT, () => fontFaces = []);
 		let scriptElement = document.createElement("script");
 		scriptElement.src = "data:," + "(" + injectedScript.toString() + ")()";
 		(document.documentElement || document).appendChild(scriptElement);
@@ -131,6 +141,8 @@ function injectedScript() {
 	const Blob = globalThis.Blob;
 	const warn = (console && console.warn && ((...args) => console.warn(...args))) || (() => { });
 	const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
+	const DELETE_FONT_EVENT = "single-file-delete-font";
+	const CLEAR_FONTS_EVENT = "single-file-clear-fonts";
 	const FONT_STYLE_PROPERTIES = {
 		family: "font-family",
 		style: "font-style",
@@ -146,32 +158,49 @@ function injectedScript() {
 		let warningFontFaceDisplayed;
 		globalThis.FontFace = function () {
 			if (!warningFontFaceDisplayed) {
-				warn("SingleFile is hooking the FontFace constructor to get font URLs.");
+				warn("SingleFile is hooking the FontFace constructor, document.fonts.delete and document.fonts.clear to handle dynamically loaded fonts.");
 				warningFontFaceDisplayed = true;
 			}
-			const detail = {};
-			detail["font-family"] = arguments[0];
-			detail.src = arguments[1];
-			const descriptors = arguments[2];
-			if (descriptors) {
-				Object.keys(descriptors).forEach(descriptor => {
-					if (FONT_STYLE_PROPERTIES[descriptor]) {
-						detail[FONT_STYLE_PROPERTIES[descriptor]] = descriptors[descriptor];
-					}
-				});
-			}
+			getDetailObject(...arguments).then(detail => dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail })));
+			return new FontFace(...arguments);
+		};
+		globalThis.FontFace.toString = function () { return "function FontFace() { [native code] }"; };
+		const deleteFont = document.fonts.delete;
+		document.fonts.delete = function (fontFace) {
+			getDetailObject(fontFace.family).then(detail => dispatchEvent(new CustomEvent(DELETE_FONT_EVENT, { detail })));
+			return deleteFont.call(document.fonts, fontFace);
+		};
+		document.fonts.delete.toString = function () { return "function delete() { [native code] }"; };
+		const clearFonts = document.fonts.clear;
+		document.fonts.clear = function () {
+			dispatchEvent(new CustomEvent(CLEAR_FONTS_EVENT));
+			return clearFonts.call(document.fonts);
+		};
+		document.fonts.clear.toString = function () { return "function clear() { [native code] }"; };
+	}
+
+	async function getDetailObject(fontFamily, src, descriptors) {
+		const detail = {};
+		detail["font-family"] = fontFamily;
+		detail.src = src;
+		if (descriptors) {
+			Object.keys(descriptors).forEach(descriptor => {
+				if (FONT_STYLE_PROPERTIES[descriptor]) {
+					detail[FONT_STYLE_PROPERTIES[descriptor]] = descriptors[descriptor];
+				}
+			});
+		}
+		return new Promise(resolve => {
 			if (detail.src instanceof ArrayBuffer) {
 				const reader = new FileReader();
 				reader.readAsDataURL(new Blob([detail.src]));
 				reader.addEventListener("load", () => {
 					detail.src = "url(" + reader.result + ")";
-					dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
+					resolve(detail);
 				});
 			} else {
-				dispatchEvent(new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
+				resolve(detail);
 			}
-			return new FontFace(...arguments);
-		};
-		globalThis.FontFace.toString = function () { return "function FontFace() { [native code] }"; };
+		});
 	}
 }