Skip to content

Share one safeSelf() cache between the scriptlets in a frame - #63

Merged
philipp-classen merged 1 commit into
mainfrom
avoid_exponential_call_explosion
Oct 2, 2026
Merged

philipp-classen merged 1 commit into
mainfrom
avoid_exponential_call_explosion

Conversation

@philipp-classen

Copy link
Copy Markdown
Member

Context: Each scriptlet has its own copy of safeSelf(). It saves the functions that it finds when it first runs, and those were often already the wrappers that earlier scriptlets had installed, not the browser's own functions. So each hook called all earlier hooks again. With six hooks on JSON.stringify (as on youtube.com), one JSON.stringify call from the page became 2^6=64 native calls.

The extension gives each scriptlet its own copy of scriptletGlobals, so the first scriptlet in a frame (and world) now keeps the cache on globalThis, and the later scriptlets use it. The property is read-only, and its key has the uBO version, so other versions do not share it. This works for all injection paths (registered scripts and executeScript), without changes in the extension.

refs ghostery/ghostery-extension#3672

@smalluban smalluban left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not something like this?

if (scriptletGlobals["safe-${tagName}"]) { 
  safeSelf.safe = scripletGlobals["safe-${tagName}"];
} else {
  scriptletGlobals["safe-${tagName}"] = safeSelf();
}

@smalluban

smalluban commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

The args are serialized and copied, so we cannot use scripletGlobals - it is meant to keep some configuration options.

Context: Each scriptlet has its own copy of safeSelf(). It saves the
functions that it finds when it first runs, and those were often already
the wrappers that earlier scriptlets had installed, not the browser's own
functions. So each hook called all earlier hooks again. With six hooks on
JSON.stringify (as on youtube.com), one JSON.stringify call from the page
became 2^6=64 native calls.

The extension gives each scriptlet its own copy of scriptletGlobals, so
the first scriptlet in a frame (and world) now keeps the cache on
globalThis, and the later scriptlets use it. The property is read-only,
and its key has the uBO version, so other versions do not share it. This
works for all injection paths (registered scripts and executeScript),
without changes in the extension.

refs ghostery/ghostery-extension#3672
@philipp-classen
philipp-classen force-pushed the avoid_exponential_call_explosion branch from ba330c4 to 1e2de50 Compare October 2, 2026 11:30
@philipp-classen
philipp-classen merged commit 58bae9d into main Oct 2, 2026
1 check passed
@philipp-classen
philipp-classen deleted the avoid_exponential_call_explosion branch October 2, 2026 12:28
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