Progress listeners
gBrowser re-broadcasts web progress — loads starting and finishing, locations
changing, security state changing — to listeners registered on it. Little of the
behaviour is guessable from the signatures, so this page describes what each
listener type receives, what the tabbrowser does with raw web progress before
it reaches you, and what a tab switch replays.
The two listener types
Global listeners, registered with gBrowser.addProgressListener(), receive
notifications for the selected browser only. Their callbacks take the same
arguments as the corresponding nsIWebProgressListener
methods. Because a global listener follows the selected browser, switching tabs
has to bring it up to date: gBrowser re-sends the new browser’s recorded state,
the replay covered in What a tab switch replays.
Tabs listeners, registered with gBrowser.addTabsProgressListener(), receive
notifications for every tab, and their callbacks take the browser the
notification belongs to as an extra leading argument:
let listener = {
onLocationChange(browser, webProgress, request, location, flags) {
if (browser == myTab.linkedBrowser) {
// ...
}
},
};
gBrowser.addTabsProgressListener(listener);
// later
gBrowser.removeTabsProgressListener(listener);
What is broadcast
Nine notifications reach listeners. Seven mirror
nsIWebProgressListener; the last two exist only on
gBrowser.
Notification |
Received by |
Notes |
|---|---|---|
|
tabs and global listeners |
Dispatched separately to each type, and substituted for the initial |
|
tabs and global listeners |
Also replayed to global listeners on a tab switch. |
|
tabs and global listeners |
|
|
tabs and global listeners |
The status text is also recorded for replay. |
|
tabs and global listeners |
Also replayed to global listeners on a tab switch. |
|
tabs and global listeners |
Carries a trailing flag that is true when the event is simulated rather than observed. |
|
tabs and global listeners |
The only one whose return value is acted on. |
|
tabs and global listeners |
|
|
global listeners only |
|
Both listener types share the same dispatch rules:
A listener is a plain object, not an XPCOM interface implementation.
Dispatches only when
if (method in listener)is true, so implementing only the notifications you care about is normal and expected.Each call is wrapped in try/catch. A listener that throws has its error logged and does not inhibit the remaining listeners.
Global listeners run before tabs listeners.
The return value matters. If any listener returns a falsy value, the aggregate result is falsy.
onRefreshAttempted’s result decides whether a meta refresh proceeds, so a listener implementing it must returntrueunless it means to cancel the refresh.
What sits between the browser and your listener
flowchart TD
webProgress([browser webProgress])
filter[status filter<br/>coalesces the stream]
listener[TabProgressListener<br/>updates tab state]
dispatch[gBrowser]
globals[global listeners]
tabs[tabs listeners]
switched([tab switch])
replay[updateCurrentBrowser<br/>replays the recorded state]
webProgress --> filter --> listener --> dispatch
dispatch -- selected browser only --> globals
dispatch -- browser prepended --> tabs
switched --> replay --> globals
How a notification reaches a listener
Each tab gets its own TabProgressListener, wired to the browser through a
status filter that coalesces the raw notification stream, which is why
fine-grained progress rates never reach a listener.
TabProgressListener translates web progress into tab state: it maintains the
tab’s busy and progress attributes, the tabbrowser’s own busy state, the tab’s
title and icon at the right moments, and only then calls the registered listeners. It also
records the tab’s last stateFlags, status, message and totalProgress, which
is what makes the replay below possible. Two things follow from that:
The record is written after the listeners run, so a listener that reads it while handling a notification sees the previous value.
messageandtotalProgressare cleared atSTATE_STARTandSTATE_STOP, so a status delivered before a load’sSTATE_STARTis shown at the time but is not replayed on a later tab switch.
Each notification is then dispatched twice, once to the global listeners and once
to the tabs listeners. Usually both types receive the same call. The exception is a tab
still on its initial about:blank document: the global listeners receive
onUpdateCurrentBrowser(stateFlags, status, "", 0) in place of onStateChange, so
they don’t mistake a brand-new tab’s blank document for a load, while the tabs
listeners still receive the real onStateChange and have to draw that distinction
themselves.
What a tab switch replays
A listener registered against gBrowser hears about the selected browser, so
selecting another tab has to bring it up to date. updateCurrentBrowser()
replays, to global listeners only — a tabs listener sees nothing here:
onLocationChangefor the new browser’s current URI, with the simulated flag set.onSecurityChangeandonContentBlockingEvent, if the browser has asecurityUI.onUpdateCurrentBrowserwith the four recorded values, but only when the recordedstateFlagsis non-zero.A synthetic
onStateChangecarryingSTATE_START | STATE_IS_NETWORKwhen the new tab is busy and the tabbrowser was not, orSTATE_STOP | STATE_IS_NETWORKin the mirror case.
onUpdateCurrentBrowser has no nsIWebProgressListener counterpart. gBrowser
sends it to global listeners whenever it wants to hand them a state snapshot rather
than report a change it just observed — on a tab switch here, and in place of
onStateChange for the initial about:blank above. On a tab switch it carries the
recorded flags verbatim, so a consumer gated on STATE_IS_NETWORK reacts to the
separate synthetic onStateChange in step 4 instead.
Waiting for progress in a test
For the common cases, use the helpers rather than a listener of your own:
BrowserTestUtils.browserLoaded(browser) for a load in a specific tab, and
BrowserTestUtils.waitForLocationChange(gBrowser, url) for a location change
anywhere in the window.
waitForLocationChange is itself a compact example of the tabs-listener pattern —
a plain object implementing one notification, removed again once it fires:
function waitForLocationChange(tabbrowser, url) {
return new Promise(resolve => {
let listener = {
onLocationChange(browser, webProgress, request, newURI) {
if (newURI.spec != url) {
return;
}
tabbrowser.removeTabsProgressListener(listener);
resolve();
},
};
tabbrowser.addTabsProgressListener(listener);
});
}