Bug 1527719 [wpt PR 15359] - Update wpt metadata, a=testonly
[gecko.git] / browser / base / content / browser-captivePortal.js
bloba954429d3e8720eb4562bfa0b35ffe9b5dc0ceec
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,
13   /**
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.
18    */
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);
28   },
30   get canonicalURL() {
31     return Services.prefs.getCharPref("captivedetect.canonicalURL");
32   },
34   get _browserBundle() {
35     delete this._browserBundle;
36     return this._browserBundle =
37       Services.strings.createBundle("chrome://browser/locale/browser.properties");
38   },
40   init() {
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();
55       }
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;
60     }
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);
67   },
69   uninit() {
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();
75   },
77   delayedStartup() {
78     if (this._delayedRecheckPending) {
79       delete this._delayedRecheckPending;
80       this._cps.recheckCaptivePortal();
81     }
82   },
84   observe(aSubject, aTopic, aData) {
85     switch (aTopic) {
86       case "captive-portal-login":
87         this._captivePortalDetected();
88         break;
89       case "captive-portal-login-abort":
90       case "captive-portal-login-success":
91         this._captivePortalGone();
92         break;
93       case "delayed-captive-portal-handled":
94         this._cancelDelayedCaptivePortal();
95         break;
96     }
97   },
99   _captivePortalDetected() {
100     if (this._delayedCaptivePortalDetectedInProgress) {
101       return;
102     }
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")) {
109       win = null;
110     }
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");
120     }
122     this._showNotification();
123   },
125   /**
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.
129    */
130   _delayedCaptivePortalDetected() {
131     if (!this._delayedCaptivePortalDetectedInProgress) {
132       return;
133     }
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")) {
138       return;
139     }
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!
155         return;
156       }
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();
163       }
164     };
165     Services.obs.addObserver(observer, "captive-portal-check-complete");
166   },
168   _captivePortalGone() {
169     this._cancelDelayedCaptivePortal();
170     this._removeNotification();
171   },
173   _cancelDelayedCaptivePortal() {
174     if (this._delayedCaptivePortalDetectedInProgress) {
175       this._delayedCaptivePortalDetectedInProgress = false;
176       Services.obs.removeObserver(this, "delayed-captive-portal-handled");
177       window.removeEventListener("activate", this);
178     }
179   },
181   handleEvent(aEvent) {
182     switch (aEvent.type) {
183       case "activate":
184         this._delayedCaptivePortalDetected();
185         break;
186       case "TabSelect":
187         if (!this._captivePortalTab || !this._captivePortalNotification) {
188           break;
189         }
191         let tab = this._captivePortalTab.get();
192         let n = this._captivePortalNotification;
193         if (!tab || !n) {
194           break;
195         }
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";
201         } else {
202           button.style.visibility = "visible";
203         }
204         break;
205     }
206   },
208   _showNotification() {
209     let buttons = [
210       {
211         label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage2"),
212         callback: () => {
213           this.ensureCaptivePortalTab();
215           // Returning true prevents the notification from closing.
216           return true;
217         },
218       },
219     ];
221     let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage3");
223     let closeHandler = (aEventName) => {
224       if (aEventName != "removed") {
225         return;
226       }
227       gBrowser.tabContainer.removeEventListener("TabSelect", this);
228     };
230     gHighPriorityNotificationBox.appendNotification(
231       message, this.PORTAL_NOTIFICATION_VALUE, "",
232       gHighPriorityNotificationBox.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
234     gBrowser.tabContainer.addEventListener("TabSelect", this);
235   },
237   _removeNotification() {
238     let n = this._captivePortalNotification;
239     if (!n || !n.parentNode) {
240       return;
241     }
242     n.close();
243   },
245   ensureCaptivePortalTab() {
246     let tab;
247     if (this._captivePortalTab) {
248       tab = this._captivePortalTab.get();
249     }
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,
257         }),
258       });
259       this._captivePortalTab = Cu.getWeakReference(tab);
260     }
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)) {
272         return;
273       }
274       gBrowser.removeTab(tab);
275     };
276     Services.obs.addObserver(tabCloser, "captive-portal-login-abort");
277     Services.obs.addObserver(tabCloser, "captive-portal-login-success");
278   },