Bug 1627555 - Update region if in new location for a length of time. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D79272
This commit is contained in:
Dale Harvey 2020-06-25 22:20:21 +00:00
parent 7c995807da
commit ad16313f52
14 changed files with 150 additions and 40 deletions

View File

@ -52,14 +52,14 @@ add_task(async function() {
); );
// Set language back to en-US // Set language back to en-US
Services.prefs.setCharPref("intl.accept_languages", "en-US"); Services.prefs.setCharPref("intl.accept_languages", "en-US");
Region._setRegion("US", false); Region._setHomeRegion("US", false);
await reloadTab(tab); await reloadTab(tab);
await checkProxyCardVisibility(tab, false); await checkProxyCardVisibility(tab, false);
info( info(
"Check that secure proxy card is hidden if user's location is not in the US." "Check that secure proxy card is hidden if user's location is not in the US."
); );
Region._setRegion("CA", false); Region._setHomeRegion("CA", false);
await reloadTab(tab); await reloadTab(tab);
await checkProxyCardVisibility(tab, true); await checkProxyCardVisibility(tab, true);
@ -67,7 +67,7 @@ add_task(async function() {
"Check that secure proxy card is hidden if the extension is already installed." "Check that secure proxy card is hidden if the extension is already installed."
); );
// Make sure we set the region back to "US" // Make sure we set the region back to "US"
Region._setRegion("US", false); Region._setHomeRegion("US", false);
const id = "secure-proxy@mozilla.com"; const id = "secure-proxy@mozilla.com";
const extension = ExtensionTestUtils.loadExtension({ const extension = ExtensionTestUtils.loadExtension({
manifest: { manifest: {

View File

@ -29,7 +29,7 @@ add_task(async function test_cancelEditAddressDialogWithESC() {
}); });
add_task(async function test_defaultCountry() { add_task(async function test_defaultCountry() {
Region._setRegion("CA", false); Region._setHomeRegion("CA", false);
await testDialog(EDIT_ADDRESS_DIALOG_URL, win => { await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
let doc = win.document; let doc = win.document;
is( is(
@ -39,7 +39,7 @@ add_task(async function test_defaultCountry() {
); );
doc.querySelector("#cancel").click(); doc.querySelector("#cancel").click();
}); });
Region._setRegion("DE", false); Region._setHomeRegion("DE", false);
await testDialog(EDIT_ADDRESS_DIALOG_URL, win => { await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
let doc = win.document; let doc = win.document;
is( is(
@ -50,13 +50,13 @@ add_task(async function test_defaultCountry() {
doc.querySelector("#cancel").click(); doc.querySelector("#cancel").click();
}); });
// Test unsupported country // Test unsupported country
Region._setRegion("XX", false); Region._setHomeRegion("XX", false);
await testDialog(EDIT_ADDRESS_DIALOG_URL, win => { await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
let doc = win.document; let doc = win.document;
is(doc.querySelector("#country").value, "", "Default country set to empty"); is(doc.querySelector("#country").value, "", "Default country set to empty");
doc.querySelector("#cancel").click(); doc.querySelector("#cancel").click();
}); });
Region._setRegion("US", false); Region._setHomeRegion("US", false);
}); });
add_task(async function test_saveAddress() { add_task(async function test_saveAddress() {
@ -864,7 +864,7 @@ add_task(async function test_hiddenFieldRemovedWhenCountryChanged() {
}); });
add_task(async function test_countrySpecificFieldsGetRequiredness() { add_task(async function test_countrySpecificFieldsGetRequiredness() {
Region._setRegion("RO", false); Region._setHomeRegion("RO", false);
await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => { await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
let doc = win.document; let doc = win.document;
is( is(

View File

@ -3889,6 +3889,10 @@ pref("browser.region.network.scan", false);
// Timeout for whole region request. // Timeout for whole region request.
pref("browser.region.timeout", 5000); pref("browser.region.timeout", 5000);
#ifdef EARLY_BETA_OR_EARLIER
pref("browser.region.update.enabled", true);
#endif
// Enable/Disable the device storage API for content // Enable/Disable the device storage API for content
pref("device.storage.enabled", false); pref("device.storage.enabled", false);

View File

@ -215,7 +215,7 @@ class SearchConfigTest {
* The two-letter locale code. * The two-letter locale code.
*/ */
async _reinit(region, locale) { async _reinit(region, locale) {
Region._setRegion(region?.toUpperCase(), true); Region._setHomeRegion(region?.toUpperCase(), true);
if (region) { if (region) {
await SearchTestUtils.promiseSearchNotification("engines-reloaded"); await SearchTestUtils.promiseSearchNotification("engines-reloaded");
} }

View File

@ -50,7 +50,7 @@ add_task(async function should_get_geo_defaults_only_once() {
await withGeoServer(async function cont(requests) { await withGeoServer(async function cont(requests) {
// (Re)initializing the search service should trigger a request, // (Re)initializing the search service should trigger a request,
// and set the default engine based on it. // and set the default engine based on it.
Region._setRegion("FR", false); Region._setHomeRegion("FR", false);
await Promise.all([asyncReInitReloaded(), promiseAfterCache()]); await Promise.all([asyncReInitReloaded(), promiseAfterCache()]);
Assert.equal((await Services.search.getDefault()).name, kTestEngineName); Assert.equal((await Services.search.getDefault()).name, kTestEngineName);

View File

@ -14,7 +14,7 @@ add_task(async function setup() {
add_task(async function test_regular_init() { add_task(async function test_regular_init() {
await withGeoServer( await withGeoServer(
async function cont(requests) { async function cont(requests) {
Region._setRegion("us", false); Region._setHomeRegion("us", false);
await Services.search.init(); await Services.search.init();
await promiseAfterCache(); await promiseAfterCache();
@ -30,7 +30,7 @@ add_task(async function test_regular_init() {
let enginesReloaded = SearchTestUtils.promiseSearchNotification( let enginesReloaded = SearchTestUtils.promiseSearchNotification(
"engines-reloaded" "engines-reloaded"
); );
Region._setRegion("FR", true); Region._setHomeRegion("FR");
await promiseAfterCache(); await promiseAfterCache();
await enginesReloaded; await enginesReloaded;

View File

@ -16,7 +16,7 @@ add_task(async function setup() {
SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault", SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
true true
); );
Region._setRegion("US", false); Region._setHomeRegion("US", false);
}); });
add_task(async function test_listJSONlocale() { add_task(async function test_listJSONlocale() {
@ -82,7 +82,7 @@ add_task(async function test_listJSONlocaleSwitch() {
// Check that region overrides apply // Check that region overrides apply
add_task(async function test_listJSONRegionOverride() { add_task(async function test_listJSONRegionOverride() {
Region._setRegion("RU", false); Region._setHomeRegion("RU", false);
await asyncReInit(); await asyncReInit();

View File

@ -23,7 +23,7 @@ add_task(async function test_maybereloadengine_update() {
let enginesReloaded = SearchTestUtils.promiseSearchNotification( let enginesReloaded = SearchTestUtils.promiseSearchNotification(
"engines-reloaded" "engines-reloaded"
); );
Region._setRegion("FR", true); Region._setHomeRegion("FR", true);
await enginesReloaded; await enginesReloaded;
Assert.equal( Assert.equal(

View File

@ -23,7 +23,7 @@ add_task(async function setup() {
add_task(async function test_maybereloadengine_update_distro() { add_task(async function test_maybereloadengine_update_distro() {
await Services.search.init(); await Services.search.init();
Region._setRegion("FR", true); Region._setHomeRegion("FR", true);
await SearchTestUtils.promiseSearchNotification("engines-reloaded"); await SearchTestUtils.promiseSearchNotification("engines-reloaded");
let defaultEngine = await Services.search.getDefault(); let defaultEngine = await Services.search.getDefault();

View File

@ -8,7 +8,7 @@
"use strict"; "use strict";
add_task(async function setup() { add_task(async function setup() {
Region._setRegion("an", false); Region._setHomeRegion("an", false);
await AddonTestUtils.promiseStartupManager(); await AddonTestUtils.promiseStartupManager();
await useTestEngines("test-extensions"); await useTestEngines("test-extensions");
}); });
@ -48,7 +48,7 @@ add_task(async function test_changeRegion() {
let reInitPromise = SearchTestUtils.promiseSearchNotification( let reInitPromise = SearchTestUtils.promiseSearchNotification(
"reinit-complete" "reinit-complete"
); );
Region._setRegion("tr", false); Region._setHomeRegion("tr", false);
Services.search.reInit(); Services.search.reInit();
await reInitPromise; await reInitPromise;

View File

@ -159,7 +159,7 @@ add_task(async function test_init_with_slow_region_lookup() {
`http://localhost:${srv.identity.primaryPort}/fetch_region` `http://localhost:${srv.identity.primaryPort}/fetch_region`
); );
Region._setRegion("", false); Region._setHomeRegion("", false);
Region.init(); Region.init();
// Kick off a re-init. // Kick off a re-init.

View File

@ -65,7 +65,7 @@ add_task(async function basic_install_test() {
add_task(async function basic_multilocale_test() { add_task(async function basic_multilocale_test() {
await forceExpiration(); await forceExpiration();
Region._setRegion("an", false); Region._setHomeRegion("an");
await withGeoServer( await withGeoServer(
async function cont(requests) { async function cont(requests) {
@ -82,7 +82,7 @@ add_task(async function basic_multilocale_test() {
add_task(async function complex_multilocale_test() { add_task(async function complex_multilocale_test() {
await forceExpiration(); await forceExpiration();
Region._setRegion("af", false); Region._setHomeRegion("af", false);
await withGeoServer( await withGeoServer(
async function cont(requests) { async function cont(requests) {
@ -99,7 +99,7 @@ add_task(async function complex_multilocale_test() {
}); });
add_task(async function test_manifest_selection() { add_task(async function test_manifest_selection() {
await forceExpiration(); await forceExpiration();
Region._setRegion("an", false); Region._setHomeRegion("an", false);
Services.locale.availableLocales = ["af"]; Services.locale.availableLocales = ["af"];
Services.locale.requestedLocales = ["af"]; Services.locale.requestedLocales = ["af"];

View File

@ -44,6 +44,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
false false
); );
XPCOMUtils.defineLazyPreferenceGetter(
this,
"cacheBustEnabled",
"browser.region.update.enabled",
false
);
XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter(
this, this,
"localGeocodingEnabled", "localGeocodingEnabled",
@ -59,16 +66,28 @@ const log = console.createInstance({
const REGION_PREF = "browser.search.region"; const REGION_PREF = "browser.search.region";
const COLLECTION_ID = "regions"; const COLLECTION_ID = "regions";
// Prefix for all the region updating related preferences.
const UPDATE_PREFIX = "browser.region.update";
// The amount of time (in seconds) we need to be in a new
// location before we update the home region.
// Currently set to 2 weeks.
const UPDATE_INTERVAL = 60 * 60 * 24 * 14;
/** /**
* This module keeps track of the users current region (country). * This module keeps track of the users current region (country).
* so the SearchService and other consumers can apply region * so the SearchService and other consumers can apply region
* specific customisations. * specific customisations.
*/ */
class RegionDetector { class RegionDetector {
// The users home location.
_home = null;
// The most recent location the user was detected.
_current = null;
// The RemoteSettings client used to sync region files. // The RemoteSettings client used to sync region files.
_rsClient = null; _rsClient = null;
// Keep track of the wifi data across listener events. // Keep track of the wifi data across listener events.
wifiDataPromise = null; _wifiDataPromise = null;
// Topic for Observer events fired by Region.jsm. // Topic for Observer events fired by Region.jsm.
REGION_TOPIC = "browser-region"; REGION_TOPIC = "browser-region";
// Verb for event fired when we update the region. // Verb for event fired when we update the region.
@ -86,8 +105,8 @@ class RegionDetector {
* region detection. * region detection.
*/ */
init() { init() {
let region = Services.prefs.getCharPref(REGION_PREF, null); this._home = Services.prefs.getCharPref(REGION_PREF, null);
if (!region) { if (cacheBustEnabled || !this._home) {
Services.tm.idleDispatchToMainThread(this._fetchRegion.bind(this)); Services.tm.idleDispatchToMainThread(this._fetchRegion.bind(this));
} }
if (localGeocodingEnabled) { if (localGeocodingEnabled) {
@ -95,7 +114,6 @@ class RegionDetector {
this._setupRemoteSettings.bind(this) this._setupRemoteSettings.bind(this)
); );
} }
this._home = region;
} }
/** /**
@ -108,6 +126,16 @@ class RegionDetector {
return this._home; return this._home;
} }
/**
* Get the last region we detected the user to be in.
*
* @returns {string}
* The users current region.
*/
get current() {
return this._current;
}
/** /**
* Fetch the users current region. * Fetch the users current region.
* *
@ -154,8 +182,7 @@ class RegionDetector {
// the value. This works because no region defaults to // the value. This works because no region defaults to
// ZZ (unknown) in nsURLFormatter // ZZ (unknown) in nsURLFormatter
if (region != "US" || isTimezoneUS) { if (region != "US" || isTimezoneUS) {
log.info("Saving home region:", region); this._setCurrentRegion(region, true);
this._setRegion(region, true);
} }
// and telemetry... // and telemetry...
@ -211,12 +238,64 @@ class RegionDetector {
} }
/** /**
* Save the updated region and notify observers. * Save the update current region and check if the home region
* also needs an update.
* *
* @param region * @param {string} region
* The region to store. * The region to store.
*/ */
_setRegion(region = "", notify = false) { _setCurrentRegion(region = "") {
log.info("Setting current region:", region);
this._current = region;
let prefs = Services.prefs;
// Interval is in seconds.
let interval = prefs.getIntPref(
`${UPDATE_PREFIX}.interval`,
UPDATE_INTERVAL
);
let seenRegion = prefs.getCharPref(`${UPDATE_PREFIX}.region`, null);
let firstSeen = prefs.getIntPref(`${UPDATE_PREFIX}.first-seen`, 0);
// If we don't have a value for .home we can set it immediately.
if (!this._home) {
this._setHomeRegion(region);
} else if (region != this._home && region != seenRegion) {
// If we are in a different region than what is currently
// considered home, then keep track of when we first
// seen the new location.
prefs.setCharPref(`${UPDATE_PREFIX}.region`, region);
prefs.setIntPref(
`${UPDATE_PREFIX}.first-seen`,
Math.round(Date.now() / 1000)
);
} else if (region != this._home && region == seenRegion) {
// If we have been in the new region for longer than
// a specified time period, then set that as the new home.
if (Math.round(Date.now() / 1000) >= firstSeen + interval) {
this._setHomeRegion(region);
}
} else {
// If we are at home again, stop tracking the seen region.
prefs.clearUserPref(`${UPDATE_PREFIX}.region`);
prefs.clearUserPref(`${UPDATE_PREFIX}.first-seen`);
}
}
/**
* Save the updated home region and notify observers.
*
* @param {string} region
* The region to store.
* @param {boolean} [notify]
* Tests can disable the notification for convenience as it
* may trigger an engines reload.
*/
_setHomeRegion(region, notify = true) {
if (region == this._home) {
return;
}
log.info("Updating home region:", region);
this._home = region; this._home = region;
Services.prefs.setCharPref("browser.search.region", region); Services.prefs.setCharPref("browser.search.region", region);
if (notify) { if (notify) {
@ -567,13 +646,13 @@ class RegionDetector {
this.wifiService.startWatching(this); this.wifiService.startWatching(this);
return new Promise(resolve => { return new Promise(resolve => {
this.wifiDataPromise = resolve; this._wifiDataPromise = resolve;
}); });
} }
onChange(accessPoints) { onChange(accessPoints) {
log.info("onChange called"); log.info("onChange called");
if (!accessPoints || !this.wifiDataPromise) { if (!accessPoints || !this._wifiDataPromise) {
return; return;
} }
@ -582,10 +661,10 @@ class RegionDetector {
this.wifiService = null; this.wifiService = null;
} }
if (this.wifiDataPromise) { if (this._wifiDataPromise) {
let data = LocationHelper.formatWifiAccessPoints(accessPoints); let data = LocationHelper.formatWifiAccessPoints(accessPoints);
this.wifiDataPromise(data); this._wifiDataPromise(data);
this.wifiDataPromise = null; this._wifiDataPromise = null;
} }
} }
} }

View File

@ -5,11 +5,13 @@ const { AppConstants } = ChromeUtils.import(
); );
const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
const { Region } = ChromeUtils.import("resource://gre/modules/Region.jsm"); const { Region } = ChromeUtils.import("resource://gre/modules/Region.jsm");
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
const { TestUtils } = ChromeUtils.import( const { TestUtils } = ChromeUtils.import(
"resource://testing-common/TestUtils.jsm" "resource://testing-common/TestUtils.jsm"
); );
const REGION_PREF = "browser.region.network.url"; const REGION_PREF = "browser.region.network.url";
const INTERVAL_PREF = "browser.region.update.interval";
const RESPONSE_DELAY = 500; const RESPONSE_DELAY = 500;
const RESPONSE_TIMEOUT = 100; const RESPONSE_TIMEOUT = 100;
@ -82,11 +84,9 @@ add_task(async function test_mismatched_probe() {
probeHistogram.clear(); probeHistogram.clear();
} }
histogram.clear(); histogram.clear();
Region._home = null;
Services.prefs.setCharPref( setNetworkRegion("AU");
REGION_PREF,
'data:application/json,{"country_code": "AU"}'
);
await Region._fetchRegion(); await Region._fetchRegion();
Assert.equal(Region.home, "AU", "Should have correct region"); Assert.equal(Region.home, "AU", "Should have correct region");
checkTelemetry(Region.TELEMETRY.SUCCESS); checkTelemetry(Region.TELEMETRY.SUCCESS);
@ -116,6 +116,33 @@ add_task(async function test_location() {
await new Promise(r => srv.stop(r)); await new Promise(r => srv.stop(r));
}); });
add_task(async function test_update() {
Region._home = null;
setNetworkRegion("FR");
await Region._fetchRegion();
Assert.equal(Region.home, "FR", "Should have correct region");
setNetworkRegion("DE");
await Region._fetchRegion();
Assert.equal(Region.home, "FR", "Shouldnt have changed yet");
// Thie 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");
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");
});
function setNetworkRegion(region) {
Services.prefs.setCharPref(
REGION_PREF,
`data:application/json,{"country_code": "${region}"}`
);
}
function useHttpServer(pref) { function useHttpServer(pref) {
let server = new HttpServer(); let server = new HttpServer();
server.start(-1); server.start(-1);