|
|
@@ -33,6 +33,7 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
|
|
|
const MutationObserver = window.MutationObserver;
|
|
|
const addEventListener = (type, listener, options) => window.addEventListener(type, listener, options);
|
|
|
const removeEventListener = (type, listener, options) => window.removeEventListener(type, listener, options);
|
|
|
+ const timeouts = new Map();
|
|
|
|
|
|
return {
|
|
|
process: async options => {
|
|
|
@@ -55,7 +56,7 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
|
|
|
function process(options) {
|
|
|
const frames = singlefile.lib.processors.hooks.content.frames;
|
|
|
return new Promise(async resolve => { // eslint-disable-line no-async-promise-executor
|
|
|
- let timeoutId, idleTimeoutId, maxTimeoutId, loadingImages;
|
|
|
+ let loadingImages;
|
|
|
const pendingImages = new Set();
|
|
|
const observer = new MutationObserver(async mutations => {
|
|
|
mutations = mutations.filter(mutation => mutation.type == ATTRIBUTES_MUTATION_TYPE);
|
|
|
@@ -71,20 +72,21 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
|
|
|
});
|
|
|
if (updated.length) {
|
|
|
loadingImages = true;
|
|
|
- maxTimeoutId = await deferForceLazyLoadEnd(timeoutId, idleTimeoutId, maxTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
if (!pendingImages.size) {
|
|
|
- timeoutId = await deferLazyLoadEnd(timeoutId, idleTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
- idleTimeoutId = await setAsyncTimeout(() => {
|
|
|
+ await setAsyncTimeout("idleTimeout", () => {
|
|
|
if (!loadingImages) {
|
|
|
- clearAsyncTimeout(timeoutId);
|
|
|
- lazyLoadEnd(idleTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ clearAsyncTimeout("loadTimeout");
|
|
|
+ clearAsyncTimeout("maxTimeout");
|
|
|
+ lazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
}
|
|
|
- }, options.loadDeferredImagesMaxIdleTime * 1.2);
|
|
|
- maxTimeoutId = await deferForceLazyLoadEnd(timeoutId, idleTimeoutId, maxTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ }, options.loadDeferredImagesMaxIdleTime * 2);
|
|
|
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
observer.observe(document, { subtree: true, childList: true, attributes: true });
|
|
|
if (frames) {
|
|
|
addEventListener(frames.LOAD_IMAGE_EVENT, onImageLoadEvent);
|
|
|
@@ -100,17 +102,19 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
|
|
|
|
|
|
async function onImageLoadEvent(event) {
|
|
|
loadingImages = true;
|
|
|
- maxTimeoutId = await deferForceLazyLoadEnd(timeoutId, idleTimeoutId, maxTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
if (event.detail) {
|
|
|
pendingImages.add(event.detail);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function onImageLoadedEvent(event) {
|
|
|
- maxTimeoutId = await deferForceLazyLoadEnd(timeoutId, idleTimeoutId, maxTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ await deferForceLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
pendingImages.delete(event.detail);
|
|
|
if (!pendingImages.size) {
|
|
|
- timeoutId = await deferLazyLoadEnd(timeoutId, idleTimeoutId, observer, options, cleanupAndResolve);
|
|
|
+ await deferLazyLoadEnd(observer, options, cleanupAndResolve);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -125,50 +129,63 @@ this.singlefile.lib.processors.lazy.content.loader = this.singlefile.lib.process
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- async function deferLazyLoadEnd(timeoutId, idleTimeoutId, observer, options, resolve) {
|
|
|
- await clearAsyncTimeout(timeoutId);
|
|
|
- return setAsyncTimeout(() => lazyLoadEnd(idleTimeoutId, observer, options, resolve), options.loadDeferredImagesMaxIdleTime);
|
|
|
+ async function deferLazyLoadEnd(observer, options, resolve) {
|
|
|
+ await setAsyncTimeout("loadTimeout", () => lazyLoadEnd(observer, options, resolve), options.loadDeferredImagesMaxIdleTime);
|
|
|
}
|
|
|
|
|
|
- function deferForceLazyLoadEnd(timeoutId, idleTimeoutId, maxTimeoutId, observer, options, resolve) {
|
|
|
- clearAsyncTimeout(maxTimeoutId);
|
|
|
- return setAsyncTimeout(() => {
|
|
|
- clearAsyncTimeout(timeoutId);
|
|
|
- lazyLoadEnd(idleTimeoutId, observer, options, resolve);
|
|
|
+ async function deferForceLazyLoadEnd(observer, options, resolve) {
|
|
|
+ await setAsyncTimeout("maxTimeout", async () => {
|
|
|
+ await clearAsyncTimeout("loadTimeout");
|
|
|
+ await lazyLoadEnd(observer, options, resolve);
|
|
|
}, options.loadDeferredImagesMaxIdleTime * 10);
|
|
|
}
|
|
|
|
|
|
- function lazyLoadEnd(idleTimeoutId, observer, options, resolve) {
|
|
|
- clearAsyncTimeout(idleTimeoutId);
|
|
|
+ async function lazyLoadEnd(observer, options, resolve) {
|
|
|
+ await clearAsyncTimeout("idleTimeout");
|
|
|
if (singlefile.lib.processors.hooks.content.frames) {
|
|
|
singlefile.lib.processors.hooks.content.frames.loadDeferredImagesEnd(options);
|
|
|
}
|
|
|
- setAsyncTimeout(resolve, options.loadDeferredImagesMaxIdleTime / 2);
|
|
|
+ await setAsyncTimeout("endTimeout", async () => {
|
|
|
+ await clearAsyncTimeout("maxTimeout");
|
|
|
+ resolve();
|
|
|
+ }, options.loadDeferredImagesMaxIdleTime / 2);
|
|
|
observer.disconnect();
|
|
|
}
|
|
|
|
|
|
- async function setAsyncTimeout(callback, delay) {
|
|
|
+ async function setAsyncTimeout(type, callback, delay) {
|
|
|
if (browser && browser.runtime && browser.runtime.sendMessage) {
|
|
|
- const timeoutId = await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.setTimeout", delay });
|
|
|
- const timeoutCallback = message => {
|
|
|
- if (message.method == "singlefile.lazyTimeout.onTimeout" && message.id == timeoutId) {
|
|
|
- browser.runtime.onMessage.removeListener(timeoutCallback);
|
|
|
- callback();
|
|
|
- return Promise.resolve({});
|
|
|
- }
|
|
|
- };
|
|
|
- browser.runtime.onMessage.addListener(timeoutCallback);
|
|
|
- return timeoutId;
|
|
|
+ if (!timeouts.get(type)) {
|
|
|
+ timeouts.set(type, callback);
|
|
|
+ await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.setTimeout", type, delay });
|
|
|
+ const timeoutCallback = message => {
|
|
|
+ if (message.method == "singlefile.lazyTimeout.onTimeout" && message.type == type) {
|
|
|
+ browser.runtime.onMessage.removeListener(timeoutCallback);
|
|
|
+ callback();
|
|
|
+ return Promise.resolve({});
|
|
|
+ }
|
|
|
+ };
|
|
|
+ timeouts.delete(type, callback);
|
|
|
+ browser.runtime.onMessage.addListener(timeoutCallback);
|
|
|
+ }
|
|
|
} else {
|
|
|
- return window.setTimeout(callback, delay);
|
|
|
+ const timeoutId = timeouts.get(type);
|
|
|
+ if (timeoutId) {
|
|
|
+ window.clearTimeout(timeoutId);
|
|
|
+ }
|
|
|
+ timeouts.set(type, callback);
|
|
|
+ window.setTimeout(callback, delay);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async function clearAsyncTimeout(timeoutId) {
|
|
|
+ async function clearAsyncTimeout(type) {
|
|
|
if (browser && browser.runtime && browser.runtime.sendMessage) {
|
|
|
- await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.clearTimeout", id: timeoutId });
|
|
|
+ await browser.runtime.sendMessage({ method: "singlefile.lazyTimeout.clearTimeout", type });
|
|
|
} else {
|
|
|
- return window.clearTimeout(timeoutId);
|
|
|
+ const previousTimeoutId = timeouts.get(type);
|
|
|
+ timeouts.delete(type);
|
|
|
+ if (previousTimeoutId) {
|
|
|
+ window.clearTimeout(previousTimeoutId);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|