mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
Bug 989194 - Show captive portal notification bar when detected. r=MattN
MozReview-Commit-ID: KFvtTCBpMeS --HG-- extra : rebase_source : b52a42f1ecd3202165da13afedc38a4835ebff42
This commit is contained in:
parent
2670da22bf
commit
2275539222
@ -737,6 +737,15 @@ decoder.noHWAccelerationVista.message = To improve video quality, you may need t
|
||||
decoder.noPulseAudio.message = To play audio, you may need to install the required PulseAudio software.
|
||||
decoder.unsupportedLibavcodec.message = libavcodec may be vulnerable or is not supported, and should be updated to play video.
|
||||
|
||||
# LOCALIZATION NOTE (captivePortal.infoMessage):
|
||||
# This string is shown in a notification bar when we detect a captive portal is blocking network access
|
||||
# and requires the user to log in before browsing. %1$S is replaced with brandShortName.
|
||||
captivePortal.infoMessage=This network may require you to login to use the internet. %1$S has opened the login page for you.
|
||||
# LOCALIZATION NOTE (captivePortal.showLoginPage):
|
||||
# The label for a button shown in the info bar in all tabs except the login page tab.
|
||||
# The button shows the portal login page tab when clicked.
|
||||
captivePortal.showLoginPage=Show Login Page
|
||||
|
||||
permissions.remove.tooltip = Clear this permission and ask again
|
||||
|
||||
# LOCALIZATION NOTE (aboutDialog.architecture.*):
|
||||
|
@ -12,6 +12,9 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
*/
|
||||
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");
|
||||
@ -28,6 +31,9 @@ this.CaptivePortalWatcher = {
|
||||
// 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,
|
||||
|
||||
/**
|
||||
@ -50,7 +56,7 @@ this.CaptivePortalWatcher = {
|
||||
|
||||
if (cps.state == cps.LOCKED_PORTAL) {
|
||||
// A captive portal has already been detected.
|
||||
this._addCaptivePortalTab();
|
||||
this._captivePortalDetected();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,7 +75,7 @@ this.CaptivePortalWatcher = {
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "captive-portal-login":
|
||||
this._addCaptivePortalTab();
|
||||
this._captivePortalDetected();
|
||||
break;
|
||||
case "captive-portal-login-abort":
|
||||
case "captive-portal-login-success":
|
||||
@ -81,7 +87,7 @@ this.CaptivePortalWatcher = {
|
||||
}
|
||||
},
|
||||
|
||||
_addCaptivePortalTab() {
|
||||
_captivePortalDetected() {
|
||||
if (this._waitingToAddTab) {
|
||||
return;
|
||||
}
|
||||
@ -97,10 +103,25 @@ this.CaptivePortalWatcher = {
|
||||
return;
|
||||
}
|
||||
|
||||
// The browser is in use - add the tab without selecting it.
|
||||
let tab = win.gBrowser.addTab(this.canonicalURL);
|
||||
// The browser is in use - show a notification and add the tab without
|
||||
// selecting it, unless the caller specifically requested selection.
|
||||
this._ensureCaptivePortalTab(win);
|
||||
this._showNotification(win);
|
||||
},
|
||||
|
||||
_ensureCaptivePortalTab(win) {
|
||||
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 = win.gBrowser.addTab(this.canonicalURL);
|
||||
}
|
||||
|
||||
this._captivePortalTab = Cu.getWeakReference(tab);
|
||||
return;
|
||||
return tab;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -135,7 +156,9 @@ this.CaptivePortalWatcher = {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = win.gBrowser.addTab(this.canonicalURL);
|
||||
this._showNotification(win);
|
||||
let tab = this._ensureCaptivePortalTab(win);
|
||||
|
||||
// Focus the tab only if the recheck has completed, i.e. we're sure
|
||||
// that the portal is still locked. This way, if the recheck completes
|
||||
// after we add the tab and we're free of the portal, the tab contents
|
||||
@ -143,8 +166,6 @@ this.CaptivePortalWatcher = {
|
||||
if (cps.lastChecked != lastChecked) {
|
||||
win.gBrowser.selectedTab = tab;
|
||||
}
|
||||
|
||||
this._captivePortalTab = Cu.getWeakReference(tab);
|
||||
}, PORTAL_RECHECK_DELAY_MS);
|
||||
},
|
||||
|
||||
@ -154,6 +175,8 @@ this.CaptivePortalWatcher = {
|
||||
this._waitingToAddTab = false;
|
||||
}
|
||||
|
||||
this._removeNotification();
|
||||
|
||||
if (!this._captivePortalTab) {
|
||||
return;
|
||||
}
|
||||
@ -181,4 +204,81 @@ this.CaptivePortalWatcher = {
|
||||
// Remove the tab.
|
||||
tabbrowser.removeTab(tab);
|
||||
},
|
||||
|
||||
get _productName() {
|
||||
delete this._productName;
|
||||
return this._productName =
|
||||
Services.strings.createBundle("chrome://branding/locale/brand.properties")
|
||||
.GetStringFromName("brandShortName");
|
||||
},
|
||||
|
||||
get _browserBundle() {
|
||||
delete this._browserBundle;
|
||||
return this._browserBundle =
|
||||
Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
},
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (aEvent.type != "TabSelect" || !this._captivePortalTab || !this._captivePortalNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tab = this._captivePortalTab.get();
|
||||
let n = this._captivePortalNotification.get();
|
||||
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(win) {
|
||||
let buttons = [
|
||||
{
|
||||
label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage"),
|
||||
callback: () => {
|
||||
win.gBrowser.selectedTab = this._ensureCaptivePortalTab(win);
|
||||
|
||||
// Returning true prevents the notification from closing.
|
||||
return true;
|
||||
},
|
||||
isDefault: true,
|
||||
},
|
||||
];
|
||||
|
||||
let message = this._browserBundle.formatStringFromName("captivePortal.infoMessage",
|
||||
[this._productName], 1);
|
||||
|
||||
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, "",
|
||||
nb.PRIORITY_INFO_MEDIUM, buttons, closeHandler);
|
||||
|
||||
this._captivePortalNotification = Cu.getWeakReference(n);
|
||||
|
||||
win.gBrowser.tabContainer.addEventListener("TabSelect", this);
|
||||
},
|
||||
|
||||
_removeNotification() {
|
||||
if (!this._captivePortalNotification)
|
||||
return;
|
||||
let n = this._captivePortalNotification.get();
|
||||
this._captivePortalNotification = null;
|
||||
if (!n || !n.parentNode) {
|
||||
return;
|
||||
}
|
||||
n.close();
|
||||
},
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ Components.utils.import("resource:///modules/RecentWindow.jsm");
|
||||
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({
|
||||
@ -25,11 +26,15 @@ function* portalDetectedNoBrowserWindow() {
|
||||
RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
|
||||
}
|
||||
|
||||
function* openWindowAndWaitForPortalTab() {
|
||||
function* openWindowAndWaitForPortalTabAndNotification() {
|
||||
let win = yield BrowserTestUtils.openNewBrowserWindow();
|
||||
let tab = yield BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
|
||||
let [notification, tab] = yield Promise.all([
|
||||
BrowserTestUtils.waitForGlobalNotificationBar(win, PORTAL_NOTIFICATION_VALUE),
|
||||
BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL)
|
||||
]);
|
||||
is(win.gBrowser.selectedTab, tab,
|
||||
"The captive portal tab should be open and selected in the new window.");
|
||||
testShowLoginPageButtonVisibility(notification, "hidden");
|
||||
return win;
|
||||
}
|
||||
|
||||
@ -38,6 +43,44 @@ function freePortal(aSuccess) {
|
||||
"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) {
|
||||
let notificationBox =
|
||||
win.document.getElementById("high-priority-global-notificationbox");
|
||||
let notification = notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE)
|
||||
isnot(notification, null,
|
||||
"There should be a captive portal notification in the window.");
|
||||
return notification;
|
||||
}
|
||||
|
||||
// Helper to test whether the "Show Login Page" is visible in the captive portal
|
||||
// notification (it should be hidden when the portal tab is selected).
|
||||
function testShowLoginPageButtonVisibility(notification, visibility) {
|
||||
let showLoginPageButton = notification.querySelector("button.notification-button");
|
||||
// If the visibility property was never changed from default, it will be
|
||||
// an empty string, so we pretend it's "visible" (effectively the same).
|
||||
is(showLoginPageButton.style.visibility || "visible", visibility,
|
||||
"The \"Show Login Page\" button should be " + visibility + ".");
|
||||
}
|
||||
|
||||
function ensureNoPortalTab(win) {
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"There should be no captive portal tab in the window.");
|
||||
}
|
||||
|
||||
function ensureNoPortalNotification(win) {
|
||||
let notificationBox =
|
||||
win.document.getElementById("high-priority-global-notificationbox");
|
||||
is(notificationBox.getNotificationWithValue(PORTAL_NOTIFICATION_VALUE), null,
|
||||
"There should be no captive portal notification in the window.");
|
||||
}
|
||||
|
||||
// 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 = [
|
||||
@ -49,10 +92,10 @@ let testCasesForBothSuccessAndAbort = [
|
||||
*/
|
||||
function* test_detectedWithNoBrowserWindow_Open(aSuccess) {
|
||||
yield portalDetectedNoBrowserWindow();
|
||||
let win = yield openWindowAndWaitForPortalTab();
|
||||
let win = yield openWindowAndWaitForPortalTabAndNotification();
|
||||
freePortal(aSuccess);
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"The captive portal tab should have been closed.");
|
||||
ensureNoPortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
},
|
||||
|
||||
@ -69,8 +112,8 @@ let testCasesForBothSuccessAndAbort = [
|
||||
yield new Promise(resolve => {
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"No captive portal tab should have been opened.");
|
||||
ensureNoPortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
},
|
||||
|
||||
@ -84,11 +127,13 @@ let testCasesForBothSuccessAndAbort = [
|
||||
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);
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"The portal tab should have been closed.");
|
||||
ensureNoPortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -101,12 +146,14 @@ let testCasesForBothSuccessAndAbort = [
|
||||
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);
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"The portal tab should have been closed.");
|
||||
ensureNoPortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
},
|
||||
];
|
||||
|
||||
@ -120,15 +167,15 @@ let singleRunTestCases = [
|
||||
*/
|
||||
function* test_detectedWithNoBrowserWindow_Redirect() {
|
||||
yield portalDetectedNoBrowserWindow();
|
||||
let win = yield openWindowAndWaitForPortalTab();
|
||||
let win = yield openWindowAndWaitForPortalTabAndNotification();
|
||||
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);
|
||||
is(win.gBrowser.tabs.length, 2,
|
||||
"The captive portal tab should not have been closed.");
|
||||
ensurePortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
},
|
||||
|
||||
@ -143,6 +190,8 @@ let singleRunTestCases = [
|
||||
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;
|
||||
@ -151,8 +200,8 @@ let singleRunTestCases = [
|
||||
BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
|
||||
yield loadPromise;
|
||||
freePortal(true);
|
||||
is(win.gBrowser.tabs.length, 1,
|
||||
"The portal tab should have been closed.");
|
||||
ensureNoPortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -166,6 +215,7 @@ let singleRunTestCases = [
|
||||
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;
|
||||
@ -175,10 +225,82 @@ let singleRunTestCases = [
|
||||
BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
|
||||
yield loadPromise;
|
||||
freePortal(true);
|
||||
is(win.gBrowser.tabs.length, 2,
|
||||
"The portal tab should not have been closed.");
|
||||
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;
|
||||
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");
|
||||
|
||||
// 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();
|
||||
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();
|
||||
|
||||
// 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();
|
||||
|
||||
yield BrowserTestUtils.removeTab(anotherTab);
|
||||
freePortal(true);
|
||||
ensureNoPortalTab(win);
|
||||
ensureNoPortalNotification(win);
|
||||
},
|
||||
];
|
||||
|
||||
for (let testcase of testCasesForBothSuccessAndAbort) {
|
||||
|
@ -1202,6 +1202,31 @@ this.BrowserTestUtils = {
|
||||
*/
|
||||
waitForNotificationBar(tabbrowser, browser, notificationValue) {
|
||||
let notificationBox = tabbrowser.getNotificationBox(browser);
|
||||
return this.waitForNotificationInNotificationBox(notificationBox,
|
||||
notificationValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for a <xul:notification> with a particular value to appear
|
||||
* in the global <xul:notificationbox> of the given browser window.
|
||||
*
|
||||
* @param win (<xul:window>)
|
||||
* The browser window in whose global notificationbox the
|
||||
* notification is expected to appear.
|
||||
* @param notificationValue (string)
|
||||
* The "value" of the notification, which is often used as
|
||||
* a unique identifier. Example: "captive-portal-detected".
|
||||
* @return Promise
|
||||
* Resolves to the <xul:notification> that is being shown.
|
||||
*/
|
||||
waitForGlobalNotificationBar(win, notificationValue) {
|
||||
let notificationBox =
|
||||
win.document.getElementById("high-priority-global-notificationbox");
|
||||
return this.waitForNotificationInNotificationBox(notificationBox,
|
||||
notificationValue);
|
||||
},
|
||||
|
||||
waitForNotificationInNotificationBox(notificationBox, notificationValue) {
|
||||
return new Promise((resolve) => {
|
||||
let check = (event) => {
|
||||
return event.target.value == notificationValue;
|
||||
|
Loading…
Reference in New Issue
Block a user