Skip to content

fix: prevent unbounded recursion on boot - #2230

Merged
AprilSylph merged 2 commits into
masterfrom
aprilsylph/no-unbounded-recursion
Apr 28, 2026
Merged

fix: prevent unbounded recursion on boot#2230
AprilSylph merged 2 commits into
masterfrom
aprilsylph/no-unbounded-recursion

Conversation

@AprilSylph

@AprilSylph AprilSylph commented Apr 24, 2026

Copy link
Copy Markdown
Owner

Description

A theoretical improvement. Caps the maximum number of boot attempts at 3600, which is a very safe one minute on a 60Hz display, decreasing as refresh rate increases.

I think it's very safe to assume that devices able to output 360 frames per second can load React in a lot less than 10 seconds.

I would be very interested in revisiting #1971 after this.

Testing steps

  1. Load the modified addon
  2. Enable some features, ideally ones that take visible effect immediately upon activating
  3. Open a Tumblr tab
    • Expected result: XKit boots normally
  4. Open devtools → Network and enable throttling
  5. Reload the Tumblr tab
    • Expected result: The tab is sloooooow to load
    • Expected result: When the tab finally does load, React loads normally
    • Expected result: XKit boots normally after this
      (Proving that "run_at": "document_end" is working as intended)

Co-authored-by: Copilot <copilot@github.com>
@marcustyphoon

Copy link
Copy Markdown
Collaborator

Ooh, good idea. I think the chances that this is addressing a thing which is in fact happening are low, but good safety.

@marcustyphoon

marcustyphoon commented Apr 25, 2026

Copy link
Copy Markdown
Collaborator

Some alternative (non-recursive) forms (not tested, may have typos):

  const waitForReactLoaded = async () => {
    for (let attempts = 0; attempts < MAX_BOOT_ATTEMPTS; attempts++) {
      if (isReactLoaded()) return;
      await new Promise(window.requestAnimationFrame);
    }
    throw new Error('XKit Rewritten boot failed; React did not load after 10+ seconds.');
  };

  if (redpop) {
    waitForReactLoaded()
      .then(init)
      .catch(console.error);
  }
  const waitForReactLoaded = async () => {
    let attempts = 0;

    while (!isReactLoaded()) {
      if (++attempts <= MAX_BOOT_ATTEMPTS) {
        throw new Error('XKit Rewritten boot failed; React did not load after 10+ seconds.');
      }
      await new Promise(window.requestAnimationFrame);
    }
  };

  if (redpop) {
    waitForReactLoaded()
      .then(init)
      .catch(console.error);
  }

edit: we could also call await waitForReactLoaded() in init if that feels cleaner.

@marcustyphoon

Copy link
Copy Markdown
Collaborator

(How do I feel about this commit coauthor and its legal interaction with the gpl license? I'm officially Not Sure.)

@marcustyphoon marcustyphoon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... I can't get isReactLoaded() to ever return false in practice, so this code pah is untested. I hacked it to return false 90% of the time and it seems to work fine, so... success?

Co-authored-by: marcustyphoon <marcustyphoon@gmail.com>
@AprilSylph

Copy link
Copy Markdown
Owner Author

I hacked it to return false 90% of the time and it seems to work fine, so... success?

Did the same for the latest commit; turns out you can't use window.requestAnimationFrame as a generic callback, it errors with a complaint about being called on an object that doesn't implement Window.

@marcustyphoon

Copy link
Copy Markdown
Collaborator

Interesting. I guess I've always used plain requestAnimationFrame. Alternatively, if that also doesn't work, I've hardcore mandala effected myself.

@marcustyphoon

Copy link
Copy Markdown
Collaborator

(Surely it's not a module vs script thing?)

@AprilSylph

Copy link
Copy Markdown
Owner Author
  • Works: await new Promise((resolve) => window.requestAnimationFrame(resolve));
  • Works: await new Promise(requestAnimationFrame);
  • Breaks: await new Promise(window.requestAnimationFrame);

@AprilSylph
AprilSylph merged commit 48a4108 into master Apr 28, 2026
5 checks passed
@AprilSylph
AprilSylph deleted the aprilsylph/no-unbounded-recursion branch April 28, 2026 08:23
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

Successfully merging this pull request may close these issues.

2 participants