Просмотр исходного кода

refactored "remove alternative images" implementation

Gildas 7 лет назад
Родитель
Сommit
f44dd97c0a

+ 3 - 0
extension/core/bg/script-loader.js

@@ -61,6 +61,9 @@ singlefile.scriptLoader = (() => {
 			"/lib/single-file/css-media-query-parser.js",
 			"/lib/single-file/css-medias-minifier.js"
 		],
+		removeAlternativeImages: [
+			"/lib/single-file/html-alt-images.js"
+		],
 		removeUnusedStyles: [
 			"/lib/single-file/css-selector-parser.js",
 			"/lib/single-file/css-declarations-parser.js",

+ 12 - 15
lib/single-file/doc-helper.js

@@ -261,14 +261,16 @@ this.docHelper = this.docHelper || (() => {
 	function getImageData(doc, win, options) {
 		if (doc) {
 			const data = [];
-			doc.querySelectorAll("img[src]:not([srcset])").forEach((imageElement, imageElementIndex) => {
+			doc.querySelectorAll("img").forEach((imageElement, imageElementIndex) => {
+				imageElement.setAttribute(imagesAttributeName(options.sessionId), imageElementIndex);
+				const imageData = {
+					src: imageElement.currentSrc || imageElement.src
+				};
 				const computedStyle = win.getComputedStyle(imageElement);
-				let imageData = {};
 				if (computedStyle) {
-					let size = getSize(win, imageElement);
-					if (imageElement.src && size && (!computedStyle.getPropertyValue("background-image") || computedStyle.getPropertyValue("background-image") == "none")) {
-						imageElement.setAttribute(imagesAttributeName(options.sessionId), imageElementIndex);
-						imageData = size;
+					imageData.size = getSize(win, imageElement);
+					if ((!computedStyle.getPropertyValue("background-image") || computedStyle.getPropertyValue("background-image") == "none") && imageData.size.pxWidth > 1 && imageData.size.pxHeight > 1) {
+						imageData.replaceable = true;
 						imageData.objectFit = computedStyle.getPropertyValue("object-fit");
 						imageData.objectPosition = computedStyle.getPropertyValue("object-position");
 					}
@@ -335,15 +337,10 @@ this.docHelper = this.docHelper || (() => {
 						naturalHeight = imgElement.height;
 						imgElement.remove();
 					}
-					imageData.source = {
-						clientWidth: imageElement.clientWidth,
-						clientHeight: imageElement.clientHeight,
-						naturalWidth: naturalWidth,
-						naturalHeight: naturalHeight,
-						width: imageElement.width,
-						height: imageElement.height,
-						src: (!imageElement.currentSrc.startsWith("data:") && imageElement.currentSrc) || (!imageElement.src.startsWith("data:") && imageElement.src)
-					};
+					if (naturalWidth > 1 && naturalHeight > 1) {
+						imageData.src = (!imageElement.currentSrc.startsWith("data:") && imageElement.currentSrc) || (!imageElement.src.startsWith("data:") && imageElement.src);
+						imageData.srcset = imageElement.srcset;
+					}
 				}
 				data.push(imageData);
 			});

+ 113 - 0
lib/single-file/html-alt-images.js

@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   SingleFile is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU Lesser General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   SingleFile is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser General Public License
+ *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* global docHelper, parseSrcset */
+
+this.altImages = this.altImages || (() => {
+
+	const EMPTY_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
+
+	return {
+		process(doc, options) {
+			const dataAttributeName = docHelper.responsiveImagesAttributeName(options.sessionId);
+			doc.querySelectorAll("picture").forEach(pictureElement => {
+				const imgElement = pictureElement.querySelector("img");
+				let srcData = getImgSrcData(imgElement, options);
+				let { src, srcset } = srcData;
+				if (!src) {
+					const sources = Array.from(pictureElement.querySelectorAll("source")).reverse();
+					const data = getSourceSrcData(Array.from(sources));
+					src = data.src;
+					srcset = srcset || data.srcset;
+				}
+				if (!src && options.responsiveImageData) {
+					const responsiveImageData = options.responsiveImageData[Number(imgElement.getAttribute(dataAttributeName))];
+					if (responsiveImageData.src) {
+						src = responsiveImageData.src;
+					} else if (responsiveImageData.sources) {
+						const sources = responsiveImageData.sources.reverse();
+						const data = getSourceSrcData(sources);
+						src = data.src;
+						srcset = srcset || data.srcset;
+					}
+				}
+				setSrc({ src, srcset }, pictureElement.querySelector("img"), pictureElement);
+			});
+			doc.querySelectorAll(":not(picture) > img[srcset]").forEach(imgElement => setSrc(getImgSrcData(imgElement, options), imgElement));
+		}
+	};
+
+	function getImgSrcData(imgElement, options) {
+		const dataAttributeName = docHelper.responsiveImagesAttributeName(options.sessionId);
+		let src = imgElement.getAttribute("src");
+		let srcset = getSourceSrc(imgElement.getAttribute("srcset"));
+		if (options.responsiveImageData) {
+			const responsiveImageData = options.responsiveImageData[Number(imgElement.getAttribute(dataAttributeName))];
+			if (!src && responsiveImageData.src) {
+				src = responsiveImageData.src;
+			}
+			if (srcset && responsiveImageData.srcset) {
+				srcset = getSourceSrc(responsiveImageData.srcset);
+			}
+		}
+		return { src, srcset };
+	}
+
+	function getSourceSrcData(sources) {
+		let source = sources.find(source => source.src);
+		let src = source && source.src;
+		let srcset = source && source.srcset;
+		if (!src) {
+			source = sources.find(source => getSourceSrc(source.src));
+			src = source && source.src;
+		}
+		if (!srcset) {
+			source = sources.find(source => getSourceSrc(source.srcset));
+			srcset = source && source.srcset;
+		}
+		return { src, srcset };
+	}
+
+	function setSrc(srcData, imgElement, pictureElement) {
+		imgElement.src = EMPTY_IMAGE;
+		imgElement.setAttribute("srcset", "");
+		if (srcData.src) {
+			imgElement.src = srcData.src;
+		} else {
+			if (imgElement.getAttribute("srcset")) {
+				imgElement.setAttribute("srcset", srcData.srcset || "");
+				if (!srcData.srcset) {
+					imgElement.setAttribute("sizes", "");
+				}
+			}
+		}
+		if (pictureElement) {
+			pictureElement.querySelectorAll("source").forEach(sourceElement => sourceElement.remove());
+		}
+	}
+
+	function getSourceSrc(sourceSrcSet) {
+		if (sourceSrcSet) {
+			const srcset = parseSrcset.process(sourceSrcSet);
+			return (srcset.find(srcset => srcset.url)).url;
+		}
+	}
+
+})();

+ 5 - 1
lib/single-file/single-file-browser.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global SingleFileCore, DOMParser, TextDecoder, Blob, fetch, base64, superFetch, parseSrcset, uglifycss, htmlmini, cssMinifier, fontsMinifier, lazyLoader, serializer, docHelper, mediasMinifier, TextEncoder, crypto, RulesMatcher */
+/* global SingleFileCore, DOMParser, TextDecoder, Blob, fetch, base64, superFetch, parseSrcset, uglifycss, htmlmini, cssMinifier, fontsMinifier, lazyLoader, serializer, docHelper, mediasMinifier, TextEncoder, crypto, RulesMatcher, altImages */
 
 this.SingleFile = this.SingleFile || (() => {
 
@@ -231,6 +231,10 @@ this.SingleFile = this.SingleFile || (() => {
 			return mediasMinifier.process(doc);
 		}
 
+		static removeAlternativeImages(doc, options) {
+			return altImages.process(doc, options);
+		}
+
 		static parseSrcset(srcset) {
 			return parseSrcset.process(srcset);
 		}

+ 16 - 52
lib/single-file/single-file-core.js

@@ -78,7 +78,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	// -------------
 	const STAGES = [{
 		sequential: [
-			{ action: "repairDocument" },
+			{ action: "preProcessPage" },
 			{ action: "replaceStyleContents" },
 			{ option: "selected", action: "removeUnselectedElements" },
 			{ option: "removeVideoSrc", action: "insertVideoPosters" },
@@ -116,6 +116,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 	}, {
 		sequential: [
 			{ option: "lazyLoadImages", action: "lazyLoadImages" },
+			{ option: "removeAlternativeImages", action: "removeAlternativeImages" },
 			{ option: "removeAlternativeFonts", action: "removeAlternativeFonts" },
 			{ option: "compressCSS", action: "compressCSS" }
 		],
@@ -436,9 +437,18 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			}
 		}
 
-		repairDocument() {
+		preProcessPage() {
 			this.doc.querySelectorAll("singlefile-infobar, singlefile-mask").forEach(element => element.remove());
 			this.doc.body.querySelectorAll("title, meta").forEach(element => this.doc.head.appendChild(element));
+			if (this.options.imageData) {
+				const dataAttributeName = DOM.imagesAttributeName(this.options.sessionId);
+				this.doc.querySelectorAll("img").forEach(imgElement => {
+					const imgData = this.options.imageData[Number(imgElement.getAttribute(dataAttributeName))];
+					if (imgData.src) {
+						imgElement.setAttribute("src", imgData.src);
+		}
+				});
+			}
 		}
 
 		removeScripts() {
@@ -522,56 +532,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 		}
 
 		removeAlternativeImages() {
-			this.doc.querySelectorAll("picture, img[srcset]").forEach(element => {
-				const tagName = element.tagName.toLowerCase();
-				const dataAttributeName = DOM.responsiveImagesAttributeName(this.options.sessionId);
-				const responsiveImageData = this.options.responsiveImageData && this.options.responsiveImageData[Number(element.getAttribute(dataAttributeName))];
-				element.removeAttribute(dataAttributeName);
-				if (responsiveImageData) {
-					if (tagName == "img") {
-						if (responsiveImageData.source.src && responsiveImageData.source.naturalWidth > 1 && responsiveImageData.source.naturalHeight > 1) {
-							element.setAttribute("srcset", "");
-							element.setAttribute("sizes", "");
-							element.src = responsiveImageData.source.src;
-						}
-					}
-					if (tagName == "picture") {
-						const imageElement = element.querySelector("img");
-						if (responsiveImageData.source && responsiveImageData.source.src && responsiveImageData.source.naturalWidth > 1 && responsiveImageData.source.naturalHeight > 1) {
-							imageElement.setAttribute("srcset", "");
-							imageElement.setAttribute("sizes", "");
-							imageElement.src = responsiveImageData.source.src;
-							element.querySelectorAll("source").forEach(sourceElement => sourceElement.remove());
-						} else {
-							if (responsiveImageData.sources) {
-								element.querySelectorAll("source").forEach(sourceElement => {
-									if (!sourceElement.srcset && !sourceElement.dataset.srcset && !sourceElement.src) {
-										sourceElement.remove();
-									}
-								});
-								const sourceElements = element.querySelectorAll("source");
-								if (sourceElements.length) {
-									const lastSourceElement = sourceElements[sourceElements.length - 1];
-									if (imageElement) {
-										if (lastSourceElement.src) {
-											imageElement.src = lastSourceElement.src;
-										} else {
-											imageElement.removeAttribute("src");
-										}
-										if (lastSourceElement.srcset || lastSourceElement.dataset.srcset) {
-											imageElement.srcset = lastSourceElement.srcset || lastSourceElement.dataset.srcset;
-										} else {
-											imageElement.removeAttribute("srcset");
-										}
-										element.querySelectorAll("source").forEach(sourceElement => sourceElement.remove());
-									}
-								}
-							}
+			DOM.removeAlternativeImages(this.doc, this.options);
 						}
-					}
-				}
-			});
-		}
 
 		postRemoveAlternativeFonts() {
 			DOM.minifyFonts(this.doc, true);
@@ -1351,7 +1313,8 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 			const dataAttributeName = DOM.imagesAttributeName(options.sessionId);
 			if (imgElement.getAttribute(dataAttributeName) != null) {
 				const imgData = options.imageData[Number(imgElement.getAttribute(dataAttributeName))];
-				imgElement.setAttribute("src", `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="${imgData.pxWidth}" height="${imgData.pxHeight}"><rect fill-opacity="0"/></svg>`);
+				if (imgData.replaceable) {
+					imgElement.setAttribute("src", `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="${imgData.size.pxWidth}" height="${imgData.size.pxHeight}"><rect fill-opacity="0"/></svg>`);
 				if (options.lazyLoadImages) {
 					imgElement.setAttribute(GROUPED_IMG_ATTRIBUTE_NAME, "");
 				}
@@ -1368,6 +1331,7 @@ this.SingleFileCore = this.SingleFileCore || (() => {
 				return true;
 			}
 		}
+		}
 
 	}
 

+ 1 - 0
manifest.json

@@ -76,6 +76,7 @@
 			"lib/single-file/html-minifier.js",
 			"lib/single-file/html-serializer.js",
 			"lib/single-file/lazy-loader.js",
+			"lib/single-file/html-alt-images.js",
 			"lib/single-file/single-file-core.js",
 			"lib/single-file/single-file-browser.js"
 		],