Move the Tabbrowser class to a shared .sys.mjs module
Categories
(Firefox :: Tabbed Browser, task)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox156 | --- | fixed |
People
(Reporter: dao, Assigned: dao)
References
(Blocks 3 open bugs)
Details
Attachments
(9 files)
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review |
Follow-up to bug 1979688.
Today browser/components/tabbrowser/content/tabbrowser.js is loaded into each browser window via Services.scriptloader.loadSubScript(...) (browser/base/content/browser-main.js), defining window.Tabbrowser = class { ... }. Because every window evaluates the script separately, each window gets a distinct Tabbrowser class identity.
This matters for # private fields: their brand checks are per-class-identity, so a field made #-private in one window's class can't be read on another window's gBrowser. Cross-window tab swapping (swapBrowsers, swapBrowsersAndCloseOther) reaches into the other window's tabbrowser, so bug 1979688 had to keep _-prefixed accessor methods (_getTabProgressListener, _getTabProgressFilter, _setTabProgressListener) as a workaround instead of accessing the # fields directly.
Loading Tabbrowser as a single shared .sys.mjs module would give one class identity across all windows, allowing those fields to be truly #-private with direct cross-window access and removing the accessor shims.
Cost / blockers:
- The file closes over per-window globals pervasively (~135
document., ~131gBrowser, ~69window.references); in a shared module those resolve to the system global and would need to become instance-relative (this.ownerGlobal, etc.). - The sibling
TabProgressListenerclass and thenew otherTabBrowser.documentGlobal.TabProgressListener(...)construction pattern capture the window too. window/documentwould need to be passed into the constructor and threaded through, since custom-element registries are per-window.
| Assignee | ||
Comment 1•13 days ago
|
||
Some notes from scoping this out.
History. The move can keep blame. git blame follows a rename as long as git detects it, which it did for the last move of this file (bug 1837575, R100). What breaks it is content churn in the rename commit — specifically unwrapping the { // start private scope block, which dedents all ~10k lines: a rename plus a whole-file dedent is reported as delete+add, and git blame -w can't recover it, because there's no rename link left to follow. So the de-globalizing happens in place first, and the rename commit keeps the wrapper (let Tabbrowser; { … } export { Tabbrowser };). If we want the dedent afterwards, it goes in its own whitespace-only commit added to .git-blame-ignore-revs, which already carries precedent for exactly that (e.g. bug 1128203).
Keeping the wrapper doesn't cost us sphinx-js docs: jsdoc produces the same doclets (kind=class, longname=Tabbrowser, members attached) for export class Tabbrowser {} and for a class expression assigned inside a block, with no @class tag needed.
The non-class contents of the file:
- The constants at the top (
FAVICON_DEFAULTS, thensIWebNavigationload flags,DIRECTION_*,TAB_LABEL_MAX_LENGTH) are window-free and just become module-level. updateUserContextUIIndicator()closes overdocumentandgBrowserand is only called from the class, so it becomes a private method.TabProgressListenerbecomes a module-level class. Bug 2063548 covers making it window-agnostic and replacing thedocumentGlobal.TabProgressListenerconstruction, which is a prerequisite either way.URILoadingWrapperhas no consumers outside this file, and its only window closures —document.hasValidTransientUserGestureActivationandgBrowser.SponsorProtection— are reachable from thebrowserargument it already takes.StatusPanelandTabBarVisibilitysit outside the wrapper as top-levelvars, i.e. window properties, and consumers depend on that (browser.js,browser-customtitlebar.js,browser-init.js,CustomizableUI.sys.mjs,TaskbarTabsChrome.sys.mjs, plus tests usingwin.StatusPanel). A module-level binding would be process-wide, so they become exported classes instantiated per window and assigned to the window inbrowser-main.js. Keeping them in this file also keeps their blame — extracting them to their own files would lose it for those ~210 lines, since copy detection doesn't recover an extracted chunk even withgit blame -C -C -C.Tabbrowser.create(window)already takes the window, so thebrowser-main.jsside is justimportESModuleplusTabbrowser.create(window).
| Assignee | ||
Comment 2•13 days ago
|
||
The listener looked up gBrowser, gURLBar, window and clearTimeout in the realm
that constructed it, which is always the window its tab lives in, so this is a
behavior-neutral rerouting. It frees the class of the realm it is created in,
which a single shared class won't have.
The bare globals that remain hold the same value in every window.
Updated•13 days ago
|
| Assignee | ||
Comment 3•12 days ago
|
||
| Assignee | ||
Comment 4•12 days ago
|
||
| Assignee | ||
Comment 5•12 days ago
|
||
| Assignee | ||
Comment 6•12 days ago
|
||
getTabDialogBox stays free of this: ContentDispatchChooser captures it off
gBrowser and calls it receiverless, which a class method can't survive. And
TabDialogBox is a class declaration, hence a lexical binding, so browser.js has
to hand it to the window before anything outside its own scripts can reach it.
| Assignee | ||
Comment 7•12 days ago
|
||
The names left bare are on the system global, including UserInteraction, whose
Exposed=Window reads as if it weren't. setTimeout, requestAnimationFrame
and performance genuinely aren't, which is why those go through the window.
| Assignee | ||
Comment 8•12 days ago
|
||
| Assignee | ||
Comment 9•12 days ago
|
||
BrowserUtils.callModulesFromCategory imports a module entry itself, so the
window stops loading the file and the two category lines carry the new URI
instead.
The private scope block stays wrapped, which is what keeps the rename detectable
and the file's blame intact; unwrapping it is a whitespace-only follow-up. The
module getters defined on the instance stay on the instance, since
gBrowser.TabMetrics and friends are reached from ~95 call sites outside this
file.
| Assignee | ||
Comment 10•12 days ago
|
||
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Updated•12 days ago
|
Comment 11•10 days ago
|
||
| Assignee | ||
Updated•10 days ago
|
Updated•9 days ago
|
Comment 12•9 days ago
|
||
| Assignee | ||
Updated•9 days ago
|
Comment 13•9 days ago
|
||
Comment 14•9 days ago
|
||
| bugherder | ||
https://hg.mozilla.org/mozilla-central/rev/dbd80c0fb016
https://hg.mozilla.org/mozilla-central/rev/2983616e6d54
https://hg.mozilla.org/mozilla-central/rev/b3f5727c94c5
Comment 15•9 days ago
|
||
| bugherder | ||
https://hg.mozilla.org/mozilla-central/rev/d2d44d2ec558
https://hg.mozilla.org/mozilla-central/rev/d5f6fc32b43d
https://hg.mozilla.org/mozilla-central/rev/b137f59e9809
https://hg.mozilla.org/mozilla-central/rev/b73f8bad3ac2
https://hg.mozilla.org/mozilla-central/rev/bcc02bc32108
https://hg.mozilla.org/mozilla-central/rev/a290a492d7ca
Description
•