Files
sousa-gecko/browser/docs/BrowserStartup.md
T
Dimi ff152f9aeb Bug 2045665 - Port module initialization in _delayedStartup to use category-manager invocation r=Gijs,tabbrowser-reviewers,dao
Move the module init/teardown calls in gBrowserInit._delayedStartup to
category-manager entries, continuing Bug 2036714 and Bug 2039199.

Synchronous inits go in the existing browser-window-delayed-startup
category; consumers sharing a script URL use a `#<id>` suffix to keep the
key unique, and MOZ_DATA_REPORTING / non-MOZILLA_OFFICIAL entries are
guarded by the same preprocessor directives as their scripts.

Post-session-restore work moves to a new
browser-window-sessionstore-initialized category, dispatched from the
same SessionStore.promiseInitialized handler to preserve the window.closed
guard and timing.

Teardown is split: the four unconditional unload teardowns stay in
browser-window-unload, while the six that only run after delayed startup
move to a new browser-window-delayed-startup-unload category.

PanelUI.init now calls shouldSuppressPopupNotifications() directly, and
ctrlTab gains observePref/stopObservingPref to own its pref-observer
lifecycle, so neither depends on browser-init.js wiring anymore.

Differential Revision: https://phabricator.services.mozilla.com/D305183
2026-07-01 08:46:38 +00:00

8.1 KiB

Browser Startup

Invoking your code on browser startup

The first rule of running code during startup is: don't.

We take performance very seriously and ideally your component/feature should initialize only when needed.

If you have established that you really must run code during startup, register a category manager entry in browser/components/BrowserComponents.manifest. See the category manager indirection docs for more details on the mechanism.

Available category manager entries (in order)

Categories come in two types: per-application (fired once at startup) and per-window (fired for each browser window). The following categories are listed in the order they fire.

Per-application categories (invoked once, not per-window)

Category Notes
browser-before-ui-startup Please don't do it unless absolutely necessary. This will run code before the first browser window appears on the screen and make Firefox seem slow.
browser-first-window-ready After the first browser window's browser-window-delayed-startup call.
browser-idle-startup Preferred entrypoint for most startup code. Runs during a user-idle period after startup.
browser-best-effort-idle-startup Like browser-idle-startup, but your code may not run at all if the browser is shut down quickly.

Per-window categories (invoked for each browser window)

Category Notes
browser-window-before-initial-xul-layout-document-preparation Before XUL layout; before the tab bar visibility update.
browser-window-before-initial-xul-layout Before XUL layout; after the tab bar visibility update and document icon are set.
browser-window-domcontentloaded-before-tabbrowser During DOMContentLoaded, before the Tabbrowser is created.
browser-window-domcontentloaded-tabbrowser During DOMContentLoaded, creates the Tabbrowser. Internal category - do not add consumers here.
browser-window-domcontentloaded During DOMContentLoaded, after the Tabbrowser is created. Preferred entry point for DOMContentLoaded initialization.
browser-window-load-before-sessionstore-init During the window load event, before SessionStore initializes the window. For components that must be ready before session restore runs.
browser-window-load During the window load event, after SessionStore has initialized the window.
browser-window-delayed-startup After the window's first paint, for code that can run after the window is visible.
browser-window-sessionstore-initialized Fired from the window's delayed startup once SessionStore is initialized.
browser-window-location-change Fires on every XULBrowserWindow.onLocationChange for the window.
browser-window-unload-begin At the very start of window unload, before checking whether the window finished loading. Runs even if the window never fully loaded.
browser-window-unload During window unload, before Tabbrowser is destroyed. Preferred entry point for window unload cleanup.
browser-window-unload-delayed-startup During window unload, before Tabbrowser is destroyed. Teardown counterpart to browser-window-delayed-startup; only fires if the window's delayed startup actually ran.
browser-window-unload-tabbrowser During window unload, destroys the Tabbrowser. Internal category - do not add consumers here.
browser-window-final-unload During window unload, after Tabbrowser is destroyed.

Other useful points in startup are:

  • BrowserGlue's _onWindowsRestored, and/or the observer service's sessionstore-windows-restored topic, and/or a category manager call that should replace the BrowserGlue list of direct calls. This fires after session restore has completed restoring all windows (but before all pages that may have been restored have necessarily loaded). Note that this is guaranteed to fire even if automatic session restore is not enabled.

How do first run/first startup experiments work?

Why does synchronously reading Nimbus feature values work for customizing display features like about:welcome onboarding and the default browser prompt? The key invariant is that the display decisions wait for sessionstore-windows-restored to show customizable UI, and therefore we just need Nimbus available to read at that point. This is arranged either via the --first-startup flag; or, for subsequent startups, the relevant Nimbus features being marked isEarlyStartup: true. When isEarlyStartup: true, Nimbus caches all its feature variables as Gecko preferences, ready to be read during early startup. (See the early startup docs.)

Customizable display features like about:welcome or the default browser prompt are used in _maybeShowDefaultBrowserPrompt(), which is invoked as part of a startup idle task. Startup idle tasks are scheduled in response to sessionstore-windows-restored.

Now, why is sessionstore-windows-restored late enough for a first startup experiment? The answer is subtle.

During Firefox launch, final-ui-startup is notified, and in response SessionStore is initialized. Additionally, Nimbus/Normandy initialization is started but not awaited.

Then the command line is handled. When --first-startup is passed, we spin the event loop to allow Nimbus/Normandy time to complete its initialization and first fetch before continuing to process the command line. See the FirstStartup module. (Important caveat: --first-startup is only used on Windows; see Bug 1872934, for example.)

This races with SessionStore, which itself waits for the first browser window to be shown -- in particular, the sessionstore-windows-restored notification waits for the first browser window's browser-delayed-startup-finished notification.

This first browser-delayed-startup-finished notification is not guaranteed to be after --first-startup has spun the event loop! But, when launched with only --first-startup and flags considered very early in nsAppRunner.cpp -- as the stub installer does -- then the first window is guaranteed to be after the event loop has been spun, and therefore sessionstore-windows-restored is after as well. (As a counter-example: try firefox.exe --browser --first-startup and witness the --browser flag creating a window before spinning the event loop, inadvertently racing against sessionstore-windows-restored.) Making this deterministic is tracked by Bug 1944431.

Together, this means that first-startup experiments will be loaded in time to impact display features such as about:welcome and the default browser prompt, and we should not have a "split brain" scenario in which the Nimbus feature is intermittently unavailable to the relevant display features.