|
|
@@ -35,6 +35,7 @@ this.singlefile.lib.hooks.content.frames = this.singlefile.lib.hooks.content.fra
|
|
|
const IMAGE_LOADED_EVENT = "single-file-image-loaded";
|
|
|
const NEW_FONT_FACE_EVENT = "single-file-new-font-face";
|
|
|
|
|
|
+ const browser = this.browser;
|
|
|
const addEventListener = window.addEventListener;
|
|
|
const dispatchEvent = window.dispatchEvent;
|
|
|
const CustomEvent = window.CustomEvent;
|
|
|
@@ -44,9 +45,14 @@ this.singlefile.lib.hooks.content.frames = this.singlefile.lib.hooks.content.fra
|
|
|
const fontFaces = [];
|
|
|
|
|
|
if (document instanceof HTMLDocument) {
|
|
|
- const scriptElement = document.createElement("script");
|
|
|
- scriptElement.textContent = "(" + injectedScript.toString() + ")()";
|
|
|
- document.documentElement.prepend(scriptElement);
|
|
|
+ let scriptElement = document.createElement("script");
|
|
|
+ if (browser && browser.runtime && browser.runtime.getURL) {
|
|
|
+ scriptElement.src = browser.runtime.getURL("/lib/hooks/content/content-hooks-frames-web.js");
|
|
|
+ scriptElement.async = false;
|
|
|
+ } else if (this.singlefile.lib.getFileContent) {
|
|
|
+ scriptElement.textContent = this.singlefile.lib.getFileContent("/lib/hooks/content/content-hooks-frames-web.js");
|
|
|
+ }
|
|
|
+ (document.documentElement || document).appendChild(scriptElement);
|
|
|
scriptElement.remove();
|
|
|
addEventListener.call(window, NEW_FONT_FACE_EVENT, event => fontFaces.push(event.detail));
|
|
|
}
|
|
|
@@ -75,328 +81,4 @@ this.singlefile.lib.hooks.content.frames = this.singlefile.lib.hooks.content.fra
|
|
|
IMAGE_LOADED_EVENT
|
|
|
};
|
|
|
|
|
|
- function injectedScript() {
|
|
|
- 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 BLOCK_COOKIES_START_EVENT = "single-file-block-cookies-start";
|
|
|
- const BLOCK_COOKIES_END_EVENT = "single-file-block-cookies-end";
|
|
|
- const BLOCK_STORAGE_START_EVENT = "single-file-block-storage-start";
|
|
|
- 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 FONT_STYLE_PROPERTIES = {
|
|
|
- family: "font-family",
|
|
|
- style: "font-style",
|
|
|
- weight: "font-weight",
|
|
|
- stretch: "font-stretch",
|
|
|
- unicodeRange: "unicode-range",
|
|
|
- variant: "font-variant",
|
|
|
- featureSettings: "font-feature-settings"
|
|
|
- };
|
|
|
-
|
|
|
- const addEventListener = window.addEventListener;
|
|
|
- const dispatchEvent = window.dispatchEvent;
|
|
|
- const CustomEvent = window.CustomEvent;
|
|
|
- const document = window.document;
|
|
|
- const screen = window.screen;
|
|
|
- const Element = window.Element;
|
|
|
- const UIEvent = window.UIEvent;
|
|
|
- const FileReader = window.FileReader;
|
|
|
- const Blob = window.Blob;
|
|
|
- const requestAnimationFrame = window.requestAnimationFrame;
|
|
|
- const cancelAnimationFrame = window.cancelAnimationFrame;
|
|
|
- const console = window.console;
|
|
|
- const warn = console.warn;
|
|
|
-
|
|
|
- const observers = new Map();
|
|
|
- const observedElements = new Map();
|
|
|
- let loadDeferredImages;
|
|
|
-
|
|
|
- addEventListener.call(window, LOAD_DEFERRED_IMAGES_START_EVENT, () => {
|
|
|
- loadDeferredImages = true;
|
|
|
- const scrollingElement = document.scrollingElement || document.documentElement;
|
|
|
- const clientHeight = scrollingElement.clientHeight;
|
|
|
- const clientWidth = scrollingElement.clientWidth;
|
|
|
- const scrollHeight = Math.max(scrollingElement.scrollHeight - (clientHeight * .5), clientHeight);
|
|
|
- const scrollWidth = Math.max(scrollingElement.scrollWidth - (clientWidth * .5), clientWidth);
|
|
|
- scrollingElement.__defineGetter__("clientHeight", () => scrollHeight);
|
|
|
- scrollingElement.__defineGetter__("clientWidth", () => scrollWidth);
|
|
|
- screen.__defineGetter__("height", () => scrollHeight);
|
|
|
- screen.__defineGetter__("width", () => scrollWidth);
|
|
|
- if (!window._singleFile_getBoundingClientRect) {
|
|
|
- window._singleFile_getBoundingClientRect = Element.prototype.getBoundingClientRect;
|
|
|
- Element.prototype.getBoundingClientRect = function () {
|
|
|
- const boundingRect = window._singleFile_getBoundingClientRect.call(this);
|
|
|
- if (this == scrollingElement) {
|
|
|
- boundingRect.__defineGetter__("height", () => scrollHeight);
|
|
|
- boundingRect.__defineGetter__("bottom", () => scrollHeight + boundingRect.top);
|
|
|
- boundingRect.__defineGetter__("width", () => scrollWidth);
|
|
|
- boundingRect.__defineGetter__("right", () => scrollWidth + boundingRect.left);
|
|
|
- }
|
|
|
- return boundingRect;
|
|
|
- };
|
|
|
- window._singleFile_innerHeight = window.innerHeight;
|
|
|
- window._singleFile_innerWidth = window.innerWidth;
|
|
|
- window.__defineGetter__("innerHeight", () => scrollHeight);
|
|
|
- window.__defineGetter__("innerWidth", () => scrollWidth);
|
|
|
- }
|
|
|
- if (!window._singleFileImage) {
|
|
|
- const Image = window.Image;
|
|
|
- window._singleFileImage = window.Image;
|
|
|
- window.__defineGetter__("Image", function () {
|
|
|
- return function () {
|
|
|
- const image = new Image(...arguments);
|
|
|
- const result = new Image(...arguments);
|
|
|
- result.__defineSetter__("src", function (value) {
|
|
|
- image.src = value;
|
|
|
- dispatchEvent.call(window, new CustomEvent(LOAD_IMAGE_EVENT, { detail: image.src }));
|
|
|
- });
|
|
|
- result.__defineGetter__("src", function () {
|
|
|
- return image.src;
|
|
|
- });
|
|
|
- result.__defineSetter__("srcset", function (value) {
|
|
|
- dispatchEvent.call(window, new CustomEvent(LOAD_IMAGE_EVENT));
|
|
|
- image.srcset = value;
|
|
|
- });
|
|
|
- result.__defineGetter__("srcset", function () {
|
|
|
- return image.srcset;
|
|
|
- });
|
|
|
- image.onload = image.onloadend = image.onerror = event => {
|
|
|
- dispatchEvent.call(window, new CustomEvent(IMAGE_LOADED_EVENT, { detail: image.src }));
|
|
|
- result.dispatchEvent(new UIEvent(event.type, event));
|
|
|
- };
|
|
|
- if (image.decode) {
|
|
|
- result.decode = () => image.decode();
|
|
|
- }
|
|
|
- return result;
|
|
|
- };
|
|
|
- });
|
|
|
- }
|
|
|
- const zoomFactorX = (clientHeight + window.scrollY) / scrollHeight;
|
|
|
- const zoomFactorY = (clientWidth + window.scrollX) / scrollWidth;
|
|
|
- const zoomFactor = Math.min(zoomFactorX, zoomFactorY);
|
|
|
- if (zoomFactor < 1) {
|
|
|
- const transform = document.documentElement.style.getPropertyValue("transform");
|
|
|
- const transformPriority = document.documentElement.style.getPropertyPriority("transform");
|
|
|
- const transformOrigin = document.documentElement.style.getPropertyValue("transform-origin");
|
|
|
- const transformOriginPriority = document.documentElement.style.getPropertyPriority("transform-origin");
|
|
|
- document.documentElement.style.setProperty("transform-origin", (zoomFactorX < 1 ? "50%" : "0") + " " + (zoomFactorY < 1 ? "50%" : "0") + " 0", "important");
|
|
|
- document.documentElement.style.setProperty("transform", "scale3d(" + zoomFactor + ", " + zoomFactor + ", 1)", "important");
|
|
|
- dispatchEvent.call(window, new UIEvent("resize"));
|
|
|
- dispatchEvent.call(window, new UIEvent("scroll"));
|
|
|
- document.documentElement.style.setProperty("transform", transform, transformPriority);
|
|
|
- document.documentElement.style.setProperty("transform-origin", transformOrigin, transformOriginPriority);
|
|
|
- }
|
|
|
- dispatchEvent.call(window, new UIEvent("resize"));
|
|
|
- dispatchEvent.call(window, new UIEvent("scroll"));
|
|
|
- const docBoundingRect = scrollingElement.getBoundingClientRect();
|
|
|
- Array.from(observers, ([intersectionObserver, observer]) => {
|
|
|
- const rootBoundingRect = observer.options && observer.options.root && observer.options.root.getBoundingClientRect();
|
|
|
- const targetElements = observedElements.get(intersectionObserver);
|
|
|
- if (targetElements) {
|
|
|
- observer.callback(targetElements.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]) => {
|
|
|
- cancelAnimationFrame(id);
|
|
|
- callback();
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
-
|
|
|
- addEventListener.call(window, LOAD_DEFERRED_IMAGES_END_EVENT, () => {
|
|
|
- const scrollingElement = document.scrollingElement || document.documentElement;
|
|
|
- delete scrollingElement.clientHeight;
|
|
|
- delete scrollingElement.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;
|
|
|
- }
|
|
|
- if (window._singleFileImage) {
|
|
|
- delete window.Image;
|
|
|
- window.Image = window._singleFileImage;
|
|
|
- delete window._singleFileImage;
|
|
|
- }
|
|
|
- loadDeferredImages = false;
|
|
|
- dispatchEvent.call(window, new UIEvent("resize"));
|
|
|
- dispatchEvent.call(window, new UIEvent("scroll"));
|
|
|
- });
|
|
|
-
|
|
|
- addEventListener.call(window, BLOCK_COOKIES_START_EVENT, () => {
|
|
|
- try {
|
|
|
- document.__defineGetter__("cookie", () => { throw new Error("document.cookie temporary blocked by SingleFile"); });
|
|
|
- } catch (error) {
|
|
|
- // ignored
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- addEventListener.call(window, BLOCK_COOKIES_END_EVENT, () => {
|
|
|
- delete document.cookie;
|
|
|
- });
|
|
|
-
|
|
|
- addEventListener.call(window, BLOCK_STORAGE_START_EVENT, () => {
|
|
|
- if (!window._singleFile_localStorage) {
|
|
|
- window._singleFile_localStorage = window.localStorage;
|
|
|
- window.__defineGetter__("localStorage", () => { throw new Error("localStorage temporary blocked by SingleFile"); });
|
|
|
- }
|
|
|
- if (!window._singleFile_indexedDB) {
|
|
|
- window._singleFile_indexedDB = window.indexedDB;
|
|
|
- window.__defineGetter__("indexedDB", () => { throw new Error("indexedDB temporary blocked by SingleFile"); });
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- addEventListener.call(window, BLOCK_STORAGE_END_EVENT, () => {
|
|
|
- if (window._singleFile_localStorage) {
|
|
|
- delete window.localStorage;
|
|
|
- window.localStorage = window._singleFile_localStorage;
|
|
|
- delete window._singleFile_localStorage;
|
|
|
- }
|
|
|
- if (!window._singleFile_indexedDB) {
|
|
|
- delete window.indexedDB;
|
|
|
- window.indexedDB = window._singleFile_indexedDB;
|
|
|
- delete window._singleFile_indexedDB;
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- let warningRequestAnimationFrameDisplayed;
|
|
|
- const pendingRequestAnimationFrameCalls = new Map();
|
|
|
- let lastTimestamp = 0;
|
|
|
- let errorDetected;
|
|
|
- window.requestAnimationFrame = function (callback) {
|
|
|
- if (!warningRequestAnimationFrameDisplayed) {
|
|
|
- warn.call(console, "SingleFile is hooking the requestAnimationFrame and cancelAnimationFrame functions to load deferred images in background tabs."); // eslint-disable-line no-console
|
|
|
- warningRequestAnimationFrameDisplayed = true;
|
|
|
- }
|
|
|
- let requestId;
|
|
|
- if (loadDeferredImages && !errorDetected) {
|
|
|
- try {
|
|
|
- requestId = 0;
|
|
|
- callback(lastTimestamp);
|
|
|
- } catch (error) {
|
|
|
- errorDetected = true;
|
|
|
- requestId = requestAnimationFrame(timestamp => {
|
|
|
- lastTimestamp = timestamp;
|
|
|
- callback(timestamp);
|
|
|
- });
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (!loadDeferredImages) {
|
|
|
- errorDetected = false;
|
|
|
- }
|
|
|
- requestId = requestAnimationFrame(timestamp => {
|
|
|
- pendingRequestAnimationFrameCalls.delete(requestId);
|
|
|
- lastTimestamp = timestamp;
|
|
|
- callback(timestamp);
|
|
|
- });
|
|
|
- if (!loadDeferredImages || pendingRequestAnimationFrameCalls.length < 128) {
|
|
|
- pendingRequestAnimationFrameCalls.set(requestId, callback);
|
|
|
- }
|
|
|
- }
|
|
|
- return requestId;
|
|
|
- };
|
|
|
- window.requestAnimationFrame.toString = function () { return "requestAnimationFrame() { [native code] }"; };
|
|
|
-
|
|
|
- window.cancelAnimationFrame = function (requestId) {
|
|
|
- pendingRequestAnimationFrameCalls.delete(requestId);
|
|
|
- return cancelAnimationFrame(requestId);
|
|
|
- };
|
|
|
- window.cancelAnimationFrame.toString = function () { return "cancelAnimationFrame() { [native code] }"; };
|
|
|
-
|
|
|
- if (window.FontFace) {
|
|
|
- const FontFace = window.FontFace;
|
|
|
- let warningFontFaceDisplayed;
|
|
|
- window.FontFace = function () {
|
|
|
- if (!warningFontFaceDisplayed) {
|
|
|
- warn.call(console, "SingleFile is hooking the FontFace constructor to get font URLs."); // eslint-disable-line no-console
|
|
|
- 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.call(window, new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
|
|
|
- });
|
|
|
- } else {
|
|
|
- dispatchEvent.call(window, new CustomEvent(NEW_FONT_FACE_EVENT, { detail }));
|
|
|
- }
|
|
|
- return new FontFace(...arguments);
|
|
|
- };
|
|
|
- window.FontFace.toString = function () { return "function FontFace() { [native code] }"; };
|
|
|
- }
|
|
|
-
|
|
|
- if (window.IntersectionObserver) {
|
|
|
- const IntersectionObserver = window.IntersectionObserver;
|
|
|
- let warningIntersectionObserverDisplayed;
|
|
|
- window.IntersectionObserver = function () {
|
|
|
- if (!warningIntersectionObserverDisplayed) {
|
|
|
- warn.call(console, "SingleFile is hooking the IntersectionObserver API to detect and load deferred images."); // eslint-disable-line no-console
|
|
|
- warningIntersectionObserverDisplayed = true;
|
|
|
- }
|
|
|
- const intersectionObserver = new IntersectionObserver(...arguments);
|
|
|
- const observeIntersection = IntersectionObserver.prototype.observe || intersectionObserver.observe;
|
|
|
- const unobserveIntersection = IntersectionObserver.prototype.unobserve || intersectionObserver.unobserve;
|
|
|
- const callback = arguments[0];
|
|
|
- const options = arguments[1];
|
|
|
- if (observeIntersection) {
|
|
|
- intersectionObserver.observe = function (targetElement) {
|
|
|
- let targetElements = observedElements.get(intersectionObserver);
|
|
|
- if (!targetElements) {
|
|
|
- targetElements = [];
|
|
|
- observedElements.set(intersectionObserver, targetElements);
|
|
|
- }
|
|
|
- targetElements.push(targetElement);
|
|
|
- return observeIntersection.call(intersectionObserver, targetElement);
|
|
|
- };
|
|
|
- }
|
|
|
- if (unobserveIntersection) {
|
|
|
- intersectionObserver.unobserve = function (targetElement) {
|
|
|
- let targetElements = observedElements.get(intersectionObserver);
|
|
|
- if (targetElements) {
|
|
|
- targetElements = targetElements.filter(element => element != targetElement);
|
|
|
- if (targetElements.length) {
|
|
|
- observedElements.set(intersectionObserver, targetElements);
|
|
|
- } else {
|
|
|
- observedElements.delete(intersectionObserver);
|
|
|
- observers.delete(intersectionObserver);
|
|
|
- }
|
|
|
- }
|
|
|
- return unobserveIntersection.call(intersectionObserver, targetElement);
|
|
|
- };
|
|
|
- }
|
|
|
- observers.set(intersectionObserver, { callback, options });
|
|
|
- return intersectionObserver;
|
|
|
- };
|
|
|
- window.IntersectionObserver.toString = function () { return "function IntersectionObserver() { [native code] }"; };
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
})();
|