--- a/browser/modules/CaptivePortalWatcher.jsm
+++ b/browser/modules/CaptivePortalWatcher.jsm
@@ -1,53 +1,58 @@
/* 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/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-/**
- * 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 _delayedAddCaptivePortalTab for more details.
- */
-const PORTAL_RECHECK_DELAY_MS = 150;
-
-// This is the value used to identify the captive portal notification.
-const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
this.EXPORTED_SYMBOLS = [ "CaptivePortalWatcher" ];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cps",
"@mozilla.org/network/captive-portal-service;1",
"nsICaptivePortalService");
this.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: 150,
+
+ // 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,
// This holds a weak reference to the captive portal notification.
_captivePortalNotification: null,
_initialized: false,
/**
* If a portal is detected when we don't have focus, we first wait for focus
- * and then add the tab after a small delay. This is set to true while we wait
- * so that in the unlikely event that we receive another notification while
- * waiting, we can avoid adding a second tab.
+ * 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.
*/
- _waitingToAddTab: false,
+ _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 canonicalURL() {
return Services.prefs.getCharPref("captivedetect.canonicalURL");
},
init() {
Services.obs.addObserver(this, "captive-portal-login", false);
Services.obs.addObserver(this, "captive-portal-login-abort", false);
@@ -77,33 +82,33 @@ this.CaptivePortalWatcher = {
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._delayedAddCaptivePortalTab();
+ this._delayedCaptivePortalDetected();
break;
}
},
_captivePortalDetected() {
- if (this._waitingToAddTab) {
+ if (this._delayedCaptivePortalDetectedInProgress) {
return;
}
let win = RecentWindow.getMostRecentBrowserWindow();
// If there's no browser window or none have 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 login before continuing to browse.
if (!win || win != Services.ww.activeWindow) {
- this._waitingToAddTab = true;
+ this._delayedCaptivePortalDetectedInProgress = true;
Services.obs.addObserver(this, "xul-window-visible", false);
return;
}
this._showNotification(win);
},
_ensureCaptivePortalTab(win) {
@@ -122,56 +127,59 @@ this.CaptivePortalWatcher = {
return tab;
},
/**
* 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.
*/
- _delayedAddCaptivePortalTab() {
- if (!this._waitingToAddTab) {
+ _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");
// 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._waitingToAddTab = false;
+ self._waitingForRecheck = false;
+ self._delayedCaptivePortalDetectedInProgress = false;
if (cps.state != cps.LOCKED_PORTAL) {
// We're free of the portal!
return;
}
self._showNotification(win);
- if (Date.now() - requestTime <= PORTAL_RECHECK_DELAY_MS) {
+ 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(win);
}
}, "captive-portal-check-complete", false);
},
_captivePortalGone() {
- if (this._waitingToAddTab) {
+ if (this._delayedCaptivePortalDetectedInProgress) {
Services.obs.removeObserver(this, "xul-window-visible");
- this._waitingToAddTab = false;
+ this._delayedCaptivePortalDetectedInProgress = false;
}
this._removeNotification();
if (!this._captivePortalTab) {
return;
}
@@ -244,17 +252,17 @@ this.CaptivePortalWatcher = {
let closeHandler = (aEventName) => {
if (aEventName != "removed") {
return;
}
win.gBrowser.tabContainer.removeEventListener("TabSelect", this);
};
let nb = win.document.getElementById("high-priority-global-notificationbox");
- let n = nb.appendNotification(message, PORTAL_NOTIFICATION_VALUE, "",
+ let n = nb.appendNotification(message, this.PORTAL_NOTIFICATION_VALUE, "",
nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
this._captivePortalNotification = Cu.getWeakReference(n);
win.gBrowser.tabContainer.addEventListener("TabSelect", this);
},
_removeNotification() {
--- a/browser/modules/test/browser_CaptivePortalWatcher.js
+++ b/browser/modules/test/browser_CaptivePortalWatcher.js
@@ -1,12 +1,19 @@
"use strict";
Components.utils.import("resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CaptivePortalWatcher",
+ "resource:///modules/CaptivePortalWatcher.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cps",
+ "@mozilla.org/network/captive-portal-service;1",
+ "nsICaptivePortalService");
+
const CANONICAL_CONTENT = "success";
const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
const PORTAL_NOTIFICATION_VALUE = "captive-portal-detected";
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({
set: [["captivedetect.canonicalURL", CANONICAL_URL],
@@ -17,52 +24,73 @@ add_task(function* setup() {
/**
* We can't close the original window opened by mochitest without failing, so
* override RecentWindow.getMostRecentBrowserWindow to make CaptivePortalWatcher
* think there's no window.
*/
function* portalDetectedNoBrowserWindow() {
let getMostRecentBrowserWindow = RecentWindow.getMostRecentBrowserWindow;
RecentWindow.getMostRecentBrowserWindow = () => {};
- Services.obs.notifyObservers(null, "captive-portal-login", null);
+ yield portalDetected();
RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
}
-function* openWindowAndWaitForPortalTabAndNotification() {
+function* portalDetected() {
+ Services.obs.notifyObservers(null, "captive-portal-login", null);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return cps.state == cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal detected.");
+}
+
+function* freePortal(aSuccess) {
+ Services.obs.notifyObservers(null,
+ "captive-portal-login-" + (aSuccess ? "success" : "abort"), null);
+ yield BrowserTestUtils.waitForCondition(() => {
+ return cps.state != cps.LOCKED_PORTAL;
+ }, "Waiting for Captive Portal Service to update state after portal freed.");
+}
+
+function* openWindowAndWaitForPortalUI(aLongRecheck) {
+ // CaptivePortalWatcher triggers a recheck when a window gains focus. If
+ // the time taken for the check to complete is under PORTAL_RECHECK_DELAY_MS,
+ // a tab with the login page is opened and selected. If it took longer,
+ // no tab is opened. It's not reliable to time things in an async test,
+ // so use a delay threshold of -1 to simulate a long recheck (so that any
+ // amount of time is considered excessive), and a very large threshold to
+ // simulate a short recheck.
+ CaptivePortalWatcher.PORTAL_RECHECK_DELAY_MS = aLongRecheck ? -1 : 1000000;
+
let win = yield BrowserTestUtils.openNewBrowserWindow();
- // Thanks to things being async, at this point we now have a new browser window
- // but the portal notification and tab may or may not have opened. So first we
- // check if there's already a portal notification, and if not, wait.
- let notification = win.document.getElementById("high-priority-global-notificationbox")
- .getNotificationWithValue(PORTAL_NOTIFICATION_VALUE);
- if (!notification) {
- notification =
- yield BrowserTestUtils.waitForGlobalNotificationBar(win, PORTAL_NOTIFICATION_VALUE);
+
+ // After a new window is opened, CaptivePortalWatcher asks for a recheck, and
+ // waits for it to complete. We need to manually tell it a recheck completed.
+ yield BrowserTestUtils.waitForCondition(() => {
+ return CaptivePortalWatcher._waitingForRecheck;
+ }, "Waiting for CaptivePortalWatcher to trigger a recheck.");
+ Services.obs.notifyObservers(null, "captive-portal-check-complete", null);
+
+ let notification = ensurePortalNotification(win);
+
+ if (aLongRecheck) {
+ ensureNoPortalTab(win);
+ testShowLoginPageButtonVisibility(notification, "visible");
+ return win;
}
- // Then we see if there's already a portal tab. If it's open, it'll be the second one.
+
let tab = win.gBrowser.tabs[1];
- if (!tab || tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
- // The tab either hasn't been opened yet or it hasn't loaded the portal URL.
- // Waiting for a location change in the tabbrowser covers both cases.
+ if (tab.linkedBrowser.currentURI.spec != CANONICAL_URL) {
+ // The tab should load the canonical URL, wait for it.
yield BrowserTestUtils.waitForLocationChange(win.gBrowser, CANONICAL_URL);
- // At this point the portal tab should be the second tab. If there is still
- // no second tab, something is wrong, and the selectedTab test below will fail.
- tab = win.gBrowser.tabs[1];
}
is(win.gBrowser.selectedTab, tab,
"The captive portal tab should be open and selected in the new window.");
testShowLoginPageButtonVisibility(notification, "hidden");
return win;
}
-function freePortal(aSuccess) {
- Services.obs.notifyObservers(null,
- "captive-portal-login-" + (aSuccess ? "success" : "abort"), null);
-}
-
function ensurePortalTab(win) {
// For the tests that call this function, it's enough to ensure there
// are two tabs in the window - the default tab and the portal tab.
is(win.gBrowser.tabs.length, 2,
"There should be a captive portal tab in the window.");
}
function ensurePortalNotification(win) {
@@ -119,229 +147,159 @@ function* closeWindowAndWaitForXulWindow
yield BrowserTestUtils.closeWindow(win);
yield p;
}
// Each of the test cases below is run twice: once for login-success and once
// for login-abort (aSuccess set to true and false respectively).
let testCasesForBothSuccessAndAbort = [
/**
- * A portal is detected when there's no browser window,
- * then a browser window is opened, then the portal is freed.
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
* The portal tab should be added and focused when the window is
* opened, and closed automatically when the success event is fired.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
*/
function* test_detectedWithNoBrowserWindow_Open(aSuccess) {
yield portalDetectedNoBrowserWindow();
- let win = yield openWindowAndWaitForPortalTabAndNotification();
- freePortal(aSuccess);
+ let win = yield openWindowAndWaitForPortalUI();
+ yield freePortal(aSuccess);
+ ensureNoPortalTab(win);
+ ensureNoPortalNotification(win);
+ yield closeWindowAndWaitForXulWindowVisible(win);
+ },
+
+ /**
+ * A portal is detected when there's no browser window, then a browser
+ * window is opened, then the portal is freed.
+ * The recheck triggered when the browser window is opened takes a
+ * long time. No portal tab should be added.
+ * The captive portal notification should be shown when the window is
+ * opened, and closed automatically when the success event is fired.
+ */
+ function* test_detectedWithNoBrowserWindow_LongRecheck(aSuccess) {
+ yield portalDetectedNoBrowserWindow();
+ let win = yield openWindowAndWaitForPortalUI(true);
+ yield freePortal(aSuccess);
ensureNoPortalTab(win);
ensureNoPortalNotification(win);
yield closeWindowAndWaitForXulWindowVisible(win);
},
/**
* A portal is detected when there's no browser window, and the
* portal is freed before a browser window is opened. No portal
- * tab should be added when a browser window is opened.
+ * UI should be shown when a browser window is opened.
*/
function* test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) {
yield portalDetectedNoBrowserWindow();
- freePortal(aSuccess);
+ yield freePortal(aSuccess);
let win = yield BrowserTestUtils.openNewBrowserWindow();
- // Wait for a while to make sure no tab is opened.
+ // Wait for a while to make sure no UI is shown.
yield new Promise(resolve => {
setTimeout(resolve, 1000);
});
ensureNoPortalTab(win);
ensureNoPortalNotification(win);
yield closeWindowAndWaitForXulWindowVisible(win);
},
/**
- * A portal is detected when a browser window has focus. A portal tab should be
- * opened in the background in the focused browser window. If the portal is
- * freed when the tab isn't focused, the tab should be closed automatically.
+ * A portal is detected when a browser window has focus. No portal tab should
+ * be opened. A notification bar should be displayed in the focused window.
*/
function* test_detectedWithFocus(aSuccess) {
let win = RecentWindow.getMostRecentBrowserWindow();
- let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
- Services.obs.notifyObservers(null, "captive-portal-login", null);
- let tab = yield p;
- ensurePortalTab(win);
- ensurePortalNotification(win);
- isnot(win.gBrowser.selectedTab, tab,
- "The captive portal tab should be open in the background in the current window.");
- freePortal(aSuccess);
+ yield portalDetected();
ensureNoPortalTab(win);
- ensureNoPortalNotification(win);
- },
-
- /**
- * A portal is detected when a browser window has focus. A portal tab should be
- * opened in the background in the focused browser window. If the portal is
- * freed when the tab has focus, the tab should be closed automatically.
- */
- function* test_detectedWithFocus_selectedTab(aSuccess) {
- let win = RecentWindow.getMostRecentBrowserWindow();
- let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
- Services.obs.notifyObservers(null, "captive-portal-login", null);
- let tab = yield p;
- ensurePortalTab(win);
ensurePortalNotification(win);
- isnot(win.gBrowser.selectedTab, tab,
- "The captive portal tab should be open in the background in the current window.");
- win.gBrowser.selectedTab = tab;
- freePortal(aSuccess);
- ensureNoPortalTab(win);
+ yield freePortal(aSuccess);
ensureNoPortalNotification(win);
},
];
let singleRunTestCases = [
/**
* A portal is detected when there's no browser window,
* then a browser window is opened, and the portal is logged into
* and redirects to a different page. The portal tab should be added
* and focused when the window is opened, and left open after login
* since it redirected.
*/
function* test_detectedWithNoBrowserWindow_Redirect() {
yield portalDetectedNoBrowserWindow();
- let win = yield openWindowAndWaitForPortalTabAndNotification();
+ let win = yield openWindowAndWaitForPortalUI();
let browser = win.gBrowser.selectedTab.linkedBrowser;
let loadPromise =
BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
yield loadPromise;
- freePortal(true);
+ yield freePortal(true);
ensurePortalTab(win);
ensureNoPortalNotification(win);
yield closeWindowAndWaitForXulWindowVisible(win);
},
/**
- * A portal is detected when a browser window has focus. A portal tab should be
- * opened in the background in the focused browser window. If the portal is
- * freed when the tab isn't focused, the tab should be closed automatically,
- * even if the portal has redirected to a URL other than CANONICAL_URL.
- */
- function* test_detectedWithFocus_redirectUnselectedTab() {
- let win = RecentWindow.getMostRecentBrowserWindow();
- let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
- Services.obs.notifyObservers(null, "captive-portal-login", null);
- let tab = yield p;
- ensurePortalTab(win);
- ensurePortalNotification(win);
- isnot(win.gBrowser.selectedTab, tab,
- "The captive portal tab should be open in the background in the current window.");
- let browser = tab.linkedBrowser;
- let loadPromise =
- BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
- BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
- yield loadPromise;
- freePortal(true);
- ensureNoPortalTab(win);
- ensureNoPortalNotification(win);
- },
-
- /**
- * A portal is detected when a browser window has focus. A portal tab should be
- * opened in the background in the focused browser window. If the portal is
- * freed when the tab has focus, and it has redirected to another page, the
- * tab should be kept open.
- */
- function* test_detectedWithFocus_redirectSelectedTab() {
- let win = RecentWindow.getMostRecentBrowserWindow();
- let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
- Services.obs.notifyObservers(null, "captive-portal-login", null);
- let tab = yield p;
- ensurePortalNotification(win);
- isnot(win.gBrowser.selectedTab, tab,
- "The captive portal tab should be open in the background in the current window.");
- win.gBrowser.selectedTab = tab;
- let browser = tab.linkedBrowser;
- let loadPromise =
- BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
- BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
- yield loadPromise;
- freePortal(true);
- ensurePortalTab(win);
- ensureNoPortalNotification(win);
- yield BrowserTestUtils.removeTab(tab);
- },
-
- /**
* Test the various expected behaviors of the "Show Login Page" button
* in the captive portal notification. The button should be visible for
* all tabs except the captive portal tab, and when clicked, should
* ensure a captive portal tab is open and select it.
*/
function* test_showLoginPageButton() {
let win = RecentWindow.getMostRecentBrowserWindow();
- let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
- Services.obs.notifyObservers(null, "captive-portal-login", null);
- let tab = yield p;
+ yield portalDetected();
let notification = ensurePortalNotification(win);
- isnot(win.gBrowser.selectedTab, tab,
- "The captive portal tab should be open in the background in the current window.");
testShowLoginPageButtonVisibility(notification, "visible");
function testPortalTabSelectedAndButtonNotVisible() {
is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
testShowLoginPageButtonVisibility(notification, "hidden");
}
- // Select the captive portal tab. The button should hide.
- let otherTab = win.gBrowser.selectedTab;
- win.gBrowser.selectedTab = tab;
- testShowLoginPageButtonVisibility(notification, "hidden");
+ let button = notification.querySelector("button.notification-button");
+ function* clickButtonAndExpectNewPortalTab() {
+ let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
+ button.click();
+ let tab = yield p;
+ is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
+ return tab;
+ }
- // Select the other tab. The button should become visible.
- win.gBrowser.selectedTab = otherTab;
- testShowLoginPageButtonVisibility(notification, "visible");
-
- // Simulate clicking the button. The portal tab should be selected and
- // the button should hide.
- let button = notification.querySelector("button.notification-button");
- button.click();
+ // Simulate clicking the button. The portal tab should be opened and
+ // selected and the button should hide.
+ let tab = yield clickButtonAndExpectNewPortalTab();
testPortalTabSelectedAndButtonNotVisible();
// Close the tab. The button should become visible.
yield BrowserTestUtils.removeTab(tab);
ensureNoPortalTab(win);
testShowLoginPageButtonVisibility(notification, "visible");
- function* clickButtonAndExpectNewPortalTab() {
- p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
- button.click();
- tab = yield p;
- is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
- }
-
// When the button is clicked, a new portal tab should be opened and
// selected.
- yield clickButtonAndExpectNewPortalTab();
+ tab = yield clickButtonAndExpectNewPortalTab();
// Open another arbitrary tab. The button should become visible. When it's clicked,
// the portal tab should be selected.
let anotherTab = yield BrowserTestUtils.openNewForegroundTab(win.gBrowser);
testShowLoginPageButtonVisibility(notification, "visible");
button.click();
is(win.gBrowser.selectedTab, tab, "The captive portal tab should be selected.");
// Close the portal tab and select the arbitrary tab. The button should become
// visible and when it's clicked, a new portal tab should be opened.
yield BrowserTestUtils.removeTab(tab);
win.gBrowser.selectedTab = anotherTab;
testShowLoginPageButtonVisibility(notification, "visible");
- yield clickButtonAndExpectNewPortalTab();
+ tab = yield clickButtonAndExpectNewPortalTab();
yield BrowserTestUtils.removeTab(anotherTab);
- freePortal(true);
+ yield freePortal(true);
ensureNoPortalTab(win);
ensureNoPortalNotification(win);
},
];
for (let testcase of testCasesForBothSuccessAndAbort) {
add_task(testcase.bind(null, true));
add_task(testcase.bind(null, false));