Explorar el Código

added FontFace API handling

Gildas hace 7 años
padre
commit
24407a9d78

+ 1 - 0
extension/core/bg/processor.js

@@ -35,6 +35,7 @@ singlefile.processor = (() => {
 		options.url = message.url;
 		options.framesData = message.framesData;
 		options.canvasData = message.canvasData;
+		options.fontsData = message.fontsData;
 		options.stylesheetContents = message.stylesheetContents;
 		options.imageData = message.imageData;
 		options.insertSingleFileComment = true;

+ 20 - 2
extension/core/content/content-autosave.js

@@ -47,7 +47,16 @@ this.singlefile.autosave = this.singlefile.autosave || (async () => {
 				if (!options.removeFrames && this.frameTree) {
 					framesData = await frameTree.getAsync(options);
 				}
-				browser.runtime.sendMessage({ saveContent: true, content: docHelper.serialize(document, false), canvasData: docData.canvasData, stylesheetContents: docData.stylesheetContents, imageData: docData.imageData, framesData, url: location.href });
+				browser.runtime.sendMessage({
+					saveContent: true,
+					content: docHelper.serialize(document, false),
+					canvasData: docData.canvasData,
+					fontsData: docData.fontsData,
+					stylesheetContents: docData.stylesheetContents,
+					imageData: docData.imageData,
+					framesData,
+					url: location.href
+				});
 				docHelper.postProcessDoc(document, window);
 				singlefile.pageAutoSaved = true;
 			}
@@ -75,7 +84,16 @@ this.singlefile.autosave = this.singlefile.autosave || (async () => {
 	function onUnload() {
 		if (!singlefile.pageAutoSaved) {
 			const docData = docHelper.preProcessDoc(document, window, options);
-			browser.runtime.sendMessage({ saveContent: true, content: docHelper.serialize(document), canvasData: docData.canvasData, stylesheetContents: docData.stylesheetContents, imageData: docData.imageData, framesData: this.frameTree && !options.removeFrames && frameTree.getSync(options), url: location.href });
+			browser.runtime.sendMessage({
+				saveContent: true,
+				content: docHelper.serialize(document),
+				canvasData: docData.canvasData,
+				fontsData: docData.fontsData,
+				stylesheetContents: docData.stylesheetContents,
+				imageData: docData.imageData,
+				framesData: this.frameTree && !options.removeFrames && frameTree.getSync(options),
+				url: location.href
+			});
 		}
 	}
 

+ 7 - 0
lib/single-file/doc-helper.js

@@ -18,6 +18,8 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/* global fontFaceProxy */
+
 this.docHelper = this.docHelper || (() => {
 
 	const REMOVED_CONTENT_ATTRIBUTE_NAME = "data-single-file-removed-content";
@@ -65,6 +67,7 @@ this.docHelper = this.docHelper || (() => {
 		retrieveInputValues(doc, options);
 		return {
 			canvasData: getCanvasData(doc),
+			fontsData: getFontsData(doc),
 			stylesheetContents: getStylesheetContents(doc),
 			imageData: getImageData(doc, options)
 		};
@@ -177,6 +180,10 @@ this.docHelper = this.docHelper || (() => {
 		}
 	}
 
+	function getFontsData() {
+		return fontFaceProxy.getFontsData();
+	}
+
 	function retrieveInputValues(doc, options) {
 		doc.querySelectorAll("input").forEach(input => input.setAttribute(inputValueAttributeName(options.sessionId), input.value));
 		doc.querySelectorAll("textarea").forEach(textarea => textarea.setAttribute(inputValueAttributeName(options.sessionId), textarea.value));

+ 28 - 0
lib/single-file/font-face-proxy.js

@@ -0,0 +1,28 @@
+
+/* global window, addEventListener, dispatchEvent, CustomEvent, document */
+
+this.fontFaceProxy = this.fontFaceProxy || (() => {
+
+	const scriptElement = document.createElement("script");
+	scriptElement.textContent = `(${hook.toString()})()`;
+	document.documentElement.appendChild(scriptElement);
+	const fontFaces = [];
+	addEventListener("single-file-font-face", event => fontFaces.push(event.detail));
+
+	return {
+		getFontsData: () => fontFaces
+	};
+
+	function hook() {
+		Array.from(document.fonts).forEach(fontFace => document.fonts.delete(fontFace));
+		const FontFace = window.FontFace;
+		window.__defineGetter__("FontFace", () => new Proxy(FontFace, {
+			construct(FontFace, argumentsList) {
+				const detail = argumentsList;
+				dispatchEvent(new CustomEvent("single-file-font-face", { detail }));
+				return new FontFace(...argumentsList);
+			}
+		}));
+	}
+
+})();

+ 12 - 1
lib/single-file/frame-tree.js

@@ -92,6 +92,7 @@ this.frameTree = this.frameTree || (() => {
 				frameData.stylesheetContents = messageFrameData.stylesheetContents;
 				frameData.imageData = messageFrameData.imageData;
 				frameData.canvasData = messageFrameData.canvasData;
+				frameData.fontsData = messageFrameData.fontsData;
 				frameData.processed = messageFrameData.processed;
 				frameData.timeout = messageFrameData.timeout;
 			});
@@ -172,7 +173,17 @@ this.frameTree = this.frameTree || (() => {
 		const content = docHelper.serialize(document);
 		docHelper.postProcessDoc(document, window, options);
 		const baseURI = document.baseURI.split("#")[0];
-		return { windowId, content, baseURI, title: document.title, stylesheetContents: docData.stylesheetContents, imageData: docData.imageData, canvasData: docData.canvasData, processed: true };
+		return {
+			windowId,
+			content,
+			baseURI,
+			title: document.title,
+			stylesheetContents: docData.stylesheetContents,
+			imageData: docData.imageData,
+			canvasData: docData.canvasData,
+			fontsData: docData.fontsData,
+			processed: true
+		};
 	}
 
 })();

+ 30 - 0
lib/single-file/single-file-core.js

@@ -83,6 +83,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			{ option: "insertFaviconLink", action: "insertFaviconLink" },
 			{ action: "resolveHrefs" },
 			{ action: "replaceCanvasElements" },
+			{ action: "insertFonts" },
 			{ option: "removeHiddenElements", action: "removeHiddenElements" }
 		],
 		async: [
@@ -133,6 +134,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			if (this.options.doc) {
 				const docData = DOM.preProcessDoc(this.options.doc, this.options.win, this.options);
 				this.options.canvasData = docData.canvasData;
+				this.options.fontsData = docData.fontsData;
 				this.options.stylesheetContents = docData.stylesheetContents;
 				this.options.imageData = docData.imageData;
 			}
@@ -614,6 +616,34 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
+		insertFonts() {
+			if (this.options.fontsData && this.options.fontsData.length) {
+				let stylesheetContent = "";
+				this.options.fontsData.forEach(fontData => {
+					const [name, url, details] = fontData;
+					if (name && url) {
+						stylesheetContent += "@font-face{";
+						let propertiesContent = "";
+						Object.keys(details).forEach(detail => {
+							propertiesContent += detail + ":" + details[detail];
+							propertiesContent += ";";
+						});
+						propertiesContent += "font-family:\"" + name + "\";";
+						propertiesContent += "src:" + url;
+						stylesheetContent += propertiesContent + "}";
+					}
+				});
+				const styleElement = this.doc.createElement("style");
+				styleElement.textContent = stylesheetContent;
+				const existingStyleElement = this.doc.querySelector("style");
+				if (existingStyleElement) {
+					existingStyleElement.parentElement.insertBefore(styleElement, existingStyleElement);
+				} else {
+					this.doc.head.insertBefore(styleElement, this.doc.head.firstChild);
+				}
+			}
+		}
+
 		replaceStyleContents() {
 			if (this.options.stylesheetContents) {
 				this.doc.querySelectorAll("style").forEach((styleElement, styleIndex) => {

+ 1 - 0
manifest.json

@@ -17,6 +17,7 @@
 			"run_at": "document_start",
 			"js": [
 				"lib/browser-polyfill/custom-browser-polyfill.js",
+				"lib/single-file/font-face-proxy.js",
 				"extension/index.js",
 				"lib/single-file/doc-helper.js",
 				"lib/single-file/timeout.js",