mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-21 09:49:14 +00:00
Bug 1175218 - The original default engine should be set per region rather than per locale, r=markh.
--HG-- extra : rebase_source : 98eed0fe583dfaef7b83eccba4534b6e46c5accf
This commit is contained in:
parent
6938d4eca7
commit
af833ef045
@ -404,8 +404,13 @@ pref("browser.search.order.1", "chrome://browser-region/locale/re
|
||||
pref("browser.search.order.2", "chrome://browser-region/locale/region.properties");
|
||||
pref("browser.search.order.3", "chrome://browser-region/locale/region.properties");
|
||||
|
||||
// Market-specific search defaults (US market only)
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
// Market-specific search defaults
|
||||
// This is disabled globally, and then enabled for individual locales
|
||||
// in firefox-l10n.js (eg. it's enabled for en-US).
|
||||
pref("browser.search.geoSpecificDefaults", false);
|
||||
pref("browser.search.geoSpecificDefaults.url", "https://search.services.mozilla.com/1/%APP%/%VERSION%/%CHANNEL%/%LOCALE%/%REGION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%");
|
||||
|
||||
// US specific default (used as a fallback if the geoSpecificDefaults request fails).
|
||||
pref("browser.search.defaultenginename.US", "data:text/plain,browser.search.defaultenginename.US=Yahoo");
|
||||
pref("browser.search.order.US.1", "data:text/plain,browser.search.order.US.1=Yahoo");
|
||||
pref("browser.search.order.US.2", "data:text/plain,browser.search.order.US.2=Google");
|
||||
|
@ -53,8 +53,10 @@ function test() {
|
||||
ok(data.days.hasDay(now), "Have data for today.");
|
||||
let day = data.days.getDay(now);
|
||||
|
||||
// Will need to be changed if Yahoo isn't the default search engine.
|
||||
let defaultProviderID = "yahoo";
|
||||
// Will need to be changed if Google isn't the default search engine.
|
||||
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
|
||||
// non-US en-US default.
|
||||
let defaultProviderID = "google";
|
||||
let field = defaultProviderID + ".contextmenu";
|
||||
ok(day.has(field), "Have search recorded for context menu.");
|
||||
|
||||
@ -66,4 +68,3 @@ function test() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,10 @@ add_task(function* test_healthreport_search_recording() {
|
||||
let now = new Date();
|
||||
let oldCount = 0;
|
||||
|
||||
// This will to be need changed if default search engine is not Yahoo.
|
||||
let defaultEngineID = "yahoo";
|
||||
// This will to be need changed if default search engine is not Google.
|
||||
// Note: geoSpecificDefaults are disabled for mochitests, so this is the
|
||||
// non-US en-US default.
|
||||
let defaultEngineID = "google";
|
||||
|
||||
let field = defaultEngineID + ".urlbar";
|
||||
|
||||
|
@ -4,4 +4,8 @@
|
||||
|
||||
#filter substitution
|
||||
|
||||
# LOCALIZATION NOTE: this preference is set to true for en-US specifically,
|
||||
# locales without this line have the setting set to false by default.
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
|
||||
pref("general.useragent.locale", "@AB_CD@");
|
||||
|
@ -265,8 +265,13 @@ pref("browser.search.order.1", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.2", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.3", "chrome://browser/locale/region.properties");
|
||||
|
||||
// Market-specific search defaults (US market only)
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
// Market-specific search defaults
|
||||
// This is disabled globally, and then enabled for individual locales
|
||||
// in firefox-l10n.js (eg. it's enabled for en-US).
|
||||
pref("browser.search.geoSpecificDefaults", false);
|
||||
pref("browser.search.geoSpecificDefaults.url", "https://search.services.mozilla.com/1/%APP%/%VERSION%/%CHANNEL%/%LOCALE%/%REGION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%");
|
||||
|
||||
// US specific default (used as a fallback if the geoSpecificDefaults request fails).
|
||||
pref("browser.search.defaultenginename.US", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.US.1", "chrome://browser/locale/region.properties");
|
||||
pref("browser.search.order.US.2", "chrome://browser/locale/region.properties");
|
||||
|
@ -4,4 +4,8 @@
|
||||
|
||||
#filter substitution
|
||||
|
||||
# LOCALIZATION NOTE: this preference is set to true for en-US specifically,
|
||||
# locales without this line have the setting set to false by default.
|
||||
pref("browser.search.geoSpecificDefaults", true);
|
||||
|
||||
pref("general.useragent.locale", "@AB_CD@");
|
||||
|
@ -290,6 +290,8 @@ user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
|
||||
// side-effect of preventing our geoip lookup.
|
||||
user_pref("browser.search.isUS", true);
|
||||
user_pref("browser.search.countryCode", "US");
|
||||
// This will prevent HTTP requests for region defaults.
|
||||
user_pref("browser.search.geoSpecificDefaults", false);
|
||||
|
||||
// Make sure the self support tab doesn't hit the network.
|
||||
user_pref("browser.selfsupport.url", "https://%(server)s/selfsupport-dummy/");
|
||||
|
@ -9,6 +9,7 @@ function run_test() {
|
||||
// desired side-effect of preventing our geoip lookup.
|
||||
Services.prefs.setBoolPref("browser.search.isUS", true);
|
||||
Services.prefs.setCharPref("browser.search.countryCode", "US");
|
||||
Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
|
@ -210,6 +210,11 @@ var OS_UNSUPPORTED_PARAMS = [
|
||||
// specifies an updateURL, but not an updateInterval.
|
||||
const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;
|
||||
|
||||
// The default interval before checking again for the name of the
|
||||
// default engine for the region, in seconds. Only used if the response
|
||||
// from the server doesn't specify an interval.
|
||||
const SEARCH_GEO_DEFAULT_UPDATE_INTERVAL = 2592000; // 30 days.
|
||||
|
||||
// Returns false for whitespace-only or commented out lines in a
|
||||
// Sherlock file, true otherwise.
|
||||
function isUsefulLine(aLine) {
|
||||
@ -408,20 +413,21 @@ loadListener.prototype = {
|
||||
onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
|
||||
}
|
||||
|
||||
// Method to determine if we should be using geo-specific defaults
|
||||
function geoSpecificDefaultsEnabled() {
|
||||
// check to see if this is a partner build. Partner builds should not use geo-specific defaults.
|
||||
let distroID;
|
||||
function isPartnerBuild() {
|
||||
try {
|
||||
distroID = Services.prefs.getCharPref("distribution.id");
|
||||
let distroID = Services.prefs.getCharPref("distribution.id");
|
||||
|
||||
// Mozilla-provided builds (i.e. funnelcake) are not partner builds
|
||||
if (distroID && !distroID.startsWith("mozilla")) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
// if we make it here, the pref should dictate behaviour
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method to determine if we should be using geo-specific defaults
|
||||
function geoSpecificDefaultsEnabled() {
|
||||
let geoSpecificDefaults = false;
|
||||
try {
|
||||
geoSpecificDefaults = Services.prefs.getBoolPref("browser.search.geoSpecificDefaults");
|
||||
@ -500,7 +506,7 @@ function getIsUS() {
|
||||
|
||||
// Helper method to modify preference keys with geo-specific modifiers, if needed.
|
||||
function getGeoSpecificPrefName(basepref) {
|
||||
if (!geoSpecificDefaultsEnabled())
|
||||
if (!geoSpecificDefaultsEnabled() || isPartnerBuild())
|
||||
return basepref;
|
||||
if (getIsUS())
|
||||
return basepref + ".US";
|
||||
@ -532,16 +538,53 @@ function isUSTimezone() {
|
||||
// If it fails we don't touch that pref so isUS() does its normal thing.
|
||||
let ensureKnownCountryCode = Task.async(function* () {
|
||||
// If we have a country-code already stored in our prefs we trust it.
|
||||
let countryCode;
|
||||
try {
|
||||
Services.prefs.getCharPref("browser.search.countryCode");
|
||||
return; // pref exists, so we've done this before.
|
||||
countryCode = Services.prefs.getCharPref("browser.search.countryCode");
|
||||
} catch(e) {}
|
||||
// We don't have it cached, so fetch it. fetchCountryCode() will call
|
||||
// storeCountryCode if it gets a result (even if that happens after the
|
||||
// promise resolves)
|
||||
yield fetchCountryCode();
|
||||
|
||||
if (!countryCode) {
|
||||
// We don't have it cached, so fetch it. fetchCountryCode() will call
|
||||
// storeCountryCode if it gets a result (even if that happens after the
|
||||
// promise resolves) and fetchRegionDefault.
|
||||
yield fetchCountryCode();
|
||||
} else {
|
||||
// if nothing to do, return early.
|
||||
if (!geoSpecificDefaultsEnabled())
|
||||
return;
|
||||
|
||||
let expir = engineMetadataService.getGlobalAttr("searchDefaultExpir") || 0;
|
||||
if (expir > Date.now()) {
|
||||
// The territory default we have already fetched hasn't expired yet.
|
||||
// If we have an engine saved, the hash should be valid, verify it now.
|
||||
let defaultEngine = engineMetadataService.getGlobalAttr("searchDefault");
|
||||
if (!defaultEngine ||
|
||||
engineMetadataService.getGlobalAttr("searchDefaultHash") == getVerificationHash(defaultEngine)) {
|
||||
// No geo default, or valid hash; nothing to do.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
yield new Promise(resolve => {
|
||||
let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
|
||||
let timerId = setTimeout(() => {
|
||||
timerId = null;
|
||||
resolve();
|
||||
}, timeoutMS);
|
||||
|
||||
let callback = () => {
|
||||
clearTimeout(timerId);
|
||||
resolve();
|
||||
};
|
||||
fetchRegionDefault().then(callback).catch(err => {
|
||||
Components.utils.reportError(err);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// If gInitialized is true then the search service was forced to perform
|
||||
// a sync initialization during our XHR - capture this via telemetry.
|
||||
// a sync initialization during our XHRs - capture this via telemetry.
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
|
||||
});
|
||||
|
||||
@ -628,9 +671,11 @@ function fetchCountryCode() {
|
||||
// large value just incase the request never completes - we don't want the
|
||||
// XHR object to live forever)
|
||||
let timeoutMS = Services.prefs.getIntPref("browser.search.geoip.timeout");
|
||||
let geoipTimeoutPossible = true;
|
||||
let timerId = setTimeout(() => {
|
||||
LOG("_fetchCountryCode: timeout fetching country information");
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
|
||||
if (geoipTimeoutPossible)
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
|
||||
timerId = null;
|
||||
resolve();
|
||||
}, timeoutMS);
|
||||
@ -646,14 +691,29 @@ function fetchCountryCode() {
|
||||
// This notification is just for tests...
|
||||
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-complete");
|
||||
|
||||
// If we've already timed out then we've already resolved the promise,
|
||||
// so there's nothing else to do.
|
||||
if (timerId == null) {
|
||||
return;
|
||||
if (timerId) {
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(0);
|
||||
geoipTimeoutPossible = false;
|
||||
}
|
||||
|
||||
let callback = () => {
|
||||
// If we've already timed out then we've already resolved the promise,
|
||||
// so there's nothing else to do.
|
||||
if (timerId == null) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(timerId);
|
||||
resolve();
|
||||
};
|
||||
|
||||
if (result && geoSpecificDefaultsEnabled()) {
|
||||
fetchRegionDefault().then(callback).catch(err => {
|
||||
Components.utils.reportError(err);
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(0);
|
||||
clearTimeout(timerId);
|
||||
resolve();
|
||||
};
|
||||
|
||||
let request = new XMLHttpRequest();
|
||||
@ -683,6 +743,123 @@ function fetchCountryCode() {
|
||||
});
|
||||
}
|
||||
|
||||
// This will make an HTTP request to a Mozilla server that will return
|
||||
// JSON data telling us what engine should be set as the default for
|
||||
// the current region, and how soon we should check again.
|
||||
//
|
||||
// The optional cohort value returned by the server is to be kept locally
|
||||
// and sent to the server the next time we ping it. It lets the server
|
||||
// identify profiles that have been part of a specific experiment.
|
||||
//
|
||||
// This promise may take up to 100s to resolve, it's the caller's
|
||||
// responsibility to ensure with a timer that we are not going to
|
||||
// block the async init for too long.
|
||||
let fetchRegionDefault = () => new Promise(resolve => {
|
||||
let urlTemplate = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF)
|
||||
.getCharPref("geoSpecificDefaults.url");
|
||||
let endpoint = Services.urlFormatter.formatURL(urlTemplate);
|
||||
|
||||
// As an escape hatch, no endpoint means no region specific defaults.
|
||||
if (!endpoint) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Append the optional cohort value.
|
||||
const cohortPref = "browser.search.cohort";
|
||||
let cohort;
|
||||
try {
|
||||
cohort = Services.prefs.getCharPref(cohortPref);
|
||||
} catch(e) {}
|
||||
if (cohort)
|
||||
endpoint += "/" + cohort;
|
||||
|
||||
LOG("fetchRegionDefault starting with endpoint " + endpoint);
|
||||
|
||||
let startTime = Date.now();
|
||||
let request = new XMLHttpRequest();
|
||||
request.timeout = 100000; // 100 seconds as the last-chance fallback
|
||||
request.onload = function(event) {
|
||||
let took = Date.now() - startTime;
|
||||
|
||||
let status = event.target.status;
|
||||
if (status != 200) {
|
||||
LOG("fetchRegionDefault failed with HTTP code " + status);
|
||||
let retryAfter = request.getResponseHeader("retry-after");
|
||||
if (retryAfter) {
|
||||
engineMetadataService.setGlobalAttr("searchDefaultExpir",
|
||||
Date.now() + retryAfter * 1000);
|
||||
}
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let response = event.target.response || {};
|
||||
LOG("received " + response.toSource());
|
||||
|
||||
if (response.cohort) {
|
||||
Services.prefs.setCharPref(cohortPref, response.cohort);
|
||||
} else {
|
||||
Services.prefs.clearUserPref(cohortPref);
|
||||
}
|
||||
|
||||
if (response.settings && response.settings.searchDefault) {
|
||||
let defaultEngine = response.settings.searchDefault;
|
||||
engineMetadataService.setGlobalAttr("searchDefault", defaultEngine);
|
||||
let hash = getVerificationHash(defaultEngine);
|
||||
LOG("fetchRegionDefault saved searchDefault: " + defaultEngine +
|
||||
" with verification hash: " + hash);
|
||||
engineMetadataService.setGlobalAttr("searchDefaultHash", hash);
|
||||
}
|
||||
|
||||
let interval = response.interval || SEARCH_GEO_DEFAULT_UPDATE_INTERVAL;
|
||||
let milliseconds = interval * 1000; // |interval| is in seconds.
|
||||
engineMetadataService.setGlobalAttr("searchDefaultExpir",
|
||||
Date.now() + milliseconds);
|
||||
|
||||
LOG("fetchRegionDefault got success response in " + took + "ms");
|
||||
resolve();
|
||||
};
|
||||
request.ontimeout = function(event) {
|
||||
LOG("fetchRegionDefault: XHR finally timed-out");
|
||||
resolve();
|
||||
};
|
||||
request.onerror = function(event) {
|
||||
LOG("fetchRegionDefault: failed to retrieve territory default information");
|
||||
resolve();
|
||||
};
|
||||
request.open("GET", endpoint, true);
|
||||
request.setRequestHeader("Content-Type", "application/json");
|
||||
request.responseType = "json";
|
||||
request.send();
|
||||
});
|
||||
|
||||
function getVerificationHash(aName) {
|
||||
let disclaimer = "By modifying this file, I agree that I am doing so " +
|
||||
"only within $appName itself, using official, user-driven search " +
|
||||
"engine selection processes, and in a way which does not circumvent " +
|
||||
"user consent. I acknowledge that any attempt to change this file " +
|
||||
"from outside of $appName is a malicious act, and will be responded " +
|
||||
"to accordingly."
|
||||
|
||||
let salt = OS.Path.basename(OS.Constants.Path.profileDir) + aName +
|
||||
disclaimer.replace(/\$appName/g, Services.appinfo.name);
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
// Data is an array of bytes.
|
||||
let data = converter.convertToByteArray(salt, {});
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
hasher.update(data, data.length);
|
||||
|
||||
return hasher.finish(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to verify a given DOM node's localName and namespaceURI.
|
||||
* @param aElement
|
||||
@ -3368,25 +3545,34 @@ SearchService.prototype = {
|
||||
return this.__sortedEngines;
|
||||
},
|
||||
|
||||
// Get the original Engine object that belongs to the defaultenginename pref
|
||||
// of the default branch.
|
||||
// Get the original Engine object that is the default for this region,
|
||||
// ignoring changes the user may have subsequently made.
|
||||
get _originalDefaultEngine() {
|
||||
let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
let defaultEngine;
|
||||
|
||||
let defPref = getGeoSpecificPrefName("defaultenginename");
|
||||
try {
|
||||
defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
|
||||
} catch (ex) {
|
||||
// If the default pref is invalid (e.g. an add-on set it to a bogus value)
|
||||
// getEngineByName will just return null, which is the best we can do.
|
||||
let defaultEngine = engineMetadataService.getGlobalAttr("searchDefault");
|
||||
if (defaultEngine &&
|
||||
engineMetadataService.getGlobalAttr("searchDefaultHash") != getVerificationHash(defaultEngine)) {
|
||||
LOG("get _originalDefaultEngine, invalid searchDefaultHash for: " + defaultEngine);
|
||||
defaultEngine = "";
|
||||
}
|
||||
|
||||
if (!defaultEngine) {
|
||||
let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
|
||||
let defPref = getGeoSpecificPrefName("defaultenginename");
|
||||
try {
|
||||
defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
|
||||
} catch (ex) {
|
||||
// If the default pref is invalid (e.g. an add-on set it to a bogus value)
|
||||
// getEngineByName will just return null, which is the best we can do.
|
||||
}
|
||||
}
|
||||
|
||||
return this.getEngineByName(defaultEngine);
|
||||
},
|
||||
|
||||
resetToOriginalDefaultEngine: function SRCH_SVC__resetToOriginalDefaultEngine() {
|
||||
this.defaultEngine = this._originalDefaultEngine;
|
||||
this.currentEngine = this._originalDefaultEngine;
|
||||
},
|
||||
|
||||
_buildCache: function SRCH_SVC__buildCache() {
|
||||
@ -3714,6 +3900,7 @@ SearchService.prototype = {
|
||||
},
|
||||
|
||||
_asyncReInit: function () {
|
||||
LOG("_asyncReInit");
|
||||
// Start by clearing the initialized state, so we don't abort early.
|
||||
gInitialized = false;
|
||||
|
||||
@ -3729,8 +3916,14 @@ SearchService.prototype = {
|
||||
|
||||
Task.spawn(function* () {
|
||||
try {
|
||||
LOG("Restarting engineMetadataService");
|
||||
yield engineMetadataService.init();
|
||||
yield this._asyncLoadEngines();
|
||||
yield ensureKnownCountryCode();
|
||||
|
||||
// Due to the HTTP requests done by ensureKnownCountryCode, it's possible that
|
||||
// at this point a synchronous init has been forced by other code.
|
||||
if (!gInitialized)
|
||||
yield this._asyncLoadEngines();
|
||||
|
||||
// Typically we'll re-init as a result of a pref observer,
|
||||
// so signal to 'callers' that we're done.
|
||||
@ -4295,31 +4488,6 @@ SearchService.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_getVerificationHash: function SRCH_SVC__getVerificationHash(aName) {
|
||||
let disclaimer = "By modifying this file, I agree that I am doing so " +
|
||||
"only within $appName itself, using official, user-driven search " +
|
||||
"engine selection processes, and in a way which does not circumvent " +
|
||||
"user consent. I acknowledge that any attempt to change this file " +
|
||||
"from outside of $appName is a malicious act, and will be responded " +
|
||||
"to accordingly."
|
||||
|
||||
let salt = OS.Path.basename(OS.Constants.Path.profileDir) + aName +
|
||||
disclaimer.replace(/\$appName/g, Services.appinfo.name);
|
||||
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
// Data is an array of bytes.
|
||||
let data = converter.convertToByteArray(salt, {});
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
hasher.update(data, data.length);
|
||||
|
||||
return hasher.finish(true);
|
||||
},
|
||||
|
||||
// nsIBrowserSearchService
|
||||
init: function SRCH_SVC_init(observer) {
|
||||
LOG("SearchService.init");
|
||||
@ -4673,7 +4841,7 @@ SearchService.prototype = {
|
||||
this._ensureInitialized();
|
||||
if (!this._currentEngine) {
|
||||
let name = engineMetadataService.getGlobalAttr("current");
|
||||
if (engineMetadataService.getGlobalAttr("hash") == this._getVerificationHash(name)) {
|
||||
if (engineMetadataService.getGlobalAttr("hash") == getVerificationHash(name)) {
|
||||
this._currentEngine = this.getEngineByName(name);
|
||||
}
|
||||
}
|
||||
@ -4713,7 +4881,7 @@ SearchService.prototype = {
|
||||
}
|
||||
|
||||
engineMetadataService.setGlobalAttr("current", newName);
|
||||
engineMetadataService.setGlobalAttr("hash", this._getVerificationHash(newName));
|
||||
engineMetadataService.setGlobalAttr("hash", getVerificationHash(newName));
|
||||
|
||||
notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
|
||||
},
|
||||
|
@ -251,6 +251,22 @@ function isUSTimezone() {
|
||||
return UTCOffset >= 150 && UTCOffset <= 600;
|
||||
}
|
||||
|
||||
const kDefaultenginenamePref = "browser.search.defaultenginename";
|
||||
const kTestEngineName = "Test search engine";
|
||||
const kLocalePref = "general.useragent.locale";
|
||||
|
||||
function getDefaultEngineName(isUS) {
|
||||
const nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
// Copy the logic from nsSearchService
|
||||
let pref = kDefaultenginenamePref;
|
||||
if (isUS === undefined)
|
||||
isUS = Services.prefs.getCharPref(kLocalePref) == "en-US" && isUSTimezone();
|
||||
if (isUS) {
|
||||
pref += ".US";
|
||||
}
|
||||
return Services.prefs.getComplexValue(pref, nsIPLS).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for metadata being committed.
|
||||
* @return {Promise} Resolved when the metadata is committed to disk.
|
||||
@ -317,6 +333,8 @@ if (!isChild) {
|
||||
Services.prefs.setIntPref("browser.search.geoip.timeout", 2000);
|
||||
// But still disable geoip lookups - tests that need it will re-configure this.
|
||||
Services.prefs.setCharPref("browser.search.geoip.url", "");
|
||||
// Also disable region defaults - tests using it will also re-configure it.
|
||||
Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref("geoSpecificDefaults.url", "");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -489,6 +507,24 @@ function waitForSearchNotification(aExpectedData) {
|
||||
});
|
||||
}
|
||||
|
||||
function asyncInit() {
|
||||
return new Promise(resolve => {
|
||||
Services.search.init(function() {
|
||||
do_check_true(Services.search.isInitialized);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function asyncReInit() {
|
||||
let promise = waitForSearchNotification("reinit-complete");
|
||||
|
||||
Services.search.QueryInterface(Ci.nsIObserver)
|
||||
.observe(null, "nsPref:changed", kLocalePref);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// This "enum" from nsSearchService.js
|
||||
const TELEMETRY_RESULT_ENUM = {
|
||||
SUCCESS: 0,
|
||||
|
284
toolkit/components/search/tests/xpcshell/test_geodefaults.js
Normal file
284
toolkit/components/search/tests/xpcshell/test_geodefaults.js
Normal file
@ -0,0 +1,284 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var requests = [];
|
||||
var gServerCohort = "";
|
||||
|
||||
const kUrlPref = "geoSpecificDefaults.url";
|
||||
|
||||
const kDayInSeconds = 86400;
|
||||
const kYearInSeconds = kDayInSeconds * 365;
|
||||
|
||||
function run_test() {
|
||||
updateAppInfo();
|
||||
installTestEngine();
|
||||
|
||||
let srv = new HttpServer();
|
||||
|
||||
srv.registerPathHandler("/lookup_defaults", (metadata, response) => {
|
||||
response.setStatusLine("1.1", 200, "OK");
|
||||
let data = {interval: kYearInSeconds,
|
||||
settings: {searchDefault: "Test search engine"}};
|
||||
if (gServerCohort)
|
||||
data.cohort = gServerCohort;
|
||||
response.write(JSON.stringify(data));
|
||||
requests.push(metadata);
|
||||
});
|
||||
|
||||
srv.registerPathHandler("/lookup_fail", (metadata, response) => {
|
||||
response.setStatusLine("1.1", 404, "Not Found");
|
||||
requests.push(metadata);
|
||||
});
|
||||
|
||||
srv.registerPathHandler("/lookup_unavailable", (metadata, response) => {
|
||||
response.setStatusLine("1.1", 503, "Service Unavailable");
|
||||
response.setHeader("Retry-After", kDayInSeconds.toString());
|
||||
requests.push(metadata);
|
||||
});
|
||||
|
||||
srv.start(-1);
|
||||
do_register_cleanup(() => srv.stop(() => {}));
|
||||
|
||||
let url = "http://localhost:" + srv.identity.primaryPort + "/lookup_defaults?";
|
||||
Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, url);
|
||||
// Set a bogus user value so that running the test ensures we ignore it.
|
||||
Services.prefs.setCharPref(BROWSER_SEARCH_PREF + kUrlPref, "about:blank");
|
||||
Services.prefs.setCharPref("browser.search.geoip.url",
|
||||
'data:application/json,{"country_code": "FR"}');
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function checkNoRequest() {
|
||||
do_check_eq(requests.length, 0);
|
||||
}
|
||||
|
||||
function checkRequest(cohort = "") {
|
||||
do_check_eq(requests.length, 1);
|
||||
let req = requests.pop();
|
||||
do_check_eq(req._method, "GET");
|
||||
do_check_eq(req._queryString, cohort ? "/" + cohort : "");
|
||||
}
|
||||
|
||||
function promiseGlobalMetadata() {
|
||||
return new Promise(resolve => Task.spawn(function* () {
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json");
|
||||
let bytes = yield OS.File.read(path);
|
||||
resolve(JSON.parse(new TextDecoder().decode(bytes))["[global]"]);
|
||||
}));
|
||||
}
|
||||
|
||||
function promiseSaveGlobalMetadata(globalData) {
|
||||
return new Promise(resolve => Task.spawn(function* () {
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir, "search-metadata.json");
|
||||
let bytes = yield OS.File.read(path);
|
||||
let data = JSON.parse(new TextDecoder().decode(bytes));
|
||||
data["[global]"] = globalData;
|
||||
yield OS.File.writeAtomic(path,
|
||||
new TextEncoder().encode(JSON.stringify(data)));
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
|
||||
let forceExpiration = Task.async(function* () {
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
|
||||
// Make the current geodefaults expire 1s ago.
|
||||
metadata.searchdefaultexpir = Date.now() - 1000;
|
||||
yield promiseSaveGlobalMetadata(metadata);
|
||||
});
|
||||
|
||||
add_task(function* no_request_if_prefed_off() {
|
||||
// Disable geoSpecificDefaults and check no HTTP request is made.
|
||||
Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
|
||||
yield asyncInit();
|
||||
checkNoRequest();
|
||||
|
||||
// The default engine should be set based on the prefs.
|
||||
do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
|
||||
|
||||
// Change the default engine and then revert to default to ensure
|
||||
// the metadata file exists.
|
||||
let commitPromise = promiseAfterCommit();
|
||||
Services.search.currentEngine = Services.search.getEngineByName(kTestEngineName);
|
||||
Services.search.resetToOriginalDefaultEngine();
|
||||
yield commitPromise;
|
||||
|
||||
// Ensure nothing related to geoSpecificDefaults has been written in the metadata.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "undefined");
|
||||
do_check_eq(typeof metadata.searchdefault, "undefined");
|
||||
do_check_eq(typeof metadata.searchdefaulthash, "undefined");
|
||||
|
||||
Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
|
||||
});
|
||||
|
||||
add_task(function* should_get_geo_defaults_only_once() {
|
||||
// (Re)initializing the search service should trigger a request,
|
||||
// and set the default engine based on it.
|
||||
let commitPromise = promiseAfterCommit();
|
||||
// Due to the previous initialization, we expect the countryCode to already be set.
|
||||
do_check_true(Services.prefs.prefHasUserValue("browser.search.countryCode"));
|
||||
do_check_eq(Services.prefs.getCharPref("browser.search.countryCode"), "FR");
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
do_check_eq(Services.search.currentEngine.name, kTestEngineName);
|
||||
yield commitPromise;
|
||||
|
||||
// Verify the metadata was written correctly.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "number");
|
||||
do_check_true(metadata.searchdefaultexpir > Date.now());
|
||||
do_check_eq(typeof metadata.searchdefault, "string");
|
||||
do_check_eq(metadata.searchdefault, "Test search engine");
|
||||
do_check_eq(typeof metadata.searchdefaulthash, "string");
|
||||
do_check_eq(metadata.searchdefaulthash.length, 44);
|
||||
|
||||
// The next restart shouldn't trigger a request.
|
||||
yield asyncReInit();
|
||||
checkNoRequest();
|
||||
do_check_eq(Services.search.currentEngine.name, kTestEngineName);
|
||||
});
|
||||
|
||||
add_task(function* should_request_when_countryCode_not_set() {
|
||||
Services.prefs.clearUserPref("browser.search.countryCode");
|
||||
let commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
});
|
||||
|
||||
add_task(function* should_recheck_if_interval_expired() {
|
||||
yield forceExpiration();
|
||||
|
||||
let commitPromise = promiseAfterCommit();
|
||||
let date = Date.now();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the expiration timestamp has been updated.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "number");
|
||||
do_check_true(metadata.searchdefaultexpir >= date + kYearInSeconds * 1000);
|
||||
do_check_true(metadata.searchdefaultexpir < date + (kYearInSeconds + 3600) * 1000);
|
||||
});
|
||||
|
||||
add_task(function* should_recheck_when_broken_hash() {
|
||||
// This test verifies both that we ignore saved geo-defaults if the
|
||||
// hash is invalid, and that we keep the local preferences-based
|
||||
// default for all of the session in case a synchronous
|
||||
// initialization was triggered before our HTTP request completed.
|
||||
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
|
||||
// Break the hash.
|
||||
let hash = metadata.searchdefaulthash;
|
||||
metadata.searchdefaulthash = "broken";
|
||||
yield promiseSaveGlobalMetadata(metadata);
|
||||
|
||||
let commitPromise = promiseAfterCommit();
|
||||
let reInitPromise = asyncReInit();
|
||||
|
||||
// Synchronously check the current default engine, to force a sync init.
|
||||
// The hash is wrong, so we should fallback to the default engine from prefs.
|
||||
// XXX For some reason forcing a sync init while already asynchronously
|
||||
// reinitializing causes a shutdown warning related to engineMetadataService's
|
||||
// finalize method having already been called. Seems harmless for the purpose
|
||||
// of this test.
|
||||
do_check_false(Services.search.isInitialized)
|
||||
do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
|
||||
do_check_true(Services.search.isInitialized)
|
||||
|
||||
yield reInitPromise;
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the hash is back to its previous value.
|
||||
metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaulthash, "string");
|
||||
do_check_eq(metadata.searchdefaulthash, hash);
|
||||
|
||||
// The current default engine shouldn't change during a session.
|
||||
do_check_eq(Services.search.currentEngine.name, getDefaultEngineName(false));
|
||||
|
||||
// After another restart, the current engine should be back to the geo default,
|
||||
// without doing yet another request.
|
||||
yield asyncReInit();
|
||||
checkNoRequest();
|
||||
do_check_eq(Services.search.currentEngine.name, kTestEngineName);
|
||||
});
|
||||
|
||||
add_task(function* should_remember_cohort_id() {
|
||||
// Check that initially the cohort pref doesn't exist.
|
||||
const cohortPref = "browser.search.cohort";
|
||||
do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_INVALID);
|
||||
|
||||
// Make the server send a cohort id.
|
||||
let cohort = gServerCohort = "xpcshell";
|
||||
|
||||
// Trigger a new request.
|
||||
yield forceExpiration();
|
||||
let commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the cohort was saved.
|
||||
do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_STRING);
|
||||
do_check_eq(Services.prefs.getCharPref(cohortPref), cohort);
|
||||
|
||||
// Make the server stop sending the cohort.
|
||||
gServerCohort = "";
|
||||
|
||||
// Check that the next request sends the previous cohort id, and
|
||||
// will remove it from the prefs due to the server no longer sending it.
|
||||
yield forceExpiration();
|
||||
commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest(cohort);
|
||||
yield commitPromise;
|
||||
do_check_eq(Services.prefs.getPrefType(cohortPref), Services.prefs.PREF_INVALID);
|
||||
});
|
||||
|
||||
add_task(function* should_retry_after_failure() {
|
||||
let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let originalUrl = defaultBranch.getCharPref(kUrlPref);
|
||||
defaultBranch.setCharPref(kUrlPref, originalUrl.replace("defaults", "fail"));
|
||||
|
||||
// Trigger a new request.
|
||||
yield forceExpiration();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
|
||||
// After another restart, a new request should be triggered automatically without
|
||||
// the test having to call forceExpiration again.
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
});
|
||||
|
||||
add_task(function* should_honor_retry_after_header() {
|
||||
let defaultBranch = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
|
||||
let originalUrl = defaultBranch.getCharPref(kUrlPref);
|
||||
defaultBranch.setCharPref(kUrlPref, originalUrl.replace("fail", "unavailable"));
|
||||
|
||||
// Trigger a new request.
|
||||
yield forceExpiration();
|
||||
let date = Date.now();
|
||||
let commitPromise = promiseAfterCommit();
|
||||
yield asyncReInit();
|
||||
checkRequest();
|
||||
yield commitPromise;
|
||||
|
||||
// Check that the expiration timestamp has been updated.
|
||||
let metadata = yield promiseGlobalMetadata();
|
||||
do_check_eq(typeof metadata.searchdefaultexpir, "number");
|
||||
do_check_true(metadata.searchdefaultexpir >= date + kDayInSeconds * 1000);
|
||||
do_check_true(metadata.searchdefaultexpir < date + (kDayInSeconds + 3600) * 1000);
|
||||
|
||||
// After another restart, a new request should not be triggered.
|
||||
yield asyncReInit();
|
||||
checkNoRequest();
|
||||
});
|
@ -3,73 +3,11 @@
|
||||
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
const kDefaultenginenamePref = "browser.search.defaultenginename";
|
||||
const kSelectedEnginePref = "browser.search.selectedEngine";
|
||||
|
||||
const kTestEngineName = "Test search engine";
|
||||
|
||||
// These two functions (getLocale and getIsUS) are copied from nsSearchService.js
|
||||
function getLocale() {
|
||||
let LOCALE_PREF = "general.useragent.locale";
|
||||
return Services.prefs.getCharPref(LOCALE_PREF);
|
||||
}
|
||||
|
||||
function getIsUS() {
|
||||
if (getLocale() != "en-US") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Timezone assumptions! We assume that if the system clock's timezone is
|
||||
// between Newfoundland and Hawaii, that the user is in North America.
|
||||
|
||||
// This includes all of South America as well, but we have relatively few
|
||||
// en-US users there, so that's OK.
|
||||
|
||||
// 150 minutes = 2.5 hours (UTC-2.5), which is
|
||||
// Newfoundland Daylight Time (http://www.timeanddate.com/time/zones/ndt)
|
||||
|
||||
// 600 minutes = 10 hours (UTC-10), which is
|
||||
// Hawaii-Aleutian Standard Time (http://www.timeanddate.com/time/zones/hast)
|
||||
|
||||
let UTCOffset = (new Date()).getTimezoneOffset();
|
||||
let isNA = UTCOffset >= 150 && UTCOffset <= 600;
|
||||
|
||||
return isNA;
|
||||
}
|
||||
|
||||
function getDefaultEngineName() {
|
||||
const nsIPLS = Ci.nsIPrefLocalizedString;
|
||||
// Copy the logic from nsSearchService
|
||||
let pref = kDefaultenginenamePref;
|
||||
if (getIsUS()) {
|
||||
pref += ".US";
|
||||
}
|
||||
return Services.prefs.getComplexValue(pref, nsIPLS).data;
|
||||
}
|
||||
|
||||
// waitForSearchNotification is in head_search.js
|
||||
let waitForNotification = waitForSearchNotification;
|
||||
|
||||
function asyncInit() {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
Services.search.init(function() {
|
||||
do_check_true(Services.search.isInitialized);
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function asyncReInit() {
|
||||
let promise = waitForNotification("reinit-complete");
|
||||
|
||||
Services.search.QueryInterface(Ci.nsIObserver)
|
||||
.observe(null, "nsPref:changed", "general.useragent.locale");
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Check that the default engine matches the defaultenginename pref
|
||||
add_task(function* test_defaultEngine() {
|
||||
yield asyncInit();
|
||||
@ -138,7 +76,7 @@ add_task(function* test_ignoreInvalidHash() {
|
||||
json["[global]"].hash = "invalid";
|
||||
|
||||
let data = new TextEncoder().encode(JSON.stringify(json));
|
||||
let promise = OS.File.writeAtomic(path, data);//, { tmpPath: path + ".tmp" });
|
||||
yield OS.File.writeAtomic(path, data);//, { tmpPath: path + ".tmp" });
|
||||
|
||||
// Re-init the search service, and check that the json file is ignored.
|
||||
yield asyncReInit();
|
||||
|
@ -75,3 +75,4 @@ support-files =
|
||||
[test_sync_profile_engine.js]
|
||||
[test_rel_searchform.js]
|
||||
[test_selectedEngine.js]
|
||||
[test_geodefaults.js]
|
||||
|
@ -90,6 +90,15 @@ nsURLFormatterService.prototype = {
|
||||
LOCALE: function() Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
getService(Ci.nsIXULChromeRegistry).
|
||||
getSelectedLocale('global'),
|
||||
REGION: function() {
|
||||
try {
|
||||
// When the geoip lookup failed to identify the region, we fallback to
|
||||
// the 'ZZ' region code to mean 'unknown'.
|
||||
return Services.prefs.getCharPref("browser.search.region") || "ZZ";
|
||||
} catch(e) {
|
||||
return "ZZ";
|
||||
}
|
||||
},
|
||||
VENDOR: function() this.appInfo.vendor,
|
||||
NAME: function() this.appInfo.name,
|
||||
ID: function() this.appInfo.ID,
|
||||
|
Loading…
x
Reference in New Issue
Block a user