|
|
@@ -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);
|
|
|
}
|
|
|
}
|
|
|
|