1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 var CaptivePortalWatcher = {
6 // This is the value used to identify the captive portal notification.
7 PORTAL_NOTIFICATION_VALUE: "captive-portal-detected",
9 // This holds a weak reference to the captive portal tab so that we
10 // don't leak it if the user closes it.
11 _captivePortalTab: null,
14 * If a portal is detected when we don't have focus, we first wait for focus
15 * and then add the tab if, after a recheck, the portal is still active. This
16 * is set to true while we wait so that in the unlikely event that we receive
17 * another notification while waiting, we don't do things twice.
19 _delayedCaptivePortalDetectedInProgress: false,
21 // In the situation above, this is set to true while we wait for the recheck.
22 // This flag exists so that tests can appropriately simulate a recheck.
23 _waitingForRecheck: false,
25 get _captivePortalNotification() {
26 return gHighPriorityNotificationBox.getNotificationWithValue(
27 this.PORTAL_NOTIFICATION_VALUE);
31 return Services.prefs.getCharPref("captivedetect.canonicalURL");
34 get _browserBundle() {
35 delete this._browserBundle;
36 return this._browserBundle =
37 Services.strings.createBundle("chrome://browser/locale/browser.properties");
41 Services.obs.addObserver(this, "captive-portal-login");
42 Services.obs.addObserver(this, "captive-portal-login-abort");
43 Services.obs.addObserver(this, "captive-portal-login-success");
45 this._cps = Cc["@mozilla.org/network/captive-portal-service;1"]
46 .getService(Ci.nsICaptivePortalService);
48 if (this._cps.state == this._cps.LOCKED_PORTAL) {
49 // A captive portal has already been detected.
50 this._captivePortalDetected();
52 // Automatically open a captive portal tab if there's no other browser window.
53 if (BrowserWindowTracker.windowCount == 1) {
54 this.ensureCaptivePortalTab();
56 } else if (this._cps.state == this._cps.UNKNOWN) {
57 // We trigger a portal check after delayed startup to avoid doing a network
58 // request before first paint.
59 this._delayedRecheckPending = true;
62 // This constant is chosen to be large enough for a portal recheck to complete,
63 // and small enough that the delay in opening a tab isn't too noticeable.
64 // Please see comments for _delayedCaptivePortalDetected for more details.
65 XPCOMUtils.defineLazyPreferenceGetter(this, "PORTAL_RECHECK_DELAY_MS",
66 "captivedetect.portalRecheckDelayMS", 500);
70 Services.obs.removeObserver(this, "captive-portal-login");
71 Services.obs.removeObserver(this, "captive-portal-login-abort");
72 Services.obs.removeObserver(this, "captive-portal-login-success");
74 this._cancelDelayedCaptivePortal();
78 if (this._delayedRecheckPending) {
79 delete this._delayedRecheckPending;
80 this._cps.recheckCaptivePortal();
84 observe(aSubject, aTopic, aData) {
86 case "captive-portal-login":
87 this._captivePortalDetected();
89 case "captive-portal-login-abort":
90 case "captive-portal-login-success":
91 this._captivePortalGone();
93 case "delayed-captive-portal-handled":
94 this._cancelDelayedCaptivePortal();
99 _captivePortalDetected() {
100 if (this._delayedCaptivePortalDetectedInProgress) {
104 let win = BrowserWindowTracker.getTopWindow();
106 // Used by tests: ignore the main test window in order to enable testing of
107 // the case where we have no open windows.
108 if (win.document.documentElement.getAttribute("ignorecaptiveportal")) {
112 // If no browser window has focus, open and show the tab when we regain focus.
113 // This is so that if a different application was focused, when the user
114 // (re-)focuses a browser window, we open the tab immediately in that window
115 // so they can log in before continuing to browse.
116 if (win != Services.focus.activeWindow) {
117 this._delayedCaptivePortalDetectedInProgress = true;
118 window.addEventListener("activate", this, { once: true });
119 Services.obs.addObserver(this, "delayed-captive-portal-handled");
122 this._showNotification();
126 * Called after we regain focus if we detect a portal while a browser window
127 * doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
128 * the tab if needed after a short delay to allow the recheck to complete.
130 _delayedCaptivePortalDetected() {
131 if (!this._delayedCaptivePortalDetectedInProgress) {
135 // Used by tests: ignore the main test window in order to enable testing of
136 // the case where we have no open windows.
137 if (window.document.documentElement.getAttribute("ignorecaptiveportal")) {
141 Services.obs.notifyObservers(null, "delayed-captive-portal-handled");
143 // Trigger a portal recheck. The user may have logged into the portal via
144 // another client, or changed networks.
145 this._cps.recheckCaptivePortal();
146 this._waitingForRecheck = true;
147 let requestTime = Date.now();
149 let observer = () => {
150 let time = Date.now() - requestTime;
151 Services.obs.removeObserver(observer, "captive-portal-check-complete");
152 this._waitingForRecheck = false;
153 if (this._cps.state != this._cps.LOCKED_PORTAL) {
154 // We're free of the portal!
158 if (time <= this.PORTAL_RECHECK_DELAY_MS) {
159 // The amount of time elapsed since we requested a recheck (i.e. since
160 // the browser window was focused) was small enough that we can add and
161 // focus a tab with the login page with no noticeable delay.
162 this.ensureCaptivePortalTab();
165 Services.obs.addObserver(observer, "captive-portal-check-complete");
168 _captivePortalGone() {
169 this._cancelDelayedCaptivePortal();
170 this._removeNotification();
173 _cancelDelayedCaptivePortal() {
174 if (this._delayedCaptivePortalDetectedInProgress) {
175 this._delayedCaptivePortalDetectedInProgress = false;
176 Services.obs.removeObserver(this, "delayed-captive-portal-handled");
177 window.removeEventListener("activate", this);
181 handleEvent(aEvent) {
182 switch (aEvent.type) {
184 this._delayedCaptivePortalDetected();
187 if (!this._captivePortalTab || !this._captivePortalNotification) {
191 let tab = this._captivePortalTab.get();
192 let n = this._captivePortalNotification;
197 let doc = tab.ownerDocument;
198 let button = n.querySelector("button.notification-button");
199 if (doc.defaultView.gBrowser.selectedTab == tab) {
200 button.style.visibility = "hidden";
202 button.style.visibility = "visible";
208 _showNotification() {
211 label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage2"),
213 this.ensureCaptivePortalTab();
215 // Returning true prevents the notification from closing.
221 let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage3");
223 let closeHandler = (aEventName) => {
224 if (aEventName != "removed") {
227 gBrowser.tabContainer.removeEventListener("TabSelect", this);
230 gHighPriorityNotificationBox.appendNotification(
231 message, this.PORTAL_NOTIFICATION_VALUE, "",
232 gHighPriorityNotificationBox.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
234 gBrowser.tabContainer.addEventListener("TabSelect", this);
237 _removeNotification() {
238 let n = this._captivePortalNotification;
239 if (!n || !n.parentNode) {
245 ensureCaptivePortalTab() {
247 if (this._captivePortalTab) {
248 tab = this._captivePortalTab.get();
251 // If the tab is gone or going, we need to open a new one.
252 if (!tab || tab.closing || !tab.parentNode) {
253 tab = gBrowser.addWebTab(this.canonicalURL, {
254 ownerTab: gBrowser.selectedTab,
255 triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
256 userContextId: gBrowser.contentPrincipal.userContextId,
259 this._captivePortalTab = Cu.getWeakReference(tab);
262 gBrowser.selectedTab = tab;
264 let canonicalURI = Services.io.newURI(this.canonicalURL);
266 // When we are no longer captive, close the tab if it's at the canonical URL.
267 let tabCloser = () => {
268 Services.obs.removeObserver(tabCloser, "captive-portal-login-abort");
269 Services.obs.removeObserver(tabCloser, "captive-portal-login-success");
270 if (!tab || tab.closing || !tab.parentNode || !tab.linkedBrowser ||
271 !tab.linkedBrowser.currentURI.equalsExceptRef(canonicalURI)) {
274 gBrowser.removeTab(tab);
276 Services.obs.addObserver(tabCloser, "captive-portal-login-abort");
277 Services.obs.addObserver(tabCloser, "captive-portal-login-success");