"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
const { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
const { sinon } = ChromeUtils.importESModule(
"resource://testing-common/Sinon.sys.mjs"
);
ChromeUtils.defineESModuleGetters(
this, {
Region:
"resource://gre/modules/Region.sys.mjs",
RegionTestUtils:
"resource://testing-common/RegionTestUtils.sys.mjs",
});
const INTERVAL_PREF =
"browser.region.update.interval";
const RESPONSE_DELAY =
500;
const RESPONSE_TIMEOUT =
100;
const histogram = Services.telemetry.getHistogramById(
"SEARCH_SERVICE_COUNTRY_FETCH_RESULT"
);
add_setup(async () => {
Services.prefs.setBoolPref(
"browser.region.log",
true);
Services.prefs.setBoolPref(
"network.dns.disableIPv6",
true);
registerCleanupFunction(async () => {
Services.prefs.clearUserPref(
"network.dns.disableIPv6");
});
});
// Region.sys.mjs will call init() on being loaded and set a background
// task to fetch the region, ensure we have completed this before
// running the rest of the tests.
add_task(async
function test_startup_with_no_pref() {
Services.fog.testResetFOG();
RegionTestUtils.setNetworkRegion(
"AT");
// Region.sys.mjs is lazily loaded, so referencing `Region.` here will load
// it and automatically call the `Region.init()` function.
await checkTelemetry(Region.TELEMETRY.SUCCESS);
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"AT",
"Should have correctly stored the region in home region after getting it"
);
await cleanup();
});
add_task(async
function test_startup_with_pref() {
Services.fog.testResetFOG();
Services.prefs.setCharPref(
"browser.search.region",
"GB");
// If we failed to read the pref, we'd kick off a network connection. So set
// up the network to return a different region here so that we'd be able to
// detect that case.
RegionTestUtils.setNetworkRegion(
"DE");
// Use a different instance of region for testing this.
let region = Region.newInstance();
await region.init();
Assert.equal(region.home,
"GB",
"Should have loaded the correct region");
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"GB",
"Should have correctly stored the region in home region after getting it"
);
});
add_task(async
function test_basic() {
Services.fog.testResetFOG();
let srv = useHttpServer(RegionTestUtils.REGION_URL_PREF);
srv.registerPathHandler(
"/", (req, res) => {
res.setStatusLine(
"1.1",
200,
"OK");
send(res, { country_code:
"UK" });
});
// start to listen the notification
let updateRegion = TestUtils.topicObserved(
"browser-region-updated");
await Region._fetchRegion();
let [subject] = await updateRegion;
Assert.ok(
true,
"Region fetch should succeed");
Assert.equal(Region.home,
"UK",
"Region fetch should return correct result");
Assert.equal(
subject,
Region.home,
"Notification should be sent with the correct region"
);
assertStoredResultTelemetry({ restOfWorld:
1 });
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"UK",
"Should have correctly set the region in home region after getting it"
);
await cleanup(srv);
});
add_task(async
function test_invalid_url() {
histogram.clear();
Services.prefs.setIntPref(
"browser.region.retry-timeout",
0);
Services.prefs.setCharPref(
RegionTestUtils.REGION_URL_PREF,
"http://localhost:0"
);
await Region._fetchRegion();
await checkTelemetry(Region.TELEMETRY.NO_RESULT);
});
add_task(async
function test_invalid_json() {
histogram.clear();
Services.prefs.setCharPref(
RegionTestUtils.REGION_URL_PREF,
'data:application/json,{"country_code"'
);
await Region._fetchRegion();
await checkTelemetry(Region.TELEMETRY.NO_RESULT);
});
add_task(async
function test_timeout() {
histogram.clear();
Services.prefs.setIntPref(
"browser.region.retry-timeout",
0);
Services.prefs.setIntPref(
"browser.region.timeout", RESPONSE_TIMEOUT);
let srv = useHttpServer(RegionTestUtils.REGION_URL_PREF);
srv.registerPathHandler(
"/", (req, res) => {
res.processAsync();
do_timeout(RESPONSE_DELAY, () => {
send(res, { country_code:
"UK" });
res.finish();
});
});
await Region._fetchRegion();
await checkTelemetry(Region.TELEMETRY.TIMEOUT);
await cleanup(srv);
});
add_task(async
function test_location() {
let location = { location: { lat: -
1, lng:
1 }, accuracy:
100 };
let srv = useHttpServer(
"geo.provider.network.url");
srv.registerPathHandler(
"/", (req, res) => {
res.setStatusLine(
"1.1",
200,
"OK");
send(res, location);
});
let result = await Region._getLocation();
Assert.ok(
true,
"Region fetch should succeed");
Assert.deepEqual(result, location,
"Location is returned");
await cleanup(srv);
});
add_task(async
function test_update() {
RegionTestUtils.setNetworkRegion(
"FR");
await Region._fetchRegion();
Assert.equal(Region.home,
"FR",
"Should have correct region");
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"FR",
"Should have correctly set the region in home region after getting it"
);
RegionTestUtils.setNetworkRegion(
"DE");
await Region._fetchRegion();
Assert.equal(Region.home,
"FR",
"Shouldnt have changed yet");
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"FR",
"Should not have updated the home region telemetry yet"
);
// The first fetchRegion will set the prefs to determine when
// to update the home region, we need to do 2 fetchRegions to test
// it isnt updating when it shouldnt.
await Region._fetchRegion();
Assert.equal(Region.home,
"FR",
"Shouldnt have changed yet again");
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"FR",
"Should not have updated the home region telemetry yet (again)"
);
Services.prefs.setIntPref(INTERVAL_PREF,
1);
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await
new Promise(resolve => setTimeout(resolve,
1100));
await Region._fetchRegion();
Assert.equal(Region.home,
"DE",
"Should have updated now");
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"DE",
"Should have correctly set the home region telemetry"
);
await cleanup();
});
add_task(async
function test_update_us() {
Services.fog.testResetFOG();
// Setting the region to US whilst within a US timezone should work.
let stub = sinon.stub(Region,
"_isUSTimezone").returns(
true);
Region._setHomeRegion(
null,
false);
RegionTestUtils.setNetworkRegion(
"US");
await Region._fetchRegion();
assertStoredResultTelemetry({ unitedStates:
1 });
Assert.equal(Region.home,
"US",
"Should have correct region");
Assert.equal(
Glean.region.homeRegion.testGetValue(),
"US",
"Should have correctly set the home region telemetry"
);
Services.fog.testResetFOG();
// Setting the region to US whilst not within a US timezone should not work.
stub.returns(
false);
Region._setHomeRegion(
null,
false);
RegionTestUtils.setNetworkRegion(
"US");
await Region._fetchRegion();
assertStoredResultTelemetry({ ignoredUnitedStates:
1 });
Assert.equal(Region.home,
null,
"Should have not set the region");
sinon.restore();
});
add_task(async
function test_max_retry() {
Region._setHomeRegion(
null,
false);
let requestsSeen =
0;
Services.prefs.setIntPref(
"browser.region.retry-timeout", RESPONSE_TIMEOUT);
Services.prefs.setIntPref(
"browser.region.timeout", RESPONSE_TIMEOUT);
let srv = useHttpServer(RegionTestUtils.REGION_URL_PREF);
srv.registerPathHandler(
"/", (req, res) => {
requestsSeen++;
res.setStatusLine(
"1.1",
200,
"OK");
res.processAsync();
do_timeout(RESPONSE_DELAY, res.finish.bind(res));
});
Region._fetchRegion();
await TestUtils.waitForCondition(() => requestsSeen ===
3);
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await
new Promise(resolve => setTimeout(resolve, RESPONSE_DELAY));
Assert.equal(Region.home,
null,
"failed to fetch region");
Assert.equal(requestsSeen,
3,
"Retried 4 times");
Region._retryCount =
0;
await cleanup(srv);
});
add_task(async
function test_retry() {
Region._setHomeRegion(
null,
false);
let requestsSeen =
0;
Services.prefs.setIntPref(
"browser.region.retry-timeout", RESPONSE_TIMEOUT);
Services.prefs.setIntPref(
"browser.region.timeout", RESPONSE_TIMEOUT);
let srv = useHttpServer(RegionTestUtils.REGION_URL_PREF);
srv.registerPathHandler(
"/", (req, res) => {
res.setStatusLine(
"1.1",
200,
"OK");
if (++requestsSeen ==
2) {
res.setStatusLine(
"1.1",
200,
"OK");
send(res, { country_code:
"UK" });
}
else {
res.processAsync();
do_timeout(RESPONSE_DELAY, res.finish.bind(res));
}
});
Region._fetchRegion();
await TestUtils.waitForCondition(() => requestsSeen ===
2);
/* eslint-disable mozilla/no-arbitrary-setTimeout */
await
new Promise(resolve => setTimeout(resolve, RESPONSE_DELAY));
Assert.equal(Region.home,
"UK",
"failed to fetch region");
Assert.equal(requestsSeen,
2,
"Retried 2 times");
await cleanup(srv);
});
add_task(async
function test_timerManager() {
RegionTestUtils.setNetworkRegion(
"FR");
// Ensure the home region updates immediately, but the update
// check will only happen once per second.
Services.prefs.setIntPref(
"browser.region.update.interval",
0);
Services.prefs.setIntPref(
"browser.region.update.debounce",
1);
let region = Region.newInstance();
await region.init();
Assert.equal(region.home,
"FR",
"Should have correct initial region");
// Updates are being debounced, these should be ignored.
RegionTestUtils.setNetworkRegion(
"DE");
await region._updateTimer();
await region._updateTimer();
Assert.equal(region.home,
"FR",
"Ignored updates to region");
// Set the debounce interval to 0 so these updates are used.
Services.prefs.setIntPref(
"browser.region.update.debounce",
0);
RegionTestUtils.setNetworkRegion(
"AU");
await region._updateTimer();
await region._updateTimer();
Assert.equal(region.home,
"AU",
"region has been updated");
await cleanup();
});
function useHttpServer(pref) {
let server =
new HttpServer();
server.start(-
1);
Services.prefs.setCharPref(
pref,
`http:
//localhost:${server.identity.primaryPort}/`
);
return server;
}
function send(res, json) {
res.setStatusLine(
"1.1",
200,
"OK");
res.setHeader(
"content-type",
"application/json",
true);
res.write(JSON.stringify(json));
}
async
function cleanup(srv =
null) {
Services.prefs.clearUserPref(
"browser.search.region");
Region._setHomeRegion(
null,
false);
if (srv) {
await
new Promise(r => srv.stop(r));
}
}
async
function checkTelemetry(aExpectedValue) {
// Wait until there is 1 result.
await TestUtils.waitForCondition(() => {
let snapshot = histogram.snapshot();
return Object.values(snapshot.values).reduce((a, b) => a + b,
0) ==
1;
});
let snapshot = histogram.snapshot();
Assert.equal(snapshot.values[aExpectedValue],
1);
}
function assertStoredResultTelemetry({
restOfWorld =
null,
unitedStates =
null,
ignoredUnitedStates =
null,
}) {
Assert.equal(
Glean.region.storeRegionResult.ignoredUnitedStatesIncorrectTimezone.testGetValue
(),
ignoredUnitedStates,
"Should have the expected count for ignoring setting the region when US but not in the correct timezone"
);
Assert.equal(
Glean.region.storeRegionResult.setForUnitedStates.testGetValue(),
unitedStates,
"Should have the expected count for setting the region for the US"
);
Assert.equal(
Glean.region.storeRegionResult.setForRestOfWorld.testGetValue(),
restOfWorld,
"Should have the expected count for setting the region for the rest of the world"
);
}