Bug 1748550 support persisted events for captivePortal r=rpl,robwu

Differential Revision: https://phabricator.services.mozilla.com/D136299
This commit is contained in:
Shane Caraveo 2022-02-26 20:42:59 +00:00
parent 8d79179a4c
commit d854f45476
2 changed files with 193 additions and 75 deletions

View File

@ -27,83 +27,126 @@ var { getSettingsAPI } = ExtensionPreferencesManager;
const CAPTIVE_URL_PREF = "captivedetect.canonicalURL"; const CAPTIVE_URL_PREF = "captivedetect.canonicalURL";
function nameForCPSState(state) {
switch (state) {
case gCPS.UNKNOWN:
return "unknown";
case gCPS.NOT_CAPTIVE:
return "not_captive";
case gCPS.UNLOCKED_PORTAL:
return "unlocked_portal";
case gCPS.LOCKED_PORTAL:
return "locked_portal";
default:
return "unknown";
}
}
var { ExtensionError } = ExtensionUtils; var { ExtensionError } = ExtensionUtils;
this.captivePortal = class extends ExtensionAPI { this.captivePortal = class extends ExtensionAPI {
getAPI(context) { checkCaptivePortalEnabled() {
function checkEnabled() { if (!gCaptivePortalEnabled) {
if (!gCaptivePortalEnabled) { throw new ExtensionError("Captive Portal detection is not enabled");
throw new ExtensionError("Captive Portal detection is not enabled");
}
} }
}
nameForCPSState(state) {
switch (state) {
case gCPS.UNKNOWN:
return "unknown";
case gCPS.NOT_CAPTIVE:
return "not_captive";
case gCPS.UNLOCKED_PORTAL:
return "unlocked_portal";
case gCPS.LOCKED_PORTAL:
return "locked_portal";
default:
return "unknown";
}
}
PERSISTENT_EVENTS = {
onStateChanged: fire => {
this.checkCaptivePortalEnabled();
let observer = (subject, topic) => {
fire.async({ state: this.nameForCPSState(gCPS.state) });
};
Services.obs.addObserver(
observer,
"ipc:network:captive-portal-set-state"
);
return {
unregister: () => {
Services.obs.removeObserver(
observer,
"ipc:network:captive-portal-set-state"
);
},
convert(_fire, context) {
fire = _fire;
},
};
},
onConnectivityAvailable: fire => {
this.checkCaptivePortalEnabled();
let observer = (subject, topic, data) => {
fire.async({ status: data });
};
Services.obs.addObserver(observer, "network:captive-portal-connectivity");
return {
unregister: () => {
Services.obs.removeObserver(
observer,
"network:captive-portal-connectivity"
);
},
convert(_fire, context) {
fire = _fire;
},
};
},
"captiveURL.onChange": fire => {
let listener = (text, id) => {
fire.async({
levelOfControl: "not_controllable",
value: Services.prefs.getStringPref(CAPTIVE_URL_PREF),
});
};
Services.prefs.addObserver(CAPTIVE_URL_PREF, listener);
return {
unregister: () => {
Services.prefs.removeObserver(CAPTIVE_URL_PREF, listener);
},
convert(_fire, context) {
fire = _fire;
},
};
},
};
primeListener(extension, event, fire) {
if (Object.hasOwn(this.PERSISTENT_EVENTS, event)) {
return this.PERSISTENT_EVENTS[event](fire);
}
}
getAPI(context) {
let self = this;
return { return {
captivePortal: { captivePortal: {
getState() { getState() {
checkEnabled(); self.checkCaptivePortalEnabled();
return nameForCPSState(gCPS.state); return self.nameForCPSState(gCPS.state);
}, },
getLastChecked() { getLastChecked() {
checkEnabled(); self.checkCaptivePortalEnabled();
return gCPS.lastChecked; return gCPS.lastChecked;
}, },
onStateChanged: new EventManager({ onStateChanged: new EventManager({
context, context,
name: "captivePortal.onStateChanged", module: "captivePortal",
event: "onStateChanged",
register: fire => { register: fire => {
checkEnabled(); return self.PERSISTENT_EVENTS.onStateChanged(fire).unregister;
let observer = (subject, topic) => {
fire.async({ state: nameForCPSState(gCPS.state) });
};
Services.obs.addObserver(
observer,
"ipc:network:captive-portal-set-state"
);
return () => {
Services.obs.removeObserver(
observer,
"ipc:network:captive-portal-set-state"
);
};
}, },
}).api(), }).api(),
onConnectivityAvailable: new EventManager({ onConnectivityAvailable: new EventManager({
context, context,
name: "captivePortal.onConnectivityAvailable", module: "captivePortal",
event: "onConnectivityAvailable",
register: fire => { register: fire => {
checkEnabled(); return self.PERSISTENT_EVENTS.onConnectivityAvailable(fire)
.unregister;
let observer = (subject, topic, data) => {
fire.async({ status: data });
};
Services.obs.addObserver(
observer,
"network:captive-portal-connectivity"
);
return () => {
Services.obs.removeObserver(
observer,
"network:captive-portal-connectivity"
);
};
}, },
}).api(), }).api(),
canonicalURL: getSettingsAPI({ canonicalURL: getSettingsAPI({
@ -115,18 +158,11 @@ this.captivePortal = class extends ExtensionAPI {
readOnly: true, readOnly: true,
onChange: new ExtensionCommon.EventManager({ onChange: new ExtensionCommon.EventManager({
context, context,
name: "captiveURL.onChange", module: "captivePortal",
event: "captiveURL.onChange",
register: fire => { register: fire => {
let listener = (text, id) => { return self.PERSISTENT_EVENTS["captiveURL.onChange"](fire)
fire.async({ .unregister;
levelOfControl: "not_controllable",
value: Services.prefs.getStringPref(CAPTIVE_URL_PREF),
});
};
Services.prefs.addObserver(CAPTIVE_URL_PREF, listener);
return () => {
Services.prefs.removeObserver(CAPTIVE_URL_PREF, listener);
};
}, },
}).api(), }).api(),
}), }),

View File

@ -1,5 +1,19 @@
"use strict"; "use strict";
Services.prefs.setBoolPref(
"extensions.webextensions.background-delayed-startup",
true
);
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"43"
);
/** /**
* This duplicates the test from netwerk/test/unit/test_captive_portal_service.js * This duplicates the test from netwerk/test/unit/test_captive_portal_service.js
* however using an extension to gather the captive portal information. * however using an extension to gather the captive portal information.
@ -30,7 +44,7 @@ registerCleanupFunction(() => {
Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST); Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST);
}); });
add_task(function setup() { add_task(async function setup() {
Services.prefs.setCharPref( Services.prefs.setCharPref(
PREF_CAPTIVE_ENDPOINT, PREF_CAPTIVE_ENDPOINT,
`http://localhost:${httpserver.identity.primaryPort}/captive.txt` `http://localhost:${httpserver.identity.primaryPort}/captive.txt`
@ -38,6 +52,9 @@ add_task(function setup() {
Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true); Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true);
Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 0); Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 0);
Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true); Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true);
Services.prefs.setBoolPref("extensions.eventPages.enabled", true);
await AddonTestUtils.promiseStartupManager();
}); });
add_task(async function test_captivePortal_basic() { add_task(async function test_captivePortal_basic() {
@ -46,8 +63,10 @@ add_task(async function test_captivePortal_basic() {
); );
let extension = ExtensionTestUtils.loadExtension({ let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: { manifest: {
permissions: ["captivePortal"], permissions: ["captivePortal"],
background: { persistent: false },
}, },
isPrivileged: true, isPrivileged: true,
async background() { async background() {
@ -63,6 +82,10 @@ add_task(async function test_captivePortal_basic() {
browser.test.sendMessage("state", details); browser.test.sendMessage("state", details);
}); });
browser.captivePortal.canonicalURL.onChange.addListener(details => {
browser.test.sendMessage("url", details);
});
browser.test.onMessage.addListener(async msg => { browser.test.onMessage.addListener(async msg => {
if (msg == "getstate") { if (msg == "getstate") {
browser.test.sendMessage( browser.test.sendMessage(
@ -71,21 +94,20 @@ add_task(async function test_captivePortal_basic() {
); );
} }
}); });
browser.test.assertEq(
"unknown",
await browser.captivePortal.getState(),
"initial state unknown"
);
}, },
}); });
await extension.startup(); await extension.startup();
extension.sendMessage("getstate");
let details = await extension.awaitMessage("getstate");
equal(details, "unknown", "initial state");
// The captive portal service is started by nsIOService when the pref becomes true, so we // The captive portal service is started by nsIOService when the pref becomes true, so we
// toggle the pref. We cannot set to false before the extension loads above. // toggle the pref. We cannot set to false before the extension loads above.
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true); Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
let details = await extension.awaitMessage("connectivity"); details = await extension.awaitMessage("connectivity");
equal(details.status, "clear", "initial connectivity"); equal(details.status, "clear", "initial connectivity");
extension.sendMessage("getstate"); extension.sendMessage("getstate");
details = await extension.awaitMessage("getstate"); details = await extension.awaitMessage("getstate");
@ -106,5 +128,65 @@ add_task(async function test_captivePortal_basic() {
details = await extension.awaitMessage("state"); details = await extension.awaitMessage("state");
equal(details.state, "unlocked_portal", "state after unlocking portal"); equal(details.state, "unlocked_portal", "state after unlocking portal");
assertPersistentListeners(
extension,
"captivePortal",
"onConnectivityAvailable",
{
primed: false,
}
);
assertPersistentListeners(extension, "captivePortal", "onStateChanged", {
primed: false,
});
assertPersistentListeners(extension, "captivePortal", "captiveURL.onChange", {
primed: false,
});
info("Test event page terminate/waken");
await extension.terminateBackground();
assertPersistentListeners(extension, "captivePortal", "onStateChanged", {
primed: true,
});
assertPersistentListeners(
extension,
"captivePortal",
"onConnectivityAvailable",
{
primed: true,
}
);
assertPersistentListeners(extension, "captivePortal", "captiveURL.onChange", {
primed: true,
});
info("REFRESH 2nd pass to other");
cpResponse = "other";
cps.recheckCaptivePortal();
details = await extension.awaitMessage("state");
equal(details.state, "locked_portal", "state in portal");
info("Test event page terminate/waken with settings");
await extension.terminateBackground();
assertPersistentListeners(extension, "captivePortal", "captiveURL.onChange", {
primed: true,
});
Services.prefs.setStringPref(
"captivedetect.canonicalURL",
"http://example.com"
);
let url = await extension.awaitMessage("url");
equal(
url.value,
"http://example.com",
"The canonicalURL setting has the expected value."
);
await extension.unload(); await extension.unload();
}); });