diff options
Diffstat (limited to 'browser/base/content/browser-captivePortal.js')
-rw-r--r-- | browser/base/content/browser-captivePortal.js | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/browser/base/content/browser-captivePortal.js b/browser/base/content/browser-captivePortal.js new file mode 100644 index 000000000..c2e45c4ed --- /dev/null +++ b/browser/base/content/browser-captivePortal.js @@ -0,0 +1,257 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +XPCOMUtils.defineLazyServiceGetter(this, "cps", + "@mozilla.org/network/captive-portal-service;1", + "nsICaptivePortalService"); + +var CaptivePortalWatcher = { + /** + * This constant is chosen to be large enough for a portal recheck to complete, + * and small enough that the delay in opening a tab isn't too noticeable. + * Please see comments for _delayedCaptivePortalDetected for more details. + */ + PORTAL_RECHECK_DELAY_MS: Preferences.get("captivedetect.portalRecheckDelayMS", 500), + + // This is the value used to identify the captive portal notification. + PORTAL_NOTIFICATION_VALUE: "captive-portal-detected", + + // This holds a weak reference to the captive portal tab so that we + // don't leak it if the user closes it. + _captivePortalTab: null, + + /** + * If a portal is detected when we don't have focus, we first wait for focus + * and then add the tab if, after a recheck, the portal is still active. This + * is set to true while we wait so that in the unlikely event that we receive + * another notification while waiting, we don't do things twice. + */ + _delayedCaptivePortalDetectedInProgress: false, + + // In the situation above, this is set to true while we wait for the recheck. + // This flag exists so that tests can appropriately simulate a recheck. + _waitingForRecheck: false, + + get _captivePortalNotification() { + let nb = document.getElementById("high-priority-global-notificationbox"); + return nb.getNotificationWithValue(this.PORTAL_NOTIFICATION_VALUE); + }, + + get canonicalURL() { + return Services.prefs.getCharPref("captivedetect.canonicalURL"); + }, + + get _browserBundle() { + delete this._browserBundle; + return this._browserBundle = + Services.strings.createBundle("chrome://browser/locale/browser.properties"); + }, + + init() { + Services.obs.addObserver(this, "captive-portal-login", false); + Services.obs.addObserver(this, "captive-portal-login-abort", false); + Services.obs.addObserver(this, "captive-portal-login-success", false); + + if (cps.state == cps.LOCKED_PORTAL) { + // A captive portal has already been detected. + this._captivePortalDetected(); + + // Automatically open a captive portal tab if there's no other browser window. + let windows = Services.wm.getEnumerator("navigator:browser"); + if (windows.getNext() == window && !windows.hasMoreElements()) { + this.ensureCaptivePortalTab(); + } + } + + cps.recheckCaptivePortal(); + }, + + uninit() { + Services.obs.removeObserver(this, "captive-portal-login"); + Services.obs.removeObserver(this, "captive-portal-login-abort"); + Services.obs.removeObserver(this, "captive-portal-login-success"); + + + if (this._delayedCaptivePortalDetectedInProgress) { + Services.obs.removeObserver(this, "xul-window-visible"); + } + }, + + observe(aSubject, aTopic, aData) { + switch (aTopic) { + case "captive-portal-login": + this._captivePortalDetected(); + break; + case "captive-portal-login-abort": + case "captive-portal-login-success": + this._captivePortalGone(); + break; + case "xul-window-visible": + this._delayedCaptivePortalDetected(); + break; + } + }, + + _captivePortalDetected() { + if (this._delayedCaptivePortalDetectedInProgress) { + return; + } + + let win = RecentWindow.getMostRecentBrowserWindow(); + // If no browser window has focus, open and show the tab when we regain focus. + // This is so that if a different application was focused, when the user + // (re-)focuses a browser window, we open the tab immediately in that window + // so they can log in before continuing to browse. + if (win != Services.ww.activeWindow) { + this._delayedCaptivePortalDetectedInProgress = true; + Services.obs.addObserver(this, "xul-window-visible", false); + } + + this._showNotification(); + }, + + /** + * Called after we regain focus if we detect a portal while a browser window + * doesn't have focus. Triggers a portal recheck to reaffirm state, and adds + * the tab if needed after a short delay to allow the recheck to complete. + */ + _delayedCaptivePortalDetected() { + if (!this._delayedCaptivePortalDetectedInProgress) { + return; + } + + let win = RecentWindow.getMostRecentBrowserWindow(); + if (win != Services.ww.activeWindow) { + // The window that got focused was not a browser window. + return; + } + Services.obs.removeObserver(this, "xul-window-visible"); + this._delayedCaptivePortalDetectedInProgress = false; + + if (win != window) { + // Some other browser window got focus, we don't have to do anything. + return; + } + // Trigger a portal recheck. The user may have logged into the portal via + // another client, or changed networks. + cps.recheckCaptivePortal(); + this._waitingForRecheck = true; + let requestTime = Date.now(); + + let self = this; + Services.obs.addObserver(function observer() { + let time = Date.now() - requestTime; + Services.obs.removeObserver(observer, "captive-portal-check-complete"); + self._waitingForRecheck = false; + if (cps.state != cps.LOCKED_PORTAL) { + // We're free of the portal! + return; + } + + if (time <= self.PORTAL_RECHECK_DELAY_MS) { + // The amount of time elapsed since we requested a recheck (i.e. since + // the browser window was focused) was small enough that we can add and + // focus a tab with the login page with no noticeable delay. + self.ensureCaptivePortalTab(); + } + }, "captive-portal-check-complete", false); + }, + + _captivePortalGone() { + if (this._delayedCaptivePortalDetectedInProgress) { + Services.obs.removeObserver(this, "xul-window-visible"); + this._delayedCaptivePortalDetectedInProgress = false; + } + + this._removeNotification(); + }, + + handleEvent(aEvent) { + if (aEvent.type != "TabSelect" || !this._captivePortalTab || !this._captivePortalNotification) { + return; + } + + let tab = this._captivePortalTab.get(); + let n = this._captivePortalNotification; + if (!tab || !n) { + return; + } + + let doc = tab.ownerDocument; + let button = n.querySelector("button.notification-button"); + if (doc.defaultView.gBrowser.selectedTab == tab) { + button.style.visibility = "hidden"; + } else { + button.style.visibility = "visible"; + } + }, + + _showNotification() { + let buttons = [ + { + label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage"), + callback: () => { + this.ensureCaptivePortalTab(); + + // Returning true prevents the notification from closing. + return true; + }, + isDefault: true, + }, + ]; + + let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage2"); + + let closeHandler = (aEventName) => { + if (aEventName != "removed") { + return; + } + gBrowser.tabContainer.removeEventListener("TabSelect", this); + }; + + let nb = document.getElementById("high-priority-global-notificationbox"); + nb.appendNotification(message, this.PORTAL_NOTIFICATION_VALUE, "", + nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler); + + gBrowser.tabContainer.addEventListener("TabSelect", this); + }, + + _removeNotification() { + let n = this._captivePortalNotification; + if (!n || !n.parentNode) { + return; + } + n.close(); + }, + + ensureCaptivePortalTab() { + let tab; + if (this._captivePortalTab) { + tab = this._captivePortalTab.get(); + } + + // If the tab is gone or going, we need to open a new one. + if (!tab || tab.closing || !tab.parentNode) { + tab = gBrowser.addTab(this.canonicalURL, { ownerTab: gBrowser.selectedTab }); + this._captivePortalTab = Cu.getWeakReference(tab); + } + + gBrowser.selectedTab = tab; + + let canonicalURI = makeURI(this.canonicalURL); + + // 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); + }, +}; |