Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os != 'android'
- Manifest: browser/components/ipprotection/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { IPPL10nHelper } = ChromeUtils.importESModule(
"moz-src:///browser/components/ipprotection/IPPL10nHelper.sys.mjs"
);
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const HAS_SEEN_FEATURE_PREF = "browser.ipProtection.hasSeenFeature";
const GATE_VERSION_PREF = "browser.ipProtection.l10nGateVersion";
const CURRENT_VERSION = parseInt(AppConstants.MOZ_APP_VERSION, 10);
// A locale with a private variant subtag: no build packages coverage for it, so
// the lookup only ever sees the mock source registered below.
const LOC_COVERAGE = "de-cover";
// ipProtection.ftl: 10 messages, three untranslated (ratio 0.7, below the
// threshold used by IPPL10nHelper).
const COVERAGE_JSON = JSON.stringify({
"browser/browser/ipProtection.ftl": {
total: 10,
missing: [
"ipprotection-title",
"ipprotection-button-label",
"ipprotection-help-button-label",
],
},
});
do_get_profile();
add_setup(async function () {
// The coverage of the effective locale is cached until a live Localization
// observes an app-locale change, so this instance is kept alive on the global
// for the tasks that switch locale below.
globalThis.coverageLocalization = new Localization(
["browser/ipProtection.ftl"],
true
);
await putServerInRemoteSettings();
IPProtectionService.uninit();
IPProtectionActivator.addHelpers([IPPL10nHelper]);
IPProtectionActivator.setupHelpers();
registerCleanupFunction(async () => {
Services.prefs.clearUserPref(HAS_SEEN_FEATURE_PREF);
Services.prefs.clearUserPref(GATE_VERSION_PREF);
IPProtectionActivator.removeHelpers();
IPProtectionActivator.setupHelpers();
await IPProtectionService.init();
});
});
/**
* Sufficient coverage exposes the feature. Runs before any locale is requested,
* while the app locale is still the source locale (en-US), which counts as fully
* covered.
*/
add_task(async function test_hidesFeature_sufficient_coverage() {
Services.prefs.setBoolPref(HAS_SEEN_FEATURE_PREF, false);
Assert.equal(
IPPL10nHelper.hidesFeature(),
false,
"The feature is exposed in a locale that is localized enough"
);
Assert.ok(
!Services.prefs.getBoolPref(HAS_SEEN_FEATURE_PREF),
"Checking the coverage does not set the hasSeenFeature pref"
);
Assert.equal(
Services.prefs.getIntPref(GATE_VERSION_PREF, 0),
0,
"An exposed feature does not record a gate version"
);
Services.prefs.clearUserPref(HAS_SEEN_FEATURE_PREF);
});
/**
* hidesFeature() gates on l10n coverage only while the user has never seen the
* feature; the hasSeenFeature pref forces it on regardless.
*/
add_task(async function test_hidesFeature_gating() {
const resProto = Services.io
.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
const l10nReg = L10nRegistry.getInstance();
const root = do_get_tempdir();
root.append("ipp-l10n-coverage");
const covDir = root.clone();
covDir.append(LOC_COVERAGE);
await IOUtils.makeDirectory(covDir.path, { createAncestors: true });
await IOUtils.writeUTF8(
PathUtils.join(covDir.path, "coverage.json"),
COVERAGE_JSON
);
resProto.setSubstitution("ipp-covtest", Services.io.newFileURI(root));
l10nReg.registerSources([
L10nFileSource.createMock(
"ipp-covtest",
"app",
[LOC_COVERAGE],
[]
),
]);
const origAvailable = Services.locale.availableLocales;
const origRequested = Services.locale.requestedLocales;
registerCleanupFunction(() => {
Services.locale.availableLocales = origAvailable;
Services.locale.requestedLocales = origRequested;
l10nReg.removeSources(["ipp-covtest"]);
resProto.setSubstitution("ipp-covtest", null);
});
Services.locale.availableLocales = [LOC_COVERAGE, "en-US"];
Services.locale.requestedLocales = [LOC_COVERAGE];
Services.prefs.setBoolPref(HAS_SEEN_FEATURE_PREF, false);
Services.prefs.clearUserPref(GATE_VERSION_PREF);
Assert.equal(
IPPL10nHelper.hidesFeature(),
true,
"A user who never saw the feature does not see it when the UI " +
"is not localized enough (coverage 0.7)"
);
Assert.ok(
!Services.prefs.getBoolPref(HAS_SEEN_FEATURE_PREF),
"Coverage below the threshold does not set the hasSeenFeature pref"
);
Assert.equal(
Services.prefs.getIntPref(GATE_VERSION_PREF, 0),
CURRENT_VERSION,
"Hiding the feature records the current major version"
);
Services.prefs.setBoolPref(HAS_SEEN_FEATURE_PREF, true);
Assert.equal(
IPPL10nHelper.hidesFeature(),
false,
"The hasSeenFeature pref forces the feature on regardless of coverage"
);
Services.prefs.setBoolPref(HAS_SEEN_FEATURE_PREF, false);
Assert.equal(
IPPL10nHelper.hidesFeature(),
true,
"Clearing the hasSeenFeature pref brings the gate back"
);
Services.prefs.clearUserPref(HAS_SEEN_FEATURE_PREF);
Services.prefs.clearUserPref(GATE_VERSION_PREF);
});
/**
* The gate only holds for the major version that recorded it: a Firefox update
* exposes the feature even when the coverage is still below the threshold.
*/
add_task(async function test_gate_expires_on_update() {
Services.prefs.setBoolPref(HAS_SEEN_FEATURE_PREF, false);
Services.prefs.setIntPref(GATE_VERSION_PREF, CURRENT_VERSION - 1);
Assert.equal(
IPPL10nHelper.hidesFeature(),
false,
"A gate recorded by a previous major version does not hide the feature"
);
Assert.equal(
Services.prefs.getIntPref(GATE_VERSION_PREF),
CURRENT_VERSION - 1,
"An expired gate keeps its recorded version"
);
Services.prefs.setIntPref(GATE_VERSION_PREF, CURRENT_VERSION);
Assert.equal(
IPPL10nHelper.hidesFeature(),
true,
"A gate recorded by this major version still hides the feature"
);
Services.prefs.clearUserPref(HAS_SEEN_FEATURE_PREF);
Services.prefs.clearUserPref(GATE_VERSION_PREF);
});
/**
* An otherwise-ready service is UNAVAILABLE when the helper vetoes exposure,
* and recovers once it stops.
*/
add_task(async function test_state_gated_on_coverage() {
let sandbox = sinon.createSandbox();
let hidden = true;
sandbox.stub(IPPL10nHelper, "hidesFeature").callsFake(() => hidden);
IPPDummyAuthProvider.simulateSignIn(true);
IPPDummyAuthProvider.setGetEntitlementResponse({
entitlement: createTestEntitlement(),
});
await IPProtectionService.init();
Assert.equal(
IPProtectionService.state,
IPProtectionStates.UNAVAILABLE,
"Service is unavailable when the helper hides the feature"
);
hidden = false;
IPProtectionService.updateState();
Assert.equal(
IPProtectionService.state,
IPProtectionStates.READY,
"Service becomes ready once the helper stops hiding the feature"
);
IPProtectionService.uninit();
sandbox.restore();
});
/**
* The hasSeenFeature pref is pinned as soon as the state machine exposes the
* UI, which includes UNAUTHENTICATED: the login panel is part of the feature.
*/
add_task(async function test_exposing_the_feature_pins_the_pref() {
Services.prefs.setBoolPref(HAS_SEEN_FEATURE_PREF, false);
let sandbox = sinon.createSandbox();
let hidden = true;
sandbox.stub(IPPL10nHelper, "hidesFeature").callsFake(() => hidden);
IPPDummyAuthProvider.simulateSignIn(false);
IPPDummyAuthProvider.setGetEntitlementResponse({});
await IPProtectionService.init();
Assert.equal(
IPProtectionService.state,
IPProtectionStates.UNAVAILABLE,
"Service is unavailable while the feature is gated"
);
Assert.ok(
!Services.prefs.getBoolPref(HAS_SEEN_FEATURE_PREF, false),
"A gated feature does not pin the hasSeenFeature pref"
);
hidden = false;
IPProtectionService.updateState();
Assert.equal(
IPProtectionService.state,
IPProtectionStates.UNAUTHENTICATED,
"Service shows the login UI once the feature is not gated"
);
Assert.ok(
Services.prefs.getBoolPref(HAS_SEEN_FEATURE_PREF, false),
"Exposing the UI pins the hasSeenFeature pref"
);
IPProtectionService.uninit();
sandbox.restore();
Services.prefs.clearUserPref(HAS_SEEN_FEATURE_PREF);
});