Skip to content

After the await operation, other functions is cancelled due to delay. #636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
F9y4ng opened this issue Mar 29, 2024 · 9 comments
Closed

Comments

@F9y4ng
Copy link

F9y4ng commented Mar 29, 2024

code

// ==UserScript==
// @name           NewScript-pegqasky
// @description    This is your new file, start writing code
// @match          *://*/*
// @grant          GM.getValue
// @run-at         document-start
// ==/UserScript==

(function() {
    class RegisterEvents {
        constructor() {
            this.fns = [];
            this.finalfns = [];
            this.done = false;
            this.__register();
            document.addEventListener("readystatechange", () => this.__checkReadyState());
        }
        __runFns(fns) {
            let run = 0;
            let err = 0;
            for (let fn of fns) {
                try {
                    fn();
                    run++;
                } catch(e) {
                    err++;
                }
            }
            console.log(run, err, document.readyState);
        }
        __register() {
            if (this.done) return;
            if (document.readyState === "loading") {
                document.addEventListener("DOMContentLoaded", () => this.__runFns(this.fns));
            } else {
                window.addEventListener("load", () =>this.__runFns(this.fns));
            }
            this.done = true;
        }
        __checkReadyState() {
            if (!this.done || document.readyState !== "complete") return;
            this.__runFns(this.finalfns);
        }
        addFn(fn) {
            if (typeof fn !== "function") return;
            this.fns.push(fn);
        }
        addFinalFn(fn) {
            if (typeof fn !== "function") return;
            this.finalfns.push(fn);
        }
    }

    const addEvent = new RegisterEvents();
     !(async function() {
        addEvent.addFinalFn(() =>console.log("00000"));
        await GM.getValue("x");
        await GM.getValue("x");
        await GM.getValue("x");

        addEvent.addFn(() =>console.log("11111"));
        await GM.getValue("x");
        await GM.getValue("x");
        await GM.getValue("x");

        addEvent.addFn(() =>console.log("22222"));
        await GM.getValue("x");
        await GM.getValue("x");
        await GM.getValue("x");

        addEvent.addFinalFn(() =>console.log("33333"));
    })();
})()

result

  1. visit www.baidu.com
  2. open console

Normal in other script managers

Normal

Error in userscripts

Error

@ACTCD
Copy link
Collaborator

ACTCD commented Mar 29, 2024

It looks like the relevant events have occurred before the functions are registered.

I'm not sure what you expected and "functions is canceled" means here, but await does not block the page from loading.

If that's about script injection delay that we can't resolve due to Safari limitations, please refer to: #459 #184

@F9y4ng
Copy link
Author

F9y4ng commented Mar 29, 2024

Tampermonkey and Stay2 work fine in Safari, only userscripts not.

I suggest you run the above code on different script managers locally, and then give a solution. Thanks.

@ACTCD
Copy link
Collaborator

ACTCD commented Mar 29, 2024

I've added a few lines of debug logs to make the process clearer:

// ==UserScript==
// @name           NewScript-pegqasky
// @description    This is your new file, start writing code
// @match          *://*/*
// @grant          GM.getValue
// @run-at         document-start
// ==/UserScript==

(function() {
    class RegisterEvents {
        constructor() {
            this.fns = [];
            this.finalfns = [];
            this.done = false;
            this.__register();
            document.addEventListener("readystatechange", () => this.__checkReadyState());
        }
        __runFns(fns) {
            let run = 0;
            let err = 0;
            for (let fn of fns) {
                try {
                    fn();
                    run++;
                } catch(e) {
                    err++;
                }
            }
            console.log(run, err, document.readyState);
        }
        __register() {
            if (this.done) return;
            if (document.readyState === "loading") {
                document.addEventListener("DOMContentLoaded", () => this.__runFns(this.fns));
            } else {
                window.addEventListener("load", () =>this.__runFns(this.fns));
            }
            this.done = true;
        }
        __checkReadyState() {
            if (!this.done || document.readyState !== "complete") return;
            this.__runFns(this.finalfns);
        }
        addFn(fn) {
            if (typeof fn !== "function") return;
            this.fns.push(fn);
        }
        addFinalFn(fn) {
            if (typeof fn !== "function") return;
            this.finalfns.push(fn);
        }
    }

    document.addEventListener("readystatechange", () => console.debug(document.readyState));
    const addEvent = new RegisterEvents();
     !(async function() {
        console.debug("addFn0")
        addEvent.addFinalFn(() =>console.log("00000"));
        await GM.getValue("x");
        await GM.getValue("x");
        await GM.getValue("x");

        console.debug("addFn1")
        addEvent.addFn(() =>console.log("11111"));
        await GM.getValue("x");
        await GM.getValue("x");
        await GM.getValue("x");

        console.debug("addFn2")
        addEvent.addFn(() =>console.log("22222"));
        await GM.getValue("x");
        await GM.getValue("x");
        await GM.getValue("x");

        console.debug("addFn3")
        addEvent.addFinalFn(() =>console.log("33333"));
    })();
})()

On repeated refreshes I can get different results:

[Info] Injecting: NewScript-pegqasky (js/content) (userscripts.js, line 1)
[Debug] addFn0
[Debug] interactive
[Debug] addFn1
[Debug] addFn2
[Debug] addFn3
[Log] 11111
[Log] 22222
[Log] 2 – 0 – "interactive"
[Debug] complete
[Log] 00000
[Log] 33333
[Log] 2 – 0 – "complete"
[Info] Injecting: NewScript-pegqasky (js/content) (userscripts.js, line 1)
[Debug] addFn0
[Debug] complete
[Log] 00000
[Log] 1 – 0 – "complete"
[Log] 0 – 0 – "complete"
[Debug] addFn1
[Debug] addFn2
[Debug] addFn3
[Info] Injecting: NewScript-pegqasky (js/content) (userscripts.js, line 1)
[Debug] addFn0
[Debug] addFn1
[Debug] addFn2
[Debug] complete
[Log] 00000
[Log] 1 – 0 – "complete"
[Log] 11111
[Log] 22222
[Log] 2 – 0 – "complete"
[Debug] addFn3
[Info] Injecting: NewScript-pegqasky (js/content) (userscripts.js, line 1)
[Debug] addFn0
[Debug] addFn1
[Debug] addFn2
[Debug] addFn3

I don't see any issues with either outcome. These are normal result of asynchronous execution.

@F9y4ng
Copy link
Author

F9y4ng commented Mar 29, 2024

The results of code running on different sites in safari and userscripts are also different.

In www.google.com, the correct result 1 will be returned, but in www.baidu.com, the incorrect result 2 will be returned, no matter how many times it is refreshed.

I mean, the delay caused by await operation did not make the function event successfully registered.

Userscripts follow the GM.* API of Greasemonkey, but running the above code on Greasemonkey of FF will still return the correct result 1.

So, I think the userscripts caused a delay in processing await operation, which made the subsequent events not registered successfully. This can be known by logging this.fns.length and this.finalfns.length.

@ACTCD
Copy link
Collaborator

ACTCD commented Mar 29, 2024

I did not test on other websites, but only on the www.baidu.com you mentioned, and I got the above different results.

I don't find anything wrong with printing their length.

console.debug(addEvent.fns.length, addEvent.finalfns.length);

@F9y4ng
Copy link
Author

F9y4ng commented Mar 29, 2024

From the four results you gave, except for the first one, which returned the correct result, everything else is problematic.

The problem is that it is too late to execute the .addFn() method. Some results are even executed after the 'complete' readystate, so when __runFns() is executed, there is no data in this.fns and this.finalfns at all.

The reason for the late execution is the await operation, and everything is normal without any await operations.

I don't know if I described it clearly.😅

@ACTCD
Copy link
Collaborator

ACTCD commented Mar 29, 2024

Async awaits are certainly a factor in creating delays, and because of that there is some delay in user script injection itself. Because we have to use some asynchronous API to get the scripts data.

And because of these asynchronous characteristics, there is no issues with the above various results.
It just doesn't match what you expected.

@F9y4ng
Copy link
Author

F9y4ng commented Mar 29, 2024

Greasemonkey also use asynchronous API, but in most cases, it can complete the registration of this.finalfns before the 'complete' readystate, but userscripts can't, and it will fail every time.

If can't handle this problem in Safari, just give it up.Thanks for your reply!

@F9y4ng F9y4ng closed this as not planned Won't fix, can't repro, duplicate, stale Mar 29, 2024
@ACTCD
Copy link
Collaborator

ACTCD commented Mar 29, 2024

We could certainly optimize our script preparation process to reduce latency a bit, as we mentioned in other issues.

But you should never expect the order of asynchronous execution, even if it appears in some order most of the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants