Bug 989197 - Show alternate UI in cert error pages when a captive portal is active. r=Gijs draft
authorNihanth Subramanya <nhnt11@gmail.com>
Tue, 08 Nov 2016 16:10:12 +0530
changeset 445686 3c7fc59d54e3a12fb1bf28fa36e2ff3adfaeb6cd
parent 445465 8d8846f63b74eb930e48b410730ae088e9bdbee8
child 445687 e3485534505150471c5ebeb6f0387c6e9267f193
push id37584
push usernhnt11@gmail.com
push dateWed, 30 Nov 2016 03:34:50 +0000
reviewersGijs
bugs989197
milestone53.0a1
Bug 989197 - Show alternate UI in cert error pages when a captive portal is active. r=Gijs
browser/base/content/aboutNetError.xhtml
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/tabbrowser.xml
browser/themes/shared/aboutNetError.css
docshell/base/nsDocShell.cpp
netwerk/base/CaptivePortalService.cpp
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -71,16 +71,27 @@
         // desc == -1 if not found; if so, return an empty string
         // instead of what would turn out to be portions of the URI
         if (desc == -1)
           return "";
 
         return decodeURIComponent(url.slice(desc + 2));
       }
 
+      function isCaptive() {
+        let url = document.documentURI;
+        let matches = url.match(/captive\=([^&]+)\&/);
+        // captive flag is optional, if no match just return false.
+        if (!matches || matches.length < 2)
+          return false;
+
+        // parenthetical match is the second entry
+        return decodeURIComponent(matches[1]) == "true";
+      }
+
       function retryThis(buttonEl)
       {
         // Note: The application may wish to handle switching off "offline mode"
         // before this event handler runs, but using a capturing event handler.
 
         // Session history has the URL of the page that failed
         // to load, not the one of the error page. So, just call
         // reload(), which will also repost POST data correctly.
@@ -119,17 +130,17 @@
         panel.style.display = "block";
         document.getElementById("netErrorButtonContainer").style.display = "none";
         document.getElementById("prefResetButton").addEventListener("click", function resetPreferences(e) {
           const event = new CustomEvent("AboutNetErrorResetPreferences", {bubbles:true});
           document.dispatchEvent(event);
         });
       }
 
-      function showAdvancedButton(allowOverride) {
+      function setupAdvancedButton(allowOverride) {
         // Get the hostname and add it to the panel
         var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
         var panel = document.getElementById(panelId);
         for (var span of panel.querySelectorAll("span.hostname")) {
           span.textContent = document.location.hostname;
         }
         if (!gIsCertError) {
           panel.replaceChild(document.getElementById("errorLongDesc"),
@@ -155,107 +166,97 @@
           }
         });
 
         if (allowOverride) {
           document.getElementById("overrideWeakCryptoPanel").style.display = "flex";
           var overrideLink = document.getElementById("overrideWeakCrypto");
           overrideLink.addEventListener("click", () => doOverride(overrideLink), false);
         }
-      }
 
-      function initPageCertError() {
-        document.body.className = "certerror";
-        document.title = document.getElementById("certErrorPageTitle").textContent;
-        for (let host of document.querySelectorAll(".hostname")) {
-          host.textContent = document.location.hostname;
+        if (!gIsCertError) {
+          return;
         }
 
-        showAdvancedButton(true);
-
-        var cssClass = getCSSClass();
-        if (cssClass == "expertBadCert") {
+        if (getCSSClass() == "expertBadCert") {
           toggleDisplay(document.getElementById("badCertAdvancedPanel"));
           // Toggling the advanced panel must ensure that the debugging
           // information panel is hidden as well, since it's opened by the
           // error code link in the advanced panel.
           var div = document.getElementById("certificateErrorDebugInformation");
           div.style.display = "none";
         }
 
-        document.getElementById("learnMoreContainer").style.display = "block";
-
-        var checkbox = document.getElementById("automaticallyReportInFuture");
-        checkbox.addEventListener("change", function({target: {checked}}) {
-          document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
-            detail: checked,
-            bubbles: true
-          }));
-        });
+        disallowCertOverridesIfNeeded();
 
-        addEventListener("AboutNetErrorOptions", function(event) {
-          var options = JSON.parse(event.detail);
-          if (options && options.enabled) {
-            // Display error reporting UI
-            document.getElementById("certificateErrorReporting").style.display = "block";
+        document.getElementById("badCertTechnicalInfo").textContent = getDescription();
+      }
 
-            // set the checkbox
-            checkbox.checked = !!options.automatic;
-          }
-        }, true, true);
-
+      function disallowCertOverridesIfNeeded() {
+        var cssClass = getCSSClass();
         // Disallow overrides if this is a Strict-Transport-Security
         // host and the cert is bad (STS Spec section 7.3) or if the
         // certerror is in a frame (bug 633691).
         if (cssClass == "badStsCert" || window != top) {
           document.getElementById("exceptionDialogButton").setAttribute("hidden", "true");
         }
         if (cssClass == "badStsCert") {
           document.getElementById("badStsCertExplanation").removeAttribute("hidden");
         }
-
-        document.getElementById("badCertTechnicalInfo").textContent = getDescription();
-
-        var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
-        document.getElementById("advancedButton").dispatchEvent(event);
-
-        addDomainErrorLinks();
       }
 
       function initPage()
       {
         var err = getErrorCode();
         gIsCertError = (err == "nssBadCert");
+        // Only worry about captive portals if this is a cert error.
+        let captivePortalActive = isCaptive() && gIsCertError;
 
         // if it's an unknown error or there's no title or description
         // defined, get the generic message
-        var errTitle = document.getElementById("et_" + err);
-        var errDesc  = document.getElementById("ed_" + err);
+        var errTitle;
+        var errDesc;
+        if (captivePortalActive) {
+          errTitle = document.getElementById("et_captivePortal");
+          errDesc = document.getElementById("ed_captivePortal");
+        } else {
+          errTitle = document.getElementById("et_" + err);
+          errDesc  = document.getElementById("ed_" + err);
+        }
         if (!errTitle || !errDesc)
         {
           errTitle = document.getElementById("et_generic");
           errDesc  = document.getElementById("ed_generic");
         }
 
         document.querySelector(".title-text").innerHTML = errTitle.innerHTML;
 
         var sd = document.getElementById("errorShortDescText");
         if (sd) {
-          if (gIsCertError) {
+          if (captivePortalActive) {
+            sd.innerHTML = document.getElementById("ed_captivePortal").innerHTML;
+          }
+          else if (gIsCertError) {
             sd.innerHTML = document.getElementById("ed_nssBadCert").innerHTML;
           }
           else {
             sd.textContent = getDescription();
           }
         }
+        if (captivePortalActive) {
+          initPageCaptivePortal();
+          return;
+        }
         if (gIsCertError) {
           initPageCertError();
           return;
         }
 
+        document.body.className = "neterror";
+
         var ld = document.getElementById("errorLongDesc");
         if (ld)
         {
           ld.innerHTML = errDesc.innerHTML;
         }
 
         if (err == "sslv3Used") {
           document.getElementById("learnMoreContainer").style.display = "block";
@@ -339,17 +340,17 @@
               "SSL_ERROR_NO_CIPHERS_SUPPORTED"
             ].some((substring) => getDescription().includes(substring));
             // If it looks like an error that is user config based
             if (getErrorCode() == "nssFailure2" && hasPrefStyleError && options && options.changedCertPrefs) {
               showPrefChangeContainer();
             }
           }
           if (getErrorCode() == "weakCryptoUsed" || getErrorCode() == "sslv3Used") {
-            showAdvancedButton(getErrorCode() == "weakCryptoUsed");
+            setupAdvancedButton(getErrorCode() == "weakCryptoUsed");
           }
         }.bind(this), true, true);
 
         var event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
         document.dispatchEvent(event);
 
         if (err == "inadequateSecurityError") {
           // Remove the "Try again" button for HTTP/2 inadequate security as it
@@ -360,16 +361,71 @@
           for (var span of container.querySelectorAll("span.hostname")) {
             span.textContent = document.location.hostname;
           }
         }
 
         addDomainErrorLinks();
       }
 
+      function initPageCaptivePortal()
+      {
+        document.body.className = "captiveportal";
+        document.title = document.getElementById("captivePortalPageTitle").textContent;
+        for (let host of document.querySelectorAll(".hostname")) {
+          host.textContent = document.location.hostname;
+        }
+
+        document.getElementById("openPortalLoginPageButton")
+                .addEventListener("click", () => {
+          let event = new CustomEvent("AboutNetErrorOpenCaptivePortal", {bubbles:true});
+          document.dispatchEvent(event);
+        });
+
+        setupAdvancedButton(true);
+
+        addDomainErrorLinks();
+      }
+
+      function initPageCertError() {
+        document.body.className = "certerror";
+        document.title = document.getElementById("certErrorPageTitle").textContent;
+        for (let host of document.querySelectorAll(".hostname")) {
+          host.textContent = document.location.hostname;
+        }
+
+        setupAdvancedButton(true);
+
+        document.getElementById("learnMoreContainer").style.display = "block";
+
+        let checkbox = document.getElementById("automaticallyReportInFuture");
+        checkbox.addEventListener("change", function ({target: {checked}}) {
+          document.dispatchEvent(new CustomEvent("AboutNetErrorSetAutomatic", {
+            detail: checked,
+            bubbles: true
+          }));
+        });
+
+        addEventListener("AboutNetErrorOptions", function (event) {
+          var options = JSON.parse(event.detail);
+          if (options && options.enabled) {
+            // Display error reporting UI
+            document.getElementById("certificateErrorReporting").style.display = "block";
+
+            // set the checkbox
+            checkbox.checked = !!options.automatic;
+          }
+        }, true, true);
+
+        let event = new CustomEvent("AboutNetErrorLoad", {bubbles:true});
+        document.getElementById("advancedButton").dispatchEvent(event);
+
+        addDomainErrorLinks();
+      }
+
       /* Try to preserve the links contained in the error description, like
          the error code.
 
          Also, in the case of SSL error pages about domain mismatch, see if
          we can hyperlink the user to the correct site.  We don't want
          to do this generically since it allows MitM attacks to redirect
          users to a site under attacker control, but in certain cases
          it is safe (and helpful!) to do so.  Bug 402210
@@ -490,21 +546,23 @@
         el.appendChild(anchorEl);
       }
     ]]></script>
   </head>
 
   <body dir="&locale.dir;">
     <!-- Contains an alternate page title set on page init for cert errors. -->
     <div id="certErrorPageTitle" style="display: none;">&certerror.pagetitle1;</div>
+    <div id="captivePortalPageTitle" style="display: none;">&captivePortal.title;</div>
 
     <!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) -->
     <div id="errorContainer">
       <div id="errorTitlesContainer">
         <h1 id="et_generic">&generic.title;</h1>
+        <h1 id="et_captivePortal">&captivePortal.title;</h1>
         <h1 id="et_dnsNotFound">&dnsNotFound.title;</h1>
         <h1 id="et_fileNotFound">&fileNotFound.title;</h1>
         <h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1>
         <h1 id="et_malformedURI">&malformedURI.title;</h1>
         <h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1>
         <h1 id="et_connectionFailure">&connectionFailure.title;</h1>
         <h1 id="et_netTimeout">&netTimeout.title;</h1>
         <h1 id="et_redirectLoop">&redirectLoop.title;</h1>
@@ -524,16 +582,17 @@
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
         <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
         <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
+        <div id="ed_captivePortal">&captivePortal.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
         <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div>
         <div id="ed_malformedURI">&malformedURI.longDesc;</div>
         <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div>
         <div id="ed_connectionFailure">&connectionFailure.longDesc;</div>
         <div id="ed_netTimeout">&netTimeout.longDesc;</div>
         <div id="ed_redirectLoop">&redirectLoop.longDesc;</div>
@@ -586,18 +645,19 @@
           <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
         </div>
 
         <div id="prefChangeContainer" class="button-container">
           <p>&prefReset.longDesc;</p>
           <button id="prefResetButton" class="primary" autocomplete="off">&prefReset.label;</button>
         </div>
 
-        <div id="certErrorButtonContainer" class="button-container">
+        <div id="certErrorAndCaptivePortalButtonContainer" class="button-container">
           <button id="returnButton" class="primary" autocomplete="off" autofocus="true">&returnToPreviousPage.label;</button>
+          <button id="openPortalLoginPageButton" class="primary" autocomplete="off" autofocus="true">&openPortalLoginPage.label;</button>
           <div class="button-spacer"></div>
           <button id="advancedButton" autocomplete="off" autofocus="true">&advanced.label;</button>
         </div>
       </div>
 
       <div id="netErrorButtonContainer" class="button-container">
         <button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
       </div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -146,16 +146,19 @@ XPCOMUtils.defineLazyGetter(this, "Win7F
       onCloseWindow: function() {
         AeroPeek.onCloseWindow(window);
       }
     };
   }
   return null;
 });
 
+XPCOMUtils.defineLazyServiceGetter(this, "gCaptivePortalService",
+                                   "@mozilla.org/network/captive-portal-service;1",
+                                   "nsICaptivePortalService");
 
 const nsIWebNavigation = Ci.nsIWebNavigation;
 
 var gLastBrowserCharset = null;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
 var gContextMenu = null; // nsContextMenu instance
 var gMultiProcessBrowser =
@@ -2723,16 +2726,17 @@ const PREF_SSL_IMPACT = PREF_SSL_IMPACT_
  * Handle command events bubbling up from error page content
  * or from about:newtab or from remote error pages that invoke
  * us via async messaging.
  */
 var BrowserOnClick = {
   init: function() {
     let mm = window.messageManager;
     mm.addMessageListener("Browser:CertExceptionError", this);
+    mm.addMessageListener("Browser:OpenCaptivePortalPage", this);
     mm.addMessageListener("Browser:SiteBlockedError", this);
     mm.addMessageListener("Browser:EnableOnlineMode", this);
     mm.addMessageListener("Browser:SendSSLErrorReport", this);
     mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
     mm.addMessageListener("Browser:ResetSSLPreferences", this);
     mm.addMessageListener("Browser:SSLErrorReportTelemetry", this);
     mm.addMessageListener("Browser:OverrideWeakCrypto", this);
     mm.addMessageListener("Browser:SSLErrorGoBack", this);
@@ -2771,16 +2775,19 @@ var BrowserOnClick = {
 
   receiveMessage: function(msg) {
     switch (msg.name) {
       case "Browser:CertExceptionError":
         this.onCertError(msg.target, msg.data.elementId,
                          msg.data.isTopFrame, msg.data.location,
                          msg.data.securityInfoAsString);
       break;
+      case "Browser:OpenCaptivePortalPage":
+        this.onOpenCaptivePortalPage();
+      break;
       case "Browser:SiteBlockedError":
         this.onAboutBlocked(msg.data.elementId, msg.data.reason,
                             msg.data.isTopFrame, msg.data.location);
       break;
       case "Browser:EnableOnlineMode":
         if (Services.io.offline) {
           // Reset network state and refresh the page.
           Services.io.offline = false;
@@ -2905,16 +2912,38 @@ var BrowserOnClick = {
         let detailedInfo = getDetailedCertErrorInfo(location,
                                                     securityInfo);
         gClipboardHelper.copyString(detailedInfo);
         break;
 
     }
   },
 
+  onOpenCaptivePortalPage: function() {
+    // Open a new tab with the canonical URL that we use to check for a captive portal.
+    // It will be redirected to the login page.
+    let canonicalURL = Services.prefs.getCharPref("captivedetect.canonicalURL");
+    let tab = gBrowser.addTab(canonicalURL);
+    let canonicalURI = makeURI(canonicalURL);
+    gBrowser.selectedTab = tab;
+
+    // When we are no longer captive, close the tab if it's at the canonical URL.
+    let tabCloser = () => {
+      Services.obs.removeObserver(tabCloser, "captive-portal-login-abort");
+      Services.obs.removeObserver(tabCloser, "captive-portal-login-success");
+      if (!tab || tab.closing || !tab.parentNode || !tab.linkedBrowser ||
+          !tab.linkedBrowser.currentURI.equalsExceptRef(canonicalURI)) {
+        return;
+      }
+      gBrowser.removeTab(tab);
+    }
+    Services.obs.addObserver(tabCloser, "captive-portal-login-abort", false);
+    Services.obs.addObserver(tabCloser, "captive-portal-login-success", false);
+  },
+
   onAboutBlocked: function(elementId, reason, isTopFrame, location) {
     // Depending on what page we are displaying here (malware/phishing/unwanted)
     // use the right strings and links for each.
     let bucketName = "";
     let sendTelemetry = false;
     if (reason === 'malware') {
       sendTelemetry = true;
       bucketName = "WARNING_MALWARE_PAGE_";
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -260,31 +260,32 @@ function getSerializedSecurityInfo(docSh
 
   return serhelper.serializeToString(securityInfo);
 }
 
 var AboutNetAndCertErrorListener = {
   init: function(chromeGlobal) {
     addMessageListener("CertErrorDetails", this);
     chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
+    chromeGlobal.addEventListener('AboutNetErrorOpenCaptivePortal', this, false, true);
     chromeGlobal.addEventListener('AboutNetErrorSetAutomatic', this, false, true);
     chromeGlobal.addEventListener('AboutNetErrorOverride', this, false, true);
     chromeGlobal.addEventListener('AboutNetErrorResetPreferences', this, false, true);
   },
 
   get isAboutNetError() {
     return content.document.documentURI.startsWith("about:neterror");
   },
 
   get isAboutCertError() {
     return content.document.documentURI.startsWith("about:certerror");
   },
 
   receiveMessage: function(msg) {
-    if (!this.isAboutCertError) {
+    if (!this.isAboutCertError && !this.isAboutNetError) {
       return;
     }
 
     switch (msg.name) {
       case "CertErrorDetails":
         this.onCertErrorDetails(msg);
         break;
     }
@@ -343,16 +344,19 @@ var AboutNetAndCertErrorListener = {
     if (!this.isAboutNetError && !this.isAboutCertError) {
       return;
     }
 
     switch (aEvent.type) {
     case "AboutNetErrorLoad":
       this.onPageLoad(aEvent);
       break;
+    case "AboutNetErrorOpenCaptivePortal":
+      this.openCaptivePortalPage(aEvent);
+      break;
     case "AboutNetErrorSetAutomatic":
       this.onSetAutomatic(aEvent);
       break;
     case "AboutNetErrorOverride":
       this.onOverride(aEvent);
       break;
     case "AboutNetErrorResetPreferences":
       this.onResetPreferences(aEvent);
@@ -385,16 +389,20 @@ var AboutNetAndCertErrorListener = {
         automatic: automatic
       })
     }));
 
     sendAsyncMessage("Browser:SSLErrorReportTelemetry",
                      {reportStatus: TLS_ERROR_REPORT_TELEMETRY_UI_SHOWN});
   },
 
+  openCaptivePortalPage: function(evt) {
+    sendAsyncMessage("Browser:OpenCaptivePortalPage");
+  },
+
 
   onResetPreferences: function(evt) {
     sendAsyncMessage("Browser:ResetSSLPreferences");
   },
 
   onSetAutomatic: function(evt) {
     sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
       automatic: evt.detail
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -826,17 +826,17 @@
               }
 
               if (!this.mBlank) {
                 this._callProgressListeners("onLocationChange",
                                             [aWebProgress, aRequest, aLocation,
                                              aFlags]);
               }
 
-              if (topLevel) {
+              if (topLevel && this.mBrowser) {
                 this.mBrowser.lastURI = aLocation;
                 this.mBrowser.lastLocationChange = Date.now();
               }
             },
 
             onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
               if (this.mBlank)
                 return;
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -34,42 +34,54 @@ button:disabled {
 #prefChangeContainer {
   display: none;
 }
 
 #learnMoreContainer {
   display: none;
 }
 
-#certErrorButtonContainer {
+#certErrorAndCaptivePortalButtonContainer {
   display: none;
 }
 
-body.certerror #certErrorButtonContainer {
+body:not(.neterror) #certErrorAndCaptivePortalButtonContainer {
   display: flex;
 }
 
-body.certerror #netErrorButtonContainer {
+body:not(.neterror) #netErrorButtonContainer {
   display: none;
 }
 
 #errorTryAgain {
   margin-top: 1.2em;
   min-width: 150px;
 }
 
 #returnButton {
   min-width: 250px;
 }
 
 #advancedButton {
   display: none;
 }
 
-body.certerror #advancedButton {
+body.captiveportal #returnButton {
+  display: none;
+}
+
+body:not(.captiveportal) #openPortalLoginPageButton {
+  display: none;
+}
+
+#openPortalLoginPageButton {
+  margin-inline-start: 0;
+}
+
+body:not(.neterror) #advancedButton {
   display: block;
 }
 
 #certificateErrorReporting {
   display: none;
 }
 
 .container {
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -46,16 +46,17 @@
 #include "nsIContentViewer.h"
 #include "nsIDocumentLoaderFactory.h"
 #include "nsCURILoader.h"
 #include "nsDocShellCID.h"
 #include "nsDOMCID.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "mozilla/net/ReferrerPolicy.h"
+#include "mozilla/net/CaptivePortalService.h"
 #include "nsRect.h"
 #include "prenv.h"
 #include "nsIDOMWindow.h"
 #include "nsIGlobalObject.h"
 #include "nsIViewSourceChannel.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsPoint.h"
 #include "nsIObserverService.h"
@@ -5366,16 +5367,23 @@ nsDocShell::LoadErrorPage(nsIURI* aURI, 
   }
   errorPageUrl.AppendLiteral("&c=");
   errorPageUrl.AppendASCII(escapedCharset.get());
 
   nsAutoCString frameType(FrameTypeToString(mFrameType));
   errorPageUrl.AppendLiteral("&f=");
   errorPageUrl.AppendASCII(frameType.get());
 
+  nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID);
+  int32_t state;
+  if (cps && NS_SUCCEEDED(cps->GetState(&state)) &&
+      state == nsICaptivePortalService::LOCKED_PORTAL) {
+    errorPageUrl.AppendLiteral("&captive=true");
+  }
+
   // netError.xhtml's getDescription only handles the "d" parameter at the
   // end of the URL, so append it last.
   errorPageUrl.AppendLiteral("&d=");
   errorPageUrl.AppendASCII(escapedDescription.get());
 
   nsCOMPtr<nsIURI> errorPageURI;
   nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/netwerk/base/CaptivePortalService.cpp
+++ b/netwerk/base/CaptivePortalService.cpp
@@ -196,21 +196,16 @@ CaptivePortalService::SetStateInChild(in
 
 //-----------------------------------------------------------------------------
 // CaptivePortalService::nsICaptivePortalService
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 CaptivePortalService::GetState(int32_t *aState)
 {
-  *aState = UNKNOWN;
-  if (!mInitialized) {
-    return NS_ERROR_NOT_INITIALIZED;
-  }
-
   *aState = mState;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CaptivePortalService::RecheckCaptivePortal()
 {
   LOG(("CaptivePortalService::RecheckCaptivePortal\n"));