Browse Source

fixed issue #151

Gildas 7 years ago
parent
commit
1f908492d4

+ 0 - 5
cli/back-ends/puppeteer.js

@@ -52,10 +52,6 @@ const SCRIPTS = [
 ];
 
 exports.getPageData = async options => {
-	const RESOLVED_CONTENTS = {
-		"lib/lazy/web/web-lazy-loader-before.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-before.js")).toString(),
-		"lib/lazy/web/web-lazy-loader-after.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-after.js")).toString()
-	};
 	const browserOptions = {};
 	if (options.browserHeadless !== undefined) {
 		browserOptions.headless = options.browserHeadless && !options.browserDebug;
@@ -88,7 +84,6 @@ exports.getPageData = async options => {
 			await page.setBypassCSP(true);
 		}
 		let scripts = SCRIPTS.map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString()).join("\n");
-		scripts += "\nlazyLoader.getScriptContent = " + (function (path) { return (RESOLVED_CONTENTS)[path]; }).toString().replace("RESOLVED_CONTENTS", JSON.stringify(RESOLVED_CONTENTS)) + ";";
 		await page.evaluateOnNewDocument(scripts);
 		if (options.browserDebug) {
 			await page.waitFor(3000);

+ 0 - 5
cli/back-ends/webdriver-chromium.js

@@ -54,10 +54,6 @@ const SCRIPTS = [
 ];
 
 exports.getPageData = async options => {
-	const RESOLVED_CONTENTS = {
-		"lib/lazy/web/web-lazy-loader-before.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-before.js")).toString(),
-		"lib/lazy/web/web-lazy-loader-after.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-after.js")).toString()
-	};
 	let driver;
 	try {
 		const builder = new Builder();
@@ -112,7 +108,6 @@ exports.getPageData = async options => {
 			}
 		}
 		let scripts = SCRIPTS.map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString()).join("\n");
-		scripts += "\nlazyLoader.getScriptContent = " + (function (path) { return (RESOLVED_CONTENTS)[path]; }).toString().replace("RESOLVED_CONTENTS", JSON.stringify(RESOLVED_CONTENTS)) + ";";
 		if (options.browserDebug) {
 			await driver.sleep(3000);
 		}

+ 0 - 5
cli/back-ends/webdriver-gecko.js

@@ -54,10 +54,6 @@ const SCRIPTS = [
 ];
 
 exports.getPageData = async options => {
-	const RESOLVED_CONTENTS = {
-		"lib/lazy/web/web-lazy-loader-before.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-before.js")).toString(),
-		"lib/lazy/web/web-lazy-loader-after.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-after.js")).toString()
-	};
 	let driver;
 	try {
 		const builder = new Builder().withCapabilities({ "pageLoadStrategy": "none" });
@@ -102,7 +98,6 @@ exports.getPageData = async options => {
 			}
 		}
 		let scripts = SCRIPTS.map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString().replace(/\n(this)\.([^ ]+) = (this)\.([^ ]+) \|\|/g, "\nwindow.$2 = window.$4 ||")).join("\n");
-		scripts += "\nlazyLoader.getScriptContent = " + (function (path) { return (RESOLVED_CONTENTS)[path]; }).toString().replace("RESOLVED_CONTENTS", JSON.stringify(RESOLVED_CONTENTS)) + ";";
 		if (options.browserDebug) {
 			await driver.findElement(By.css("html")).sendKeys(Key.SHIFT + Key.F5);
 			await driver.sleep(3000);

+ 1 - 2
extension/core/bg/core.js

@@ -60,8 +60,7 @@ singlefile.core = (() => {
 			"/lib/single-file/vendor/css-minifier.js"
 		],
 		loadDeferredImages: [
-			"/lib/lazy/content/content-lazy-loader.js",
-			function () { this.lazyLoader.getScriptPath = path => browser.runtime.getURL(path); }
+			"/lib/lazy/content/content-lazy-loader.js"
 		],
 		removeAlternativeImages: [
 			"/lib/single-file/modules/html-images-alt-minifier.js"

+ 114 - 19
lib/hooks/hooks-frame.js

@@ -18,10 +18,12 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global window, addEventListener, dispatchEvent, CustomEvent, document, HTMLDocument, FileReader, Blob */
+/* global window, addEventListener, dispatchEvent, CustomEvent, document, HTMLDocument, FileReader, Blob, setTimeout, clearTimeout, screen, Element, UIEvent */
 
 this.hooksFrame = this.hooksFrame || (() => {
 
+	const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
+	const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
 	const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
 	const fontFaces = [];
 
@@ -34,12 +36,15 @@ this.hooksFrame = this.hooksFrame || (() => {
 	}
 
 	return {
-		getFontsData: () => fontFaces
+		getFontsData: () => fontFaces,
+		loadDeferredImagesStart: () => dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_START_EVENT)),
+		loadDeferredImagesEnd: () => dispatchEvent(new CustomEvent(LOAD_DEFERRED_IMAGES_END_EVENT))
 	};
 
 	function hook() {
+		const LOAD_DEFERRED_IMAGES_START_EVENT = "single-file-load-deferred-images-start";
+		const LOAD_DEFERRED_IMAGES_END_EVENT = "single-file-load-deferred-images-end";
 		const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
-		const LOAD_OBSERVED_ELEMENTS_EVENT = "single-file-load-observed-elements";
 		const FONT_STYLE_PROPERTIES = {
 			family: "font-family",
 			style: "font-style",
@@ -50,6 +55,112 @@ this.hooksFrame = this.hooksFrame || (() => {
 			featureSettings: "font-feature-settings"
 		};
 
+		const requestAnimationFrame = window.requestAnimationFrame;
+		const cancelAnimationFrame = window.cancelAnimationFrame;
+		const observers = new Map();
+		const observedElements = new Map();
+		let loadDeferredImages;
+
+		addEventListener(LOAD_DEFERRED_IMAGES_START_EVENT, () => {
+			loadDeferredImages = true;
+			const clientHeight = document.documentElement.clientHeight;
+			const clientWidth = document.documentElement.clientWidth;
+			const scrollHeight = Math.max(document.documentElement.scrollHeight - (clientHeight * .5), clientHeight);
+			const scrollWidth = Math.max(document.documentElement.scrollWidth - (clientWidth * .5), clientWidth);
+			window._singleFile_innerHeight = window.innerHeight;
+			window._singleFile_innerWidth = window.innerWidth;
+			window.__defineGetter__("innerHeight", () => scrollHeight);
+			window.__defineGetter__("innerWidth", () => scrollWidth);
+			document.documentElement.__defineGetter__("clientHeight", () => scrollHeight);
+			document.documentElement.__defineGetter__("clientWidth", () => scrollWidth);
+			screen.__defineGetter__("height", () => scrollHeight);
+			screen.__defineGetter__("width", () => scrollWidth);
+			window._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
+			Element.prototype.getBoundingClientRect = function () {
+				const boundingRect = window._singleFile_getBoundingClientRect.call(this);
+				if (this == document.documentElement) {
+					boundingRect.__defineGetter__("height", () => scrollHeight);
+					boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
+					boundingRect.__defineGetter__("width", () => scrollWidth);
+					boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
+				}
+				return boundingRect;
+			};
+			window._singleFile_localStorage = window.localStorage;
+			window.__defineGetter__("localStorage", () => { throw new Error("localStorage temporary blocked by SingleFile"); });
+			document.__defineGetter__("cookie", () => { throw new Error("document.cookie temporary blocked by SingleFile"); });
+			dispatchEvent(new UIEvent("resize"));
+			dispatchEvent(new UIEvent("scroll"));
+			const docBoundingRect = document.documentElement.getBoundingClientRect();
+			Array.from(observers).forEach(([intersectionObserver, observer]) => {
+				const rootBoundingRect = observer.options.root && observer.options.root.getBoundingClientRect();
+				observer.callback(observedElements.get(intersectionObserver).map(target => {
+					const boundingClientRect = target.getBoundingClientRect();
+					const isIntersecting = true;
+					const intersectionRatio = 1;
+					const rootBounds = observer.options && observer.options.root ? rootBoundingRect : docBoundingRect;
+					const time = 0;
+					return { target, intersectionRatio, boundingClientRect, intersectionRect: boundingClientRect, isIntersecting, rootBounds, time };
+				}), intersectionObserver);
+			});
+			if (pendingRequestAnimationFrameCalls.size) {
+				Array.from(pendingRequestAnimationFrameCalls).forEach(([id, callback]) => {
+					pendingRequestAnimationFrameCalls.delete(id);
+					cancelAnimationFrame(id);
+					callback();
+				});
+			}
+		});
+
+		addEventListener(LOAD_DEFERRED_IMAGES_END_EVENT, () => {
+			loadDeferredImages = false;
+			delete document.documentElement.clientHeight;
+			delete document.documentElement.clientWidth;
+			delete screen.height;
+			delete screen.width;
+			if (window._singleFile_getBoundingClientRect) {
+				Element.prototype.getBoundingClientRect = window._singleFile_getBoundingClientRect;
+				window.innerHeight = window._singleFile_innerHeight;
+				window.innerWidth = window._singleFile_innerWidth;
+				delete window._singleFile_getBoundingClientRect;
+				delete window._singleFile_innerHeight;
+				delete window._singleFile_innerWidth;
+				delete document.cookie;
+				delete window.localStorage;
+				window.localStorage = window._singleFile_localStorage;
+				delete window._singleFile_localStorage;
+			}
+			dispatchEvent(new UIEvent("resize"));
+			dispatchEvent(new UIEvent("scroll"));
+		});
+
+		let warningRequestAnimationFrameDisplayed;
+		const pendingRequestAnimationFrameCalls = new Map();
+		window.requestAnimationFrame = function (callback) {
+			if (!warningRequestAnimationFrameDisplayed) {
+				console.warn("SingleFile is hooking the requestAnimationFrame function to load deferred images."); // eslint-disable-line no-console
+				warningRequestAnimationFrameDisplayed = true;
+			}
+			if (loadDeferredImages) {
+				return setTimeout(callback, 0);
+			} else {
+				const id = requestAnimationFrame(() => {
+					pendingRequestAnimationFrameCalls.delete(id);
+					callback();
+				});
+				pendingRequestAnimationFrameCalls.set(id, callback);
+			}
+		};
+
+		window.cancelAnimationFrame = function (id) {
+			if (loadDeferredImages) {
+				return clearTimeout(id);
+			} else {
+				pendingRequestAnimationFrameCalls.delete(id);
+				return cancelAnimationFrame(id);
+			}
+		};
+
 		const FontFace = window.FontFace;
 		let warningFontFaceDisplayed;
 		window.__defineGetter__("FontFace", () => new Proxy(FontFace, {
@@ -87,8 +198,6 @@ this.hooksFrame = this.hooksFrame || (() => {
 			const IntersectionObserver = window.IntersectionObserver;
 			const observeIntersection = IntersectionObserver.prototype.observe;
 			const unobserveIntersection = IntersectionObserver.prototype.unobserve;
-			const observedElements = new Map();
-			const observers = new Map();
 			let warningIntersectionObserverDisplayed;
 			window.__defineGetter__("IntersectionObserver", () => new Proxy(IntersectionObserver, {
 				construct(IntersectionObserver, argumentsList) {
@@ -125,20 +234,6 @@ this.hooksFrame = this.hooksFrame || (() => {
 				}
 			}));
 			window.__defineSetter__("IntersectionObserver", () => { });
-			addEventListener(LOAD_OBSERVED_ELEMENTS_EVENT, () => {
-				const docBoundingRect = document.documentElement.getBoundingClientRect();
-				Array.from(observers).forEach(([intersectionObserver, observer]) => {
-					const rootBoundingRect = observer.options.root && observer.options.root.getBoundingClientRect();
-					observer.callback(observedElements.get(intersectionObserver).map(target => {
-						const boundingClientRect = target.getBoundingClientRect();
-						const isIntersecting = true;
-						const intersectionRatio = 1;
-						const rootBounds = observer.options && observer.options.root ? rootBoundingRect : docBoundingRect;
-						const time = 0;
-						return { target, intersectionRatio, boundingClientRect, intersectionRect: boundingClientRect, isIntersecting, rootBounds, time };
-					}), intersectionObserver);
-				});
-			}, false);
 		}
 	}
 

+ 3 - 17
lib/lazy/content/content-lazy-loader.js

@@ -18,14 +18,11 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, document, MutationObserver, setTimeout, clearTimeout, lazyLoader */
+/* global browser, document, MutationObserver, setTimeout, clearTimeout, hooksFrame */
 
 this.lazyLoader = this.lazyLoader || (() => {
 
-	const SCRIPT_TAG_NAME = "script";
 	const ATTRIBUTES_MUTATION_TYPE = "attributes";
-	const SCRIPT_BEFORE_PATH = "lib/lazy/web/web-lazy-loader-before.js";
-	const SCRIPT_AFTER_PATH = "lib/lazy/web/web-lazy-loader-after.js";
 	const SINGLE_FILE_UI_ELEMENT_CLASS = "single-file-ui-element";
 	const LAZY_SRC_ATTRIBUTE_NAME = "data-lazy-loaded-src";
 
@@ -62,7 +59,7 @@ this.lazyLoader = this.lazyLoader || (() => {
 					lazyLoadEnd(idleTimeoutId, observer, resolve);
 				}
 			}, options.loadDeferredImagesMaxIdleTime * 1.2);
-			injectScript(SCRIPT_BEFORE_PATH);
+			hooksFrame.loadDeferredImagesStart();
 		});
 	}
 
@@ -73,22 +70,11 @@ this.lazyLoader = this.lazyLoader || (() => {
 
 	function lazyLoadEnd(idleTimeoutId, observer, resolve) {
 		clearAsyncTimeout(idleTimeoutId);
-		injectScript(SCRIPT_AFTER_PATH);
+		hooksFrame.loadDeferredImagesEnd();
 		setAsyncTimeout(resolve, 100);
 		observer.disconnect();
 	}
 
-	function injectScript(path) {
-		const scriptElement = document.createElement(SCRIPT_TAG_NAME);
-		if (lazyLoader.getScriptPath) {
-			scriptElement.src = lazyLoader.getScriptPath(path);
-		} else {
-			scriptElement.textContent = lazyLoader.getScriptContent(path);
-		}
-		(document.documentElement || document).appendChild(scriptElement);
-		scriptElement.onload = () => scriptElement.remove();
-	}
-
 	async function setAsyncTimeout(callback, delay) {
 		if (this.browser && browser.runtime && browser.runtime.sendMessage) {
 			const timeoutId = await browser.runtime.sendMessage({ setTimeoutRequest: true, delay });

+ 0 - 44
lib/lazy/web/web-lazy-loader-after.js

@@ -1,44 +0,0 @@
-/*
- * Copyright 2010-2019 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 screen, window, document, dispatchEvent, UIEvent, Element */
-
-(() => {
-
-	delete document.documentElement.clientHeight;
-	delete document.documentElement.clientWidth;
-	delete screen.height;
-	delete screen.width;
-	if (window._singleFile_getBoundingClientRect) {
-		Element.prototype.getBoundingClientRect = window._singleFile_getBoundingClientRect;
-		window.innerHeight = window._singleFile_innerHeight;
-		window.innerWidth = window._singleFile_innerWidth;
-		delete window._singleFile_getBoundingClientRect;
-		delete window._singleFile_innerHeight;
-		delete window._singleFile_innerWidth;
-		delete document.cookie;
-		delete window.localStorage;
-		window.localStorage = window._singleFile_localStorage;
-		delete window._singleFile_localStorage;
-	}
-	dispatchEvent(new UIEvent("resize"));
-	dispatchEvent(new UIEvent("scroll"));
-
-})();

+ 0 - 56
lib/lazy/web/web-lazy-loader-before.js

@@ -1,56 +0,0 @@
-/*
- * Copyright 2010-2019 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 screen, window, document, dispatchEvent, UIEvent, CustomEvent, Element */
-
-(() => {
-
-	const LOAD_OBSERVED_ELEMENTS_EVENT = "single-file-load-observed-elements";
-	const clientHeight = document.documentElement.clientHeight;
-	const clientWidth = document.documentElement.clientWidth;
-	const scrollHeight = Math.max(document.documentElement.scrollHeight - (clientHeight * .5), clientHeight);
-	const scrollWidth = Math.max(document.documentElement.scrollWidth - (clientWidth * .5), clientWidth);
-	window._singleFile_innerHeight = window.innerHeight;
-	window._singleFile_innerWidth = window.innerWidth;
-	window.__defineGetter__("innerHeight", () => scrollHeight);
-	window.__defineGetter__("innerWidth", () => scrollWidth);
-	document.documentElement.__defineGetter__("clientHeight", () => scrollHeight);
-	document.documentElement.__defineGetter__("clientWidth", () => scrollWidth);
-	screen.__defineGetter__("height", () => scrollHeight);
-	screen.__defineGetter__("width", () => scrollWidth);
-	window._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
-	Element.prototype.getBoundingClientRect = function () {
-		const boundingRect = window._singleFile_getBoundingClientRect.call(this);
-		if (this == document.documentElement) {
-			boundingRect.__defineGetter__("height", () => scrollHeight);
-			boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
-			boundingRect.__defineGetter__("width", () => scrollWidth);
-			boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
-		}
-		return boundingRect;
-	};
-	window._singleFile_localStorage = window.localStorage;
-	window.__defineGetter__("localStorage", () => { throw new Error("localStorage temporary blocked by SingleFile"); });
-	document.__defineGetter__("cookie", () => { throw new Error("document.cookie temporary blocked by SingleFile"); });
-	dispatchEvent(new UIEvent("resize"));
-	dispatchEvent(new UIEvent("scroll"));
-	dispatchEvent(new CustomEvent(LOAD_OBSERVED_ELEMENTS_EVENT));
-
-})();

+ 0 - 5
maff2html/back-ends/webdriver-gecko.js

@@ -54,10 +54,6 @@ const SCRIPTS = [
 ];
 
 exports.getPageData = async options => {
-	const RESOLVED_CONTENTS = {
-		"lib/lazy/web/web-lazy-loader-before.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-before.js")).toString(),
-		"lib/lazy/web/web-lazy-loader-after.js": fs.readFileSync(require.resolve("../../lib/lazy/web/web-lazy-loader-after.js")).toString()
-	};
 	let driver;
 	try {
 		const builder = new Builder().withCapabilities({ "pageLoadStrategy": "eager" });
@@ -102,7 +98,6 @@ exports.getPageData = async options => {
 			}
 		}
 		let scripts = SCRIPTS.map(scriptPath => fs.readFileSync(require.resolve(scriptPath)).toString().replace(/\n(this)\.([^ ]+) = (this)\.([^ ]+) \|\|/g, "\nwindow.$2 = window.$4 ||")).join("\n");
-		scripts += "\nlazyLoader.getScriptContent = " + (function (path) { return (RESOLVED_CONTENTS)[path]; }).toString().replace("RESOLVED_CONTENTS", JSON.stringify(RESOLVED_CONTENTS)) + ";";
 		if (options.browserDebug) {
 			await driver.findElement(By.css("html")).sendKeys(Key.SHIFT + Key.F5);
 			await driver.sleep(3000);

+ 0 - 4
manifest.json

@@ -126,10 +126,6 @@
 		}
 	},
 	"incognito": "spanning",
-	"web_accessible_resources": [
-		"lib/lazy/web/web-lazy-loader-before.js",
-		"lib/lazy/web/web-lazy-loader-after.js"
-	],
 	"manifest_version": 2,
 	"default_locale": "en"
 }