Bug 1441816 - Move mTabProgressListener into its own class. r=jaws draft
authorDão Gottwald <dao@mozilla.com>
Thu, 01 Mar 2018 07:56:39 +0100
changeset 761562 52b68f1c632247f99da772190d36da2fbc54aa90
parent 761561 b996cabc7ef54bbe050d647494bf00d668ec52e6
push id100974
push userdgottwald@mozilla.com
push dateThu, 01 Mar 2018 06:57:19 +0000
reviewersjaws
bugs1441816
milestone60.0a1
Bug 1441816 - Move mTabProgressListener into its own class. r=jaws MozReview-Commit-ID: Fj5MtThJvld
browser/base/content/tabbrowser.js
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -196,17 +196,17 @@ class TabBrowser {
     // set up the shared autoscroll popup
     this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
     this._autoScrollPopup.id = "autoscroller";
     this.appendChild(this._autoScrollPopup);
     this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
     this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
 
     // Hook up the event listeners to the first browser
-    var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false);
+    var tabListener = new TabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false);
     const nsIWebProgress = Ci.nsIWebProgress;
     const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
       .createInstance(nsIWebProgress);
     filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
     this._tabListeners.set(this.mCurrentTab, tabListener);
     this._tabFilters.set(this.mCurrentTab, filter);
     this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
 
@@ -759,447 +759,16 @@ class TabBrowser {
       ).URI;
       return resolvedURI.schemeIs("jar") || resolvedURI.schemeIs("file");
     } catch (ex) {
       // aURI might be invalid.
       return false;
     }
   }
 
-  /**
-   * A web progress listener object definition for a given tab.
-   */
-  mTabProgressListener(aTab, aBrowser, aStartsBlank, aWasPreloadedBrowser, aOrigStateFlags) {
-    let stateFlags = aOrigStateFlags || 0;
-    // Initialize mStateFlags to non-zero e.g. when creating a progress
-    // listener for preloaded browsers as there was no progress listener
-    // around when the content started loading. If the content didn't
-    // quite finish loading yet, mStateFlags will very soon be overridden
-    // with the correct value and end up at STATE_STOP again.
-    if (aWasPreloadedBrowser) {
-      stateFlags = Ci.nsIWebProgressListener.STATE_STOP |
-        Ci.nsIWebProgressListener.STATE_IS_REQUEST;
-    }
-
-    return ({
-      mTabBrowser: this,
-      mTab: aTab,
-      mBrowser: aBrowser,
-      mBlank: aStartsBlank,
-
-      // cache flags for correct status UI update after tab switching
-      mStateFlags: stateFlags,
-      mStatus: 0,
-      mMessage: "",
-      mTotalProgress: 0,
-
-      // count of open requests (should always be 0 or 1)
-      mRequestCount: 0,
-
-      destroy() {
-        delete this.mTab;
-        delete this.mBrowser;
-        delete this.mTabBrowser;
-      },
-
-      _callProgressListeners() {
-        Array.unshift(arguments, this.mBrowser);
-        return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
-      },
-
-      _shouldShowProgress(aRequest) {
-        if (this.mBlank)
-          return false;
-
-        // Don't show progress indicators in tabs for about: URIs
-        // pointing to local resources.
-        if ((aRequest instanceof Ci.nsIChannel) &&
-            this.mTabBrowser._isLocalAboutURI(aRequest.originalURI, aRequest.URI)) {
-          return false;
-        }
-
-        return true;
-      },
-
-      _isForInitialAboutBlank(aWebProgress, aStateFlags, aLocation) {
-        if (!this.mBlank || !aWebProgress.isTopLevel) {
-          return false;
-        }
-
-        // If the state has STATE_STOP, and no requests were in flight, then this
-        // must be the initial "stop" for the initial about:blank document.
-        const nsIWebProgressListener = Ci.nsIWebProgressListener;
-        if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
-            this.mRequestCount == 0 &&
-            !aLocation) {
-          return true;
-        }
-
-        let location = aLocation ? aLocation.spec : "";
-        return location == "about:blank";
-      },
-
-      onProgressChange(aWebProgress, aRequest,
-        aCurSelfProgress, aMaxSelfProgress,
-        aCurTotalProgress, aMaxTotalProgress) {
-        this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
-
-        if (!this._shouldShowProgress(aRequest))
-          return;
-
-        if (this.mTotalProgress && this.mTab.hasAttribute("busy"))
-          this.mTab.setAttribute("progress", "true");
-
-        this._callProgressListeners("onProgressChange",
-                                    [aWebProgress, aRequest,
-                                     aCurSelfProgress, aMaxSelfProgress,
-                                     aCurTotalProgress, aMaxTotalProgress]);
-      },
-
-      onProgressChange64(aWebProgress, aRequest,
-        aCurSelfProgress, aMaxSelfProgress,
-        aCurTotalProgress, aMaxTotalProgress) {
-        return this.onProgressChange(aWebProgress, aRequest,
-          aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
-          aMaxTotalProgress);
-      },
-
-      /* eslint-disable complexity */
-      onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
-        if (!aRequest)
-          return;
-
-        const nsIWebProgressListener = Ci.nsIWebProgressListener;
-        const nsIChannel = Ci.nsIChannel;
-        let location, originalLocation;
-        try {
-          aRequest.QueryInterface(nsIChannel);
-          location = aRequest.URI;
-          originalLocation = aRequest.originalURI;
-        } catch (ex) {}
-
-        let ignoreBlank = this._isForInitialAboutBlank(aWebProgress, aStateFlags,
-          location);
-
-        // If we were ignoring some messages about the initial about:blank, and we
-        // got the STATE_STOP for it, we'll want to pay attention to those messages
-        // from here forward. Similarly, if we conclude that this state change
-        // is one that we shouldn't be ignoring, then stop ignoring.
-        if ((ignoreBlank &&
-            aStateFlags & nsIWebProgressListener.STATE_STOP &&
-            aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) ||
-            !ignoreBlank && this.mBlank) {
-          this.mBlank = false;
-        }
-
-        if (aStateFlags & nsIWebProgressListener.STATE_START) {
-          this.mRequestCount++;
-        } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
-          const NS_ERROR_UNKNOWN_HOST = 2152398878;
-          if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
-            // to prevent bug 235825: wait for the request handled
-            // by the automatic keyword resolver
-            return;
-          }
-          // since we (try to) only handle STATE_STOP of the last request,
-          // the count of open requests should now be 0
-          this.mRequestCount = 0;
-        }
-
-        if (aStateFlags & nsIWebProgressListener.STATE_START &&
-            aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
-          if (aWebProgress.isTopLevel) {
-            // Need to use originalLocation rather than location because things
-            // like about:home and about:privatebrowsing arrive with nsIRequest
-            // pointing to their resolved jar: or file: URIs.
-            if (!(originalLocation && gInitialPages.includes(originalLocation.spec) &&
-                originalLocation != "about:blank" &&
-                this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec &&
-                this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
-              // Indicating that we started a load will allow the location
-              // bar to be cleared when the load finishes.
-              // In order to not overwrite user-typed content, we avoid it
-              // (see if condition above) in a very specific case:
-              // If the load is of an 'initial' page (e.g. about:privatebrowsing,
-              // about:newtab, etc.), was not explicitly typed in the location
-              // bar by the user, is not about:blank (because about:blank can be
-              // loaded by websites under their principal), and the current
-              // page in the browser is about:blank (indicating it is a newly
-              // created or re-created browser, e.g. because it just switched
-              // remoteness or is a new tab/window).
-              this.mBrowser.urlbarChangeTracker.startedLoad();
-            }
-            delete this.mBrowser.initialPageLoadedFromURLBar;
-            // If the browser is loading it must not be crashed anymore
-            this.mTab.removeAttribute("crashed");
-          }
-
-          if (this._shouldShowProgress(aRequest)) {
-            if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING) &&
-                aWebProgress && aWebProgress.isTopLevel) {
-              this.mTab.setAttribute("busy", "true");
-              this.mTab._notselectedsinceload = !this.mTab.selected;
-              SchedulePressure.startMonitoring(window, {
-                highPressureFn() {
-                  // Only switch back to the SVG loading indicator after getting
-                  // three consecutive low pressure callbacks. Used to prevent
-                  // switching quickly between the SVG and APNG loading indicators.
-                  gBrowser.tabContainer._schedulePressureCount = gBrowser.schedulePressureDefaultCount;
-                  gBrowser.tabContainer.setAttribute("schedulepressure", "true");
-                },
-                lowPressureFn() {
-                  if (!gBrowser.tabContainer._schedulePressureCount ||
-                    --gBrowser.tabContainer._schedulePressureCount <= 0) {
-                    gBrowser.tabContainer.removeAttribute("schedulepressure");
-                  }
-
-                  // If tabs are closed while they are loading we need to
-                  // stop monitoring schedule pressure. We don't stop monitoring
-                  // during high pressure times because we want to eventually
-                  // return to the SVG tab loading animations.
-                  let continueMonitoring = true;
-                  if (!document.querySelector(".tabbrowser-tab[busy]")) {
-                    SchedulePressure.stopMonitoring(window);
-                    continueMonitoring = false;
-                  }
-                  return { continueMonitoring };
-                },
-              });
-              this.mTabBrowser.syncThrobberAnimations(this.mTab);
-            }
-
-            if (this.mTab.selected) {
-              this.mTabBrowser.mIsBusy = true;
-            }
-          }
-        } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
-                   aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
-
-          if (this.mTab.hasAttribute("busy")) {
-            this.mTab.removeAttribute("busy");
-            if (!document.querySelector(".tabbrowser-tab[busy]")) {
-              SchedulePressure.stopMonitoring(window);
-              this.mTabBrowser.tabContainer.removeAttribute("schedulepressure");
-            }
-
-            // Only animate the "burst" indicating the page has loaded if
-            // the top-level page is the one that finished loading.
-            if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument &&
-                Components.isSuccessCode(aStatus) &&
-                !this.mTabBrowser.tabAnimationsInProgress &&
-                Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
-              if (this.mTab._notselectedsinceload) {
-                this.mTab.setAttribute("notselectedsinceload", "true");
-              } else {
-                this.mTab.removeAttribute("notselectedsinceload");
-              }
-
-              this.mTab.setAttribute("bursting", "true");
-            }
-
-            this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
-            if (!this.mTab.selected)
-              this.mTab.setAttribute("unread", "true");
-          }
-          this.mTab.removeAttribute("progress");
-
-          if (aWebProgress.isTopLevel) {
-            let isSuccessful = Components.isSuccessCode(aStatus);
-            if (!isSuccessful && !isTabEmpty(this.mTab)) {
-              // Restore the current document's location in case the
-              // request was stopped (possibly from a content script)
-              // before the location changed.
-
-              this.mBrowser.userTypedValue = null;
-
-              let inLoadURI = this.mBrowser.inLoadURI;
-              if (this.mTab.selected && gURLBar && !inLoadURI) {
-                URLBarSetURI();
-              }
-            } else if (isSuccessful) {
-              this.mBrowser.urlbarChangeTracker.finishedLoad();
-            }
-
-            // Ignore initial about:blank to prevent flickering.
-            if (!this.mBrowser.mIconURL && !ignoreBlank) {
-              // Don't switch to the default icon on about:home or about:newtab,
-              // since these pages get their favicon set in browser code to
-              // improve perceived performance.
-              let isNewTab = originalLocation &&
-                (originalLocation.spec == "about:newtab" ||
-                  originalLocation.spec == "about:privatebrowsing" ||
-                  originalLocation.spec == "about:home");
-              if (!isNewTab) {
-                this.mTabBrowser.useDefaultIcon(this.mTab);
-              }
-            }
-          }
-
-          // For keyword URIs clear the user typed value since they will be changed into real URIs
-          if (location.scheme == "keyword")
-            this.mBrowser.userTypedValue = null;
-
-          if (this.mTab.selected)
-            this.mTabBrowser.mIsBusy = false;
-        }
-
-        if (ignoreBlank) {
-          this._callProgressListeners("onUpdateCurrentBrowser",
-                                      [aStateFlags, aStatus, "", 0],
-                                      true, false);
-        } else {
-          this._callProgressListeners("onStateChange",
-                                      [aWebProgress, aRequest, aStateFlags, aStatus],
-                                      true, false);
-        }
-
-        this._callProgressListeners("onStateChange",
-                                    [aWebProgress, aRequest, aStateFlags, aStatus],
-                                    false);
-
-        if (aStateFlags & (nsIWebProgressListener.STATE_START |
-            nsIWebProgressListener.STATE_STOP)) {
-          // reset cached temporary values at beginning and end
-          this.mMessage = "";
-          this.mTotalProgress = 0;
-        }
-        this.mStateFlags = aStateFlags;
-        this.mStatus = aStatus;
-      },
-      /* eslint-enable complexity */
-
-      onLocationChange(aWebProgress, aRequest, aLocation,
-        aFlags) {
-        // OnLocationChange is called for both the top-level content
-        // and the subframes.
-        let topLevel = aWebProgress.isTopLevel;
-
-        if (topLevel) {
-          let isSameDocument = !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
-          // We need to clear the typed value
-          // if the document failed to load, to make sure the urlbar reflects the
-          // failed URI (particularly for SSL errors). However, don't clear the value
-          // if the error page's URI is about:blank, because that causes complete
-          // loss of urlbar contents for invalid URI errors (see bug 867957).
-          // Another reason to clear the userTypedValue is if this was an anchor
-          // navigation initiated by the user.
-          if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
-              ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
-                aLocation.spec != "about:blank") ||
-              (isSameDocument && this.mBrowser.inLoadURI)) {
-            this.mBrowser.userTypedValue = null;
-          }
-
-          // If the tab has been set to "busy" outside the stateChange
-          // handler below (e.g. by sessionStore.navigateAndRestore), and
-          // the load results in an error page, it's possible that there
-          // isn't any (STATE_IS_NETWORK & STATE_STOP) state to cause busy
-          // attribute being removed. In this case we should remove the
-          // attribute here.
-          if ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
-              this.mTab.hasAttribute("busy")) {
-            this.mTab.removeAttribute("busy");
-            this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
-          }
-
-          // If the browser was playing audio, we should remove the playing state.
-          if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
-            clearTimeout(this.mTab._soundPlayingAttrRemovalTimer);
-            this.mTab._soundPlayingAttrRemovalTimer = 0;
-            this.mTab.removeAttribute("soundplaying");
-            this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
-          }
-
-          // If the browser was previously muted, we should restore the muted state.
-          if (this.mTab.hasAttribute("muted")) {
-            this.mTab.linkedBrowser.mute();
-          }
-
-          if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
-            let findBar = this.mTabBrowser.getFindBar(this.mTab);
-
-            // Close the Find toolbar if we're in old-style TAF mode
-            if (findBar.findMode != findBar.FIND_NORMAL) {
-              findBar.close();
-            }
-          }
-
-          this.mTabBrowser.setTabTitle(this.mTab);
-
-          // Don't clear the favicon if this tab is in the pending
-          // state, as SessionStore will have set the icon for us even
-          // though we're pointed at an about:blank. Also don't clear it
-          // if onLocationChange was triggered by a pushState or a
-          // replaceState (bug 550565) or a hash change (bug 408415).
-          if (!this.mTab.hasAttribute("pending") &&
-              aWebProgress.isLoadingDocument &&
-              !isSameDocument) {
-            this.mBrowser.mIconURL = null;
-          }
-
-          let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
-          if (this.mBrowser.registeredOpenURI) {
-            this.mTabBrowser._unifiedComplete
-              .unregisterOpenPage(this.mBrowser.registeredOpenURI,
-                userContextId);
-            delete this.mBrowser.registeredOpenURI;
-          }
-          // Tabs in private windows aren't registered as "Open" so
-          // that they don't appear as switch-to-tab candidates.
-          if (!isBlankPageURL(aLocation.spec) &&
-              (!PrivateBrowsingUtils.isWindowPrivate(window) ||
-                PrivateBrowsingUtils.permanentPrivateBrowsing)) {
-            this.mTabBrowser._unifiedComplete
-              .registerOpenPage(aLocation, userContextId);
-            this.mBrowser.registeredOpenURI = aLocation;
-          }
-        }
-
-        if (!this.mBlank) {
-          this._callProgressListeners("onLocationChange",
-                                      [aWebProgress, aRequest, aLocation, aFlags]);
-        }
-
-        if (topLevel) {
-          this.mBrowser.lastURI = aLocation;
-          this.mBrowser.lastLocationChange = Date.now();
-        }
-      },
-
-      onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
-        if (this.mBlank)
-          return;
-
-        this._callProgressListeners("onStatusChange",
-                                    [aWebProgress, aRequest, aStatus, aMessage]);
-
-        this.mMessage = aMessage;
-      },
-
-      onSecurityChange(aWebProgress, aRequest, aState) {
-        this._callProgressListeners("onSecurityChange",
-                                    [aWebProgress, aRequest, aState]);
-      },
-
-      onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
-        return this._callProgressListeners("onRefreshAttempted",
-                                           [aWebProgress, aURI, aDelay, aSameURI]);
-      },
-
-      QueryInterface(aIID) {
-        if (aIID.equals(Ci.nsIWebProgressListener) ||
-          aIID.equals(Ci.nsIWebProgressListener2) ||
-          aIID.equals(Ci.nsISupportsWeakReference) ||
-          aIID.equals(Ci.nsISupports))
-          return this;
-        throw Cr.NS_NOINTERFACE;
-      }
-    });
-  }
-
   storeIcon(aBrowser, aURI, aLoadingPrincipal, aRequestContextID) {
     try {
       if (!(aURI instanceof Ci.nsIURI)) {
         aURI = makeURI(aURI);
       }
       PlacesUIUtils.loadFavicon(aBrowser, aLoadingPrincipal, aURI, aRequestContextID);
     } catch (ex) {
       Cu.reportError(ex);
@@ -2110,17 +1679,17 @@ class TabBrowser {
     // As frameLoaders start out with an active docShell we have to
     // deactivate it if this is not the selected tab's browser or the
     // browser window is minimized.
     aBrowser.docShellIsActive = this.shouldActivateDocShell(aBrowser);
 
     // Create a new tab progress listener for the new browser we just injected,
     // since tab progress listeners have logic for handling the initial about:blank
     // load
-    listener = this.mTabProgressListener(tab, aBrowser, true, false);
+    listener = new TabProgressListener(tab, aBrowser, true, false);
     this._tabListeners.set(tab, listener);
     filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
 
     // Restore the progress listener.
     aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
     // Restore the securityUI state.
     let securityUI = aBrowser.securityUI;
@@ -2511,17 +2080,17 @@ class TabBrowser {
       // browser element, which fires off a bunch of notifications. Some
       // of those notifications can cause code to run that inspects our
       // state, so it is important that the tab element is fully
       // initialized by this point.
       this.mPanelContainer.appendChild(notificationbox);
     }
 
     // wire up a progress listener for the new browser object.
-    let tabListener = this.mTabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent);
+    let tabListener = new TabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent);
     const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
       .createInstance(Ci.nsIWebProgress);
     filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
     browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
     this._tabListeners.set(aTab, tabListener);
     this._tabFilters.set(aTab, filter);
 
     browser.droppedLinkHandler = handleDroppedLink;
@@ -3596,17 +3165,17 @@ class TabBrowser {
     let tabListener = otherTabBrowser._tabListeners.get(aOtherTab);
     otherBrowser.webProgress.removeProgressListener(filter);
     filter.removeProgressListener(tabListener);
 
     // Perform the docshell swap through the common mechanism.
     this._swapBrowserDocShells(aOurTab, otherBrowser, aFlags);
 
     // Restore the listeners for the swapped in tab.
-    tabListener = otherTabBrowser.mTabProgressListener(aOtherTab, otherBrowser, false, false);
+    tabListener = new otherTabBrowser.ownerGlobal.TabProgressListener(aOtherTab, otherBrowser, false, false);
     otherTabBrowser._tabListeners.set(aOtherTab, tabListener);
 
     const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
     filter.addProgressListener(tabListener, notifyAll);
     otherBrowser.webProgress.addProgressListener(filter, notifyAll);
   }
 
   _swapBrowserDocShells(aOurTab, aOtherBrowser, aFlags, aStateFlags) {
@@ -3662,18 +3231,17 @@ class TabBrowser {
         let otherTab = remoteBrowser.getTabForBrowser(aOtherBrowser);
         if (otherTab) {
           otherTab.permanentKey = aOtherBrowser.permanentKey;
         }
       }
     }
 
     // Restore the progress listener
-    tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false,
-      aStateFlags);
+    tabListener = new TabProgressListener(aOurTab, ourBrowser, false, false, aStateFlags);
     this._tabListeners.set(aOurTab, tabListener);
 
     const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
     filter.addProgressListener(tabListener, notifyAll);
     ourBrowser.webProgress.addProgressListener(filter, notifyAll);
   }
 
   _swapRegisteredOpenURIs(aOurBrowser, aOtherBrowser) {
@@ -5730,8 +5298,433 @@ class TabBrowser {
         this._tabAttrModified(tab, ["activemedia-blocked"]);
         let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
         hist.add(2 /* unblockByVisitingTab */ );
         tab.finishMediaBlockTimer();
       }
     });
   }
 }
+
+/**
+ * A web progress listener object definition for a given tab.
+ */
+class TabProgressListener {
+  constructor(aTab, aBrowser, aStartsBlank, aWasPreloadedBrowser, aOrigStateFlags) {
+    let stateFlags = aOrigStateFlags || 0;
+    // Initialize mStateFlags to non-zero e.g. when creating a progress
+    // listener for preloaded browsers as there was no progress listener
+    // around when the content started loading. If the content didn't
+    // quite finish loading yet, mStateFlags will very soon be overridden
+    // with the correct value and end up at STATE_STOP again.
+    if (aWasPreloadedBrowser) {
+      stateFlags = Ci.nsIWebProgressListener.STATE_STOP |
+        Ci.nsIWebProgressListener.STATE_IS_REQUEST;
+    }
+
+    this.mTab = aTab;
+    this.mBrowser = aBrowser;
+    this.mBlank = aStartsBlank;
+
+    // cache flags for correct status UI update after tab switching
+    this.mStateFlags = stateFlags;
+    this.mStatus = 0;
+    this.mMessage = "";
+    this.mTotalProgress = 0;
+
+    // count of open requests (should always be 0 or 1)
+    this.mRequestCount = 0;
+  }
+
+  destroy() {
+    delete this.mTab;
+    delete this.mBrowser;
+  }
+
+  _callProgressListeners() {
+    Array.unshift(arguments, this.mBrowser);
+    return gBrowser._callProgressListeners.apply(gBrowser, arguments);
+  }
+
+  _shouldShowProgress(aRequest) {
+    if (this.mBlank)
+      return false;
+
+    // Don't show progress indicators in tabs for about: URIs
+    // pointing to local resources.
+    if ((aRequest instanceof Ci.nsIChannel) &&
+        gBrowser._isLocalAboutURI(aRequest.originalURI, aRequest.URI)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  _isForInitialAboutBlank(aWebProgress, aStateFlags, aLocation) {
+    if (!this.mBlank || !aWebProgress.isTopLevel) {
+      return false;
+    }
+
+    // If the state has STATE_STOP, and no requests were in flight, then this
+    // must be the initial "stop" for the initial about:blank document.
+    const nsIWebProgressListener = Ci.nsIWebProgressListener;
+    if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
+        this.mRequestCount == 0 &&
+        !aLocation) {
+      return true;
+    }
+
+    let location = aLocation ? aLocation.spec : "";
+    return location == "about:blank";
+  }
+
+  onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress,
+                   aCurTotalProgress, aMaxTotalProgress) {
+    this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
+
+    if (!this._shouldShowProgress(aRequest))
+      return;
+
+    if (this.mTotalProgress && this.mTab.hasAttribute("busy"))
+      this.mTab.setAttribute("progress", "true");
+
+    this._callProgressListeners("onProgressChange",
+                                [aWebProgress, aRequest,
+                                 aCurSelfProgress, aMaxSelfProgress,
+                                 aCurTotalProgress, aMaxTotalProgress]);
+  }
+
+  onProgressChange64(aWebProgress, aRequest, aCurSelfProgress,
+                     aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
+    return this.onProgressChange(aWebProgress, aRequest,
+      aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+      aMaxTotalProgress);
+  }
+
+  /* eslint-disable complexity */
+  onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+    if (!aRequest)
+      return;
+
+    const nsIWebProgressListener = Ci.nsIWebProgressListener;
+    const nsIChannel = Ci.nsIChannel;
+    let location, originalLocation;
+    try {
+      aRequest.QueryInterface(nsIChannel);
+      location = aRequest.URI;
+      originalLocation = aRequest.originalURI;
+    } catch (ex) {}
+
+    let ignoreBlank = this._isForInitialAboutBlank(aWebProgress, aStateFlags,
+      location);
+
+    // If we were ignoring some messages about the initial about:blank, and we
+    // got the STATE_STOP for it, we'll want to pay attention to those messages
+    // from here forward. Similarly, if we conclude that this state change
+    // is one that we shouldn't be ignoring, then stop ignoring.
+    if ((ignoreBlank &&
+        aStateFlags & nsIWebProgressListener.STATE_STOP &&
+        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) ||
+        !ignoreBlank && this.mBlank) {
+      this.mBlank = false;
+    }
+
+    if (aStateFlags & nsIWebProgressListener.STATE_START) {
+      this.mRequestCount++;
+    } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+      const NS_ERROR_UNKNOWN_HOST = 2152398878;
+      if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+        // to prevent bug 235825: wait for the request handled
+        // by the automatic keyword resolver
+        return;
+      }
+      // since we (try to) only handle STATE_STOP of the last request,
+      // the count of open requests should now be 0
+      this.mRequestCount = 0;
+    }
+
+    if (aStateFlags & nsIWebProgressListener.STATE_START &&
+        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+      if (aWebProgress.isTopLevel) {
+        // Need to use originalLocation rather than location because things
+        // like about:home and about:privatebrowsing arrive with nsIRequest
+        // pointing to their resolved jar: or file: URIs.
+        if (!(originalLocation && gInitialPages.includes(originalLocation.spec) &&
+            originalLocation != "about:blank" &&
+            this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec &&
+            this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
+          // Indicating that we started a load will allow the location
+          // bar to be cleared when the load finishes.
+          // In order to not overwrite user-typed content, we avoid it
+          // (see if condition above) in a very specific case:
+          // If the load is of an 'initial' page (e.g. about:privatebrowsing,
+          // about:newtab, etc.), was not explicitly typed in the location
+          // bar by the user, is not about:blank (because about:blank can be
+          // loaded by websites under their principal), and the current
+          // page in the browser is about:blank (indicating it is a newly
+          // created or re-created browser, e.g. because it just switched
+          // remoteness or is a new tab/window).
+          this.mBrowser.urlbarChangeTracker.startedLoad();
+        }
+        delete this.mBrowser.initialPageLoadedFromURLBar;
+        // If the browser is loading it must not be crashed anymore
+        this.mTab.removeAttribute("crashed");
+      }
+
+      if (this._shouldShowProgress(aRequest)) {
+        if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING) &&
+            aWebProgress && aWebProgress.isTopLevel) {
+          this.mTab.setAttribute("busy", "true");
+          this.mTab._notselectedsinceload = !this.mTab.selected;
+          SchedulePressure.startMonitoring(window, {
+            highPressureFn() {
+              // Only switch back to the SVG loading indicator after getting
+              // three consecutive low pressure callbacks. Used to prevent
+              // switching quickly between the SVG and APNG loading indicators.
+              gBrowser.tabContainer._schedulePressureCount = gBrowser.schedulePressureDefaultCount;
+              gBrowser.tabContainer.setAttribute("schedulepressure", "true");
+            },
+            lowPressureFn() {
+              if (!gBrowser.tabContainer._schedulePressureCount ||
+                --gBrowser.tabContainer._schedulePressureCount <= 0) {
+                gBrowser.tabContainer.removeAttribute("schedulepressure");
+              }
+
+              // If tabs are closed while they are loading we need to
+              // stop monitoring schedule pressure. We don't stop monitoring
+              // during high pressure times because we want to eventually
+              // return to the SVG tab loading animations.
+              let continueMonitoring = true;
+              if (!document.querySelector(".tabbrowser-tab[busy]")) {
+                SchedulePressure.stopMonitoring(window);
+                continueMonitoring = false;
+              }
+              return { continueMonitoring };
+            },
+          });
+          gBrowser.syncThrobberAnimations(this.mTab);
+        }
+
+        if (this.mTab.selected) {
+          gBrowser.mIsBusy = true;
+        }
+      }
+    } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
+               aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+      if (this.mTab.hasAttribute("busy")) {
+        this.mTab.removeAttribute("busy");
+        if (!document.querySelector(".tabbrowser-tab[busy]")) {
+          SchedulePressure.stopMonitoring(window);
+          gBrowser.tabContainer.removeAttribute("schedulepressure");
+        }
+
+        // Only animate the "burst" indicating the page has loaded if
+        // the top-level page is the one that finished loading.
+        if (aWebProgress.isTopLevel && !aWebProgress.isLoadingDocument &&
+            Components.isSuccessCode(aStatus) &&
+            !gBrowser.tabAnimationsInProgress &&
+            Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
+          if (this.mTab._notselectedsinceload) {
+            this.mTab.setAttribute("notselectedsinceload", "true");
+          } else {
+            this.mTab.removeAttribute("notselectedsinceload");
+          }
+
+          this.mTab.setAttribute("bursting", "true");
+        }
+
+        gBrowser._tabAttrModified(this.mTab, ["busy"]);
+        if (!this.mTab.selected)
+          this.mTab.setAttribute("unread", "true");
+      }
+      this.mTab.removeAttribute("progress");
+
+      if (aWebProgress.isTopLevel) {
+        let isSuccessful = Components.isSuccessCode(aStatus);
+        if (!isSuccessful && !isTabEmpty(this.mTab)) {
+          // Restore the current document's location in case the
+          // request was stopped (possibly from a content script)
+          // before the location changed.
+
+          this.mBrowser.userTypedValue = null;
+
+          let inLoadURI = this.mBrowser.inLoadURI;
+          if (this.mTab.selected && gURLBar && !inLoadURI) {
+            URLBarSetURI();
+          }
+        } else if (isSuccessful) {
+          this.mBrowser.urlbarChangeTracker.finishedLoad();
+        }
+
+        // Ignore initial about:blank to prevent flickering.
+        if (!this.mBrowser.mIconURL && !ignoreBlank) {
+          // Don't switch to the default icon on about:home or about:newtab,
+          // since these pages get their favicon set in browser code to
+          // improve perceived performance.
+          let isNewTab = originalLocation &&
+            (originalLocation.spec == "about:newtab" ||
+              originalLocation.spec == "about:privatebrowsing" ||
+              originalLocation.spec == "about:home");
+          if (!isNewTab) {
+            gBrowser.useDefaultIcon(this.mTab);
+          }
+        }
+      }
+
+      // For keyword URIs clear the user typed value since they will be changed into real URIs
+      if (location.scheme == "keyword")
+        this.mBrowser.userTypedValue = null;
+
+      if (this.mTab.selected)
+        gBrowser.mIsBusy = false;
+    }
+
+    if (ignoreBlank) {
+      this._callProgressListeners("onUpdateCurrentBrowser",
+                                  [aStateFlags, aStatus, "", 0],
+                                  true, false);
+    } else {
+      this._callProgressListeners("onStateChange",
+                                  [aWebProgress, aRequest, aStateFlags, aStatus],
+                                  true, false);
+    }
+
+    this._callProgressListeners("onStateChange",
+                                [aWebProgress, aRequest, aStateFlags, aStatus],
+                                false);
+
+    if (aStateFlags & (nsIWebProgressListener.STATE_START |
+        nsIWebProgressListener.STATE_STOP)) {
+      // reset cached temporary values at beginning and end
+      this.mMessage = "";
+      this.mTotalProgress = 0;
+    }
+    this.mStateFlags = aStateFlags;
+    this.mStatus = aStatus;
+  }
+  /* eslint-enable complexity */
+
+  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+    // OnLocationChange is called for both the top-level content
+    // and the subframes.
+    let topLevel = aWebProgress.isTopLevel;
+
+    if (topLevel) {
+      let isSameDocument = !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
+      // We need to clear the typed value
+      // if the document failed to load, to make sure the urlbar reflects the
+      // failed URI (particularly for SSL errors). However, don't clear the value
+      // if the error page's URI is about:blank, because that causes complete
+      // loss of urlbar contents for invalid URI errors (see bug 867957).
+      // Another reason to clear the userTypedValue is if this was an anchor
+      // navigation initiated by the user.
+      if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
+          ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+            aLocation.spec != "about:blank") ||
+          (isSameDocument && this.mBrowser.inLoadURI)) {
+        this.mBrowser.userTypedValue = null;
+      }
+
+      // If the tab has been set to "busy" outside the stateChange
+      // handler below (e.g. by sessionStore.navigateAndRestore), and
+      // the load results in an error page, it's possible that there
+      // isn't any (STATE_IS_NETWORK & STATE_STOP) state to cause busy
+      // attribute being removed. In this case we should remove the
+      // attribute here.
+      if ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+          this.mTab.hasAttribute("busy")) {
+        this.mTab.removeAttribute("busy");
+        gBrowser._tabAttrModified(this.mTab, ["busy"]);
+      }
+
+      // If the browser was playing audio, we should remove the playing state.
+      if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
+        clearTimeout(this.mTab._soundPlayingAttrRemovalTimer);
+        this.mTab._soundPlayingAttrRemovalTimer = 0;
+        this.mTab.removeAttribute("soundplaying");
+        gBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
+      }
+
+      // If the browser was previously muted, we should restore the muted state.
+      if (this.mTab.hasAttribute("muted")) {
+        this.mTab.linkedBrowser.mute();
+      }
+
+      if (gBrowser.isFindBarInitialized(this.mTab)) {
+        let findBar = gBrowser.getFindBar(this.mTab);
+
+        // Close the Find toolbar if we're in old-style TAF mode
+        if (findBar.findMode != findBar.FIND_NORMAL) {
+          findBar.close();
+        }
+      }
+
+      gBrowser.setTabTitle(this.mTab);
+
+      // Don't clear the favicon if this tab is in the pending
+      // state, as SessionStore will have set the icon for us even
+      // though we're pointed at an about:blank. Also don't clear it
+      // if onLocationChange was triggered by a pushState or a
+      // replaceState (bug 550565) or a hash change (bug 408415).
+      if (!this.mTab.hasAttribute("pending") &&
+          aWebProgress.isLoadingDocument &&
+          !isSameDocument) {
+        this.mBrowser.mIconURL = null;
+      }
+
+      let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
+      if (this.mBrowser.registeredOpenURI) {
+        gBrowser._unifiedComplete
+          .unregisterOpenPage(this.mBrowser.registeredOpenURI, userContextId);
+        delete this.mBrowser.registeredOpenURI;
+      }
+      // Tabs in private windows aren't registered as "Open" so
+      // that they don't appear as switch-to-tab candidates.
+      if (!isBlankPageURL(aLocation.spec) &&
+          (!PrivateBrowsingUtils.isWindowPrivate(window) ||
+            PrivateBrowsingUtils.permanentPrivateBrowsing)) {
+        gBrowser._unifiedComplete.registerOpenPage(aLocation, userContextId);
+        this.mBrowser.registeredOpenURI = aLocation;
+      }
+    }
+
+    if (!this.mBlank) {
+      this._callProgressListeners("onLocationChange",
+                                  [aWebProgress, aRequest, aLocation, aFlags]);
+    }
+
+    if (topLevel) {
+      this.mBrowser.lastURI = aLocation;
+      this.mBrowser.lastLocationChange = Date.now();
+    }
+  }
+
+  onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+    if (this.mBlank)
+      return;
+
+    this._callProgressListeners("onStatusChange",
+                                [aWebProgress, aRequest, aStatus, aMessage]);
+
+    this.mMessage = aMessage;
+  }
+
+  onSecurityChange(aWebProgress, aRequest, aState) {
+    this._callProgressListeners("onSecurityChange",
+                                [aWebProgress, aRequest, aState]);
+  }
+
+  onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
+    return this._callProgressListeners("onRefreshAttempted",
+                                       [aWebProgress, aURI, aDelay, aSameURI]);
+  }
+
+  QueryInterface(aIID) {
+    if (aIID.equals(Ci.nsIWebProgressListener) ||
+        aIID.equals(Ci.nsIWebProgressListener2) ||
+        aIID.equals(Ci.nsISupportsWeakReference) ||
+        aIID.equals(Ci.nsISupports))
+      return this;
+    throw Cr.NS_NOINTERFACE;
+  }
+}
+