зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1313568 - Handle captive portal UI in per-window script. r=MattN
MozReview-Commit-ID: FxjE2NblJe4 --HG-- rename : browser/modules/CaptivePortalWatcher.jsm => browser/base/content/browser-captivePortal.js extra : rebase_source : c6c1321b591fdbd870ff40374477bbc05fbcb27c
This commit is contained in:
Родитель
ef758dcbf4
Коммит
f2ba2936d1
|
@ -0,0 +1,259 @@
|
||||||
|
/* 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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._showNotification();
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
};
|
|
@ -1009,6 +1009,7 @@ var gBrowserInit = {
|
||||||
AboutPrivateBrowsingListener.init();
|
AboutPrivateBrowsingListener.init();
|
||||||
TrackingProtection.init();
|
TrackingProtection.init();
|
||||||
RefreshBlocker.init();
|
RefreshBlocker.init();
|
||||||
|
CaptivePortalWatcher.init();
|
||||||
|
|
||||||
let mm = window.getGroupMessageManager("browsers");
|
let mm = window.getGroupMessageManager("browsers");
|
||||||
mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
|
mm.loadFrameScript("chrome://browser/content/tab-content.js", true);
|
||||||
|
@ -1536,6 +1537,8 @@ var gBrowserInit = {
|
||||||
|
|
||||||
RefreshBlocker.uninit();
|
RefreshBlocker.uninit();
|
||||||
|
|
||||||
|
CaptivePortalWatcher.uninit();
|
||||||
|
|
||||||
gMenuButtonUpdateBadge.uninit();
|
gMenuButtonUpdateBadge.uninit();
|
||||||
|
|
||||||
gMenuButtonBadgeManager.uninit();
|
gMenuButtonBadgeManager.uninit();
|
||||||
|
@ -2852,7 +2855,7 @@ var BrowserOnClick = {
|
||||||
msg.data.securityInfoAsString);
|
msg.data.securityInfoAsString);
|
||||||
break;
|
break;
|
||||||
case "Browser:OpenCaptivePortalPage":
|
case "Browser:OpenCaptivePortalPage":
|
||||||
this.onOpenCaptivePortalPage();
|
CaptivePortalWatcher.ensureCaptivePortalTab();
|
||||||
break;
|
break;
|
||||||
case "Browser:SiteBlockedError":
|
case "Browser:SiteBlockedError":
|
||||||
this.onAboutBlocked(msg.data.elementId, msg.data.reason,
|
this.onAboutBlocked(msg.data.elementId, msg.data.reason,
|
||||||
|
@ -2988,28 +2991,6 @@ var BrowserOnClick = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenCaptivePortalPage() {
|
|
||||||
// Open a new tab with the canonical URL that we use to check for a captive portal.
|
|
||||||
// It will be redirected to the login page.
|
|
||||||
let canonicalURL = Services.prefs.getCharPref("captivedetect.canonicalURL");
|
|
||||||
let tab = gBrowser.addTab(canonicalURL);
|
|
||||||
let canonicalURI = makeURI(canonicalURL);
|
|
||||||
gBrowser.selectedTab = tab;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
},
|
|
||||||
|
|
||||||
onAboutBlocked(elementId, reason, isTopFrame, location) {
|
onAboutBlocked(elementId, reason, isTopFrame, location) {
|
||||||
// Depending on what page we are displaying here (malware/phishing/unwanted)
|
// Depending on what page we are displaying here (malware/phishing/unwanted)
|
||||||
// use the right strings and links for each.
|
// use the right strings and links for each.
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
|
<script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
|
||||||
|
|
||||||
<script type="application/javascript" src="chrome://browser/content/browser-addons.js"/>
|
<script type="application/javascript" src="chrome://browser/content/browser-addons.js"/>
|
||||||
|
<script type="application/javascript" src="chrome://browser/content/browser-captivePortal.js"/>
|
||||||
<script type="application/javascript" src="chrome://browser/content/browser-ctrlTab.js"/>
|
<script type="application/javascript" src="chrome://browser/content/browser-ctrlTab.js"/>
|
||||||
<script type="application/javascript" src="chrome://browser/content/browser-customization.js"/>
|
<script type="application/javascript" src="chrome://browser/content/browser-customization.js"/>
|
||||||
<script type="application/javascript" src="chrome://browser/content/browser-compacttheme.js"/>
|
<script type="application/javascript" src="chrome://browser/content/browser-compacttheme.js"/>
|
||||||
|
|
|
@ -52,6 +52,19 @@ add_task(function* checkCaptivePortalCertErrorUI() {
|
||||||
let portalTab = yield portalTabPromise;
|
let portalTab = yield portalTabPromise;
|
||||||
is(gBrowser.selectedTab, portalTab, "Login page should be open in a new foreground tab.");
|
is(gBrowser.selectedTab, portalTab, "Login page should be open in a new foreground tab.");
|
||||||
|
|
||||||
|
// Make sure clicking the "Open Login Page" button again focuses the existing portal tab.
|
||||||
|
yield BrowserTestUtils.switchTab(gBrowser, errorTab);
|
||||||
|
// Passing an empty function to BrowserTestUtils.switchTab lets us wait for an arbitrary
|
||||||
|
// tab switch.
|
||||||
|
portalTabPromise = BrowserTestUtils.switchTab(gBrowser, () => {});
|
||||||
|
yield ContentTask.spawn(browser, null, () => {
|
||||||
|
info("Clicking the Open Login Page button.");
|
||||||
|
content.document.getElementById("openPortalLoginPageButton").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
let portalTab2 = yield portalTabPromise;
|
||||||
|
is(portalTab2, portalTab, "The existing portal tab should be focused.");
|
||||||
|
|
||||||
let portalTabRemoved = BrowserTestUtils.removeTab(portalTab, {dontRemove: true});
|
let portalTabRemoved = BrowserTestUtils.removeTab(portalTab, {dontRemove: true});
|
||||||
let errorTabReloaded = waitForCertErrorLoad(browser);
|
let errorTabReloaded = waitForCertErrorLoad(browser);
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ browser.jar:
|
||||||
content/browser/browser.js (content/browser.js)
|
content/browser/browser.js (content/browser.js)
|
||||||
* content/browser/browser.xul (content/browser.xul)
|
* content/browser/browser.xul (content/browser.xul)
|
||||||
content/browser/browser-addons.js (content/browser-addons.js)
|
content/browser/browser-addons.js (content/browser-addons.js)
|
||||||
|
content/browser/browser-captivePortal.js (content/browser-captivePortal.js)
|
||||||
content/browser/browser-ctrlTab.js (content/browser-ctrlTab.js)
|
content/browser/browser-ctrlTab.js (content/browser-ctrlTab.js)
|
||||||
content/browser/browser-customization.js (content/browser-customization.js)
|
content/browser/browser-customization.js (content/browser-customization.js)
|
||||||
content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
|
content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
|
||||||
|
|
|
@ -205,15 +205,20 @@ let testCasesForBothSuccessAndAbort = [
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A portal is detected when a browser window has focus. No portal tab should
|
* 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.
|
* be opened. A notification bar should be displayed in all browser windows.
|
||||||
*/
|
*/
|
||||||
function* test_detectedWithFocus(aSuccess) {
|
function* test_detectedWithFocus(aSuccess) {
|
||||||
let win = RecentWindow.getMostRecentBrowserWindow();
|
let win1 = RecentWindow.getMostRecentBrowserWindow();
|
||||||
|
let win2 = yield BrowserTestUtils.openNewBrowserWindow();
|
||||||
yield portalDetected();
|
yield portalDetected();
|
||||||
ensureNoPortalTab(win);
|
ensureNoPortalTab(win1);
|
||||||
ensurePortalNotification(win);
|
ensureNoPortalTab(win2);
|
||||||
|
ensurePortalNotification(win1);
|
||||||
|
ensurePortalNotification(win2);
|
||||||
yield freePortal(aSuccess);
|
yield freePortal(aSuccess);
|
||||||
ensureNoPortalNotification(win);
|
ensureNoPortalNotification(win1);
|
||||||
|
ensureNoPortalNotification(win2);
|
||||||
|
yield closeWindowAndWaitForXulWindowVisible(win2);
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче