summaryrefslogtreecommitdiffstats
path: root/toolkit/components/search/current/nsSearchService.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/search/current/nsSearchService.js')
-rw-r--r--toolkit/components/search/current/nsSearchService.js427
1 files changed, 5 insertions, 422 deletions
diff --git a/toolkit/components/search/current/nsSearchService.js b/toolkit/components/search/current/nsSearchService.js
index db90e5150..fd8c9140e 100644
--- a/toolkit/components/search/current/nsSearchService.js
+++ b/toolkit/components/search/current/nsSearchService.js
@@ -388,401 +388,6 @@ function isPartnerBuild() {
return false;
}
-// Method to determine if we should be using geo-specific defaults
-function geoSpecificDefaultsEnabled() {
- return Services.prefs.getBoolPref("browser.search.geoSpecificDefaults", false);
-}
-
-// Some notes on countryCode and region prefs:
-// * A "countryCode" pref is set via a geoip lookup. It always reflects the
-// result of that geoip request.
-// * A "region" pref, once set, is the region actually used for search. In
-// most cases it will be identical to the countryCode pref.
-// * The value of "region" and "countryCode" will only not agree in one edge
-// case - 34/35 users who have previously been configured to use US defaults
-// based purely on a timezone check will have "region" forced to US,
-// regardless of what countryCode geoip returns.
-// * We may want to know if we are in the US before we have *either*
-// countryCode or region - in which case we fallback to a timezone check,
-// but we don't persist that value anywhere in the expectation we will
-// eventually get a countryCode/region.
-
-// A method that "migrates" prefs if necessary.
-function migrateRegionPrefs() {
- // If we already have a "region" pref there's nothing to do.
- if (Services.prefs.prefHasUserValue("browser.search.region")) {
- return;
- }
-
- // If we have 'isUS' but no 'countryCode' then we are almost certainly
- // a profile from Fx 34/35 that set 'isUS' based purely on a timezone
- // check. If this said they were US, we force region to be US.
- // (But if isUS was false, we leave region alone - we will do a geoip request
- // and set the region accordingly)
- try {
- if (Services.prefs.getBoolPref("browser.search.isUS") &&
- !Services.prefs.prefHasUserValue("browser.search.countryCode")) {
- Services.prefs.setCharPref("browser.search.region", "US");
- }
- } catch (ex) {
- // no isUS pref, nothing to do.
- }
- // If we have a countryCode pref but no region pref, just force region
- // to be the countryCode.
- try {
- let countryCode = Services.prefs.getCharPref("browser.search.countryCode");
- if (!Services.prefs.prefHasUserValue("browser.search.region")) {
- Services.prefs.setCharPref("browser.search.region", countryCode);
- }
- } catch (ex) {
- // no countryCode pref, nothing to do.
- }
-}
-
-// A method to determine if we are in the United States (US) for the search
-// service.
-// It uses a browser.search.region pref (which typically comes from a geoip
-// request) or if that doesn't exist, falls back to a hacky timezone check.
-function getIsUS() {
- // Regardless of the region or countryCode, non en-US builds are not
- // considered to be in the US from the POV of the search service.
- if (getLocale() != "en-US") {
- return false;
- }
-
- // If we've got a region pref, trust it.
- try {
- return Services.prefs.getCharPref("browser.search.region") == "US";
- } catch (e) {}
-
- // So we are en-US but have no region pref - fallback to hacky timezone check.
- let isNA = isUSTimezone();
- LOG("getIsUS() fell back to a timezone check with the result=" + isNA);
- return isNA;
-}
-
-// Helper method to modify preference keys with geo-specific modifiers, if needed.
-function getGeoSpecificPrefName(basepref) {
- if (!geoSpecificDefaultsEnabled() || isPartnerBuild())
- return basepref;
- if (getIsUS())
- return basepref + ".US";
- return basepref;
-}
-
-// A method that tries to determine if this user is in a US geography.
-function isUSTimezone() {
- // 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();
- return UTCOffset >= 150 && UTCOffset <= 600;
-}
-
-// A less hacky method that tries to determine our country-code via an XHR
-// geoip lookup.
-// If this succeeds and we are using an en-US locale, we set the pref used by
-// the hacky method above, so isUS() can avoid the hacky timezone method.
-// If it fails we don't touch that pref so isUS() does its normal thing.
-var ensureKnownCountryCode = Task.async(function* (ss) {
- // If we have a country-code already stored in our prefs we trust it.
- let countryCode = Services.prefs.getCharPref("browser.search.countryCode", "");
- 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(ss);
- } else {
- // if nothing to do, return early.
- if (!geoSpecificDefaultsEnabled())
- return;
-
- let expir = ss.getGlobalAttr("searchDefaultExpir") || 0;
- if (expir > Date.now()) {
- // The territory default we have already fetched hasn't expired yet.
- // If we have a default engine or a list of visible default engines
- // saved, the hashes should be valid, verify them now so that we can
- // refetch if they have been tampered with.
- let defaultEngine = ss.getVerifiedGlobalAttr("searchDefault");
- let visibleDefaultEngines = ss.getVerifiedGlobalAttr("visibleDefaultEngines");
- if ((defaultEngine || defaultEngine === undefined) &&
- (visibleDefaultEngines || visibleDefaultEngines === undefined)) {
- // No geo defaults, or valid hashes; 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(ss).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 XHRs - capture this via telemetry.
- Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_CAUSED_SYNC_INIT").add(gInitialized);
-});
-
-// Store the result of the geoip request as well as any other values and
-// telemetry which depend on it.
-function storeCountryCode(cc) {
- // Set the country-code itself.
- Services.prefs.setCharPref("browser.search.countryCode", cc);
- // And set the region pref if we don't already have a value.
- if (!Services.prefs.prefHasUserValue("browser.search.region")) {
- Services.prefs.setCharPref("browser.search.region", cc);
- }
- // and telemetry...
- let isTimezoneUS = isUSTimezone();
- if (cc == "US" && !isTimezoneUS) {
- Services.telemetry.getHistogramById("SEARCH_SERVICE_US_COUNTRY_MISMATCHED_TIMEZONE").add(1);
- }
- if (cc != "US" && isTimezoneUS) {
- Services.telemetry.getHistogramById("SEARCH_SERVICE_US_TIMEZONE_MISMATCHED_COUNTRY").add(1);
- }
- // telemetry to compare our geoip response with platform-specific country data.
- // On Mac and Windows, we can get a country code via sysinfo
- let platformCC = Services.sysinfo.get("countryCode");
- if (platformCC) {
- let probeUSMismatched, probeNonUSMismatched;
- switch (Services.appinfo.OS) {
- case "Darwin":
- probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_OSX";
- probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX";
- break;
- case "WINNT":
- probeUSMismatched = "SEARCH_SERVICE_US_COUNTRY_MISMATCHED_PLATFORM_WIN";
- probeNonUSMismatched = "SEARCH_SERVICE_NONUS_COUNTRY_MISMATCHED_PLATFORM_WIN";
- break;
- default:
- Cu.reportError("Platform " + Services.appinfo.OS + " has system country code but no search service telemetry probes");
- break;
- }
- if (probeUSMismatched && probeNonUSMismatched) {
- if (cc == "US" || platformCC == "US") {
- // one of the 2 said US, so record if they are the same.
- Services.telemetry.getHistogramById(probeUSMismatched).add(cc != platformCC);
- } else {
- // different country - record if they are the same
- Services.telemetry.getHistogramById(probeNonUSMismatched).add(cc != platformCC);
- }
- }
- }
-}
-
-// Get the country we are in via a XHR geoip request.
-function fetchCountryCode(ss) {
- // values for the SEARCH_SERVICE_COUNTRY_FETCH_RESULT 'enum' telemetry probe.
- const TELEMETRY_RESULT_ENUM = {
- SUCCESS: 0,
- SUCCESS_WITHOUT_DATA: 1,
- XHRTIMEOUT: 2,
- ERROR: 3,
- // Note that we expect to add finer-grained error types here later (eg,
- // dns error, network error, ssl error, etc) with .ERROR remaining as the
- // generic catch-all that doesn't fit into other categories.
- };
- let endpoint = Services.urlFormatter.formatURLPref("browser.search.geoip.url");
- LOG("_fetchCountryCode starting with endpoint " + endpoint);
- // As an escape hatch, no endpoint means no geoip.
- if (!endpoint) {
- return Promise.resolve();
- }
- let startTime = Date.now();
- return new Promise(resolve => {
- // Instead of using a timeout on the xhr object itself, we simulate one
- // using a timer and let the XHR request complete. This allows us to
- // capture reliable telemetry on what timeout value should actually be
- // used to ensure most users don't see one while not making it so large
- // that many users end up doing a sync init of the search service and thus
- // would see the jank that implies.
- // (Note we do actually use a timeout on the XHR, but that's set to be a
- // 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");
- if (geoipTimeoutPossible)
- Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_TIMEOUT").add(1);
- timerId = null;
- resolve();
- }, timeoutMS);
-
- let resolveAndReportSuccess = (result, reason) => {
- // Even if we timed out, we want to save the country code and everything
- // related so next startup sees the value and doesn't retry this dance.
- if (result) {
- storeCountryCode(result);
- }
- Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_RESULT").add(reason);
-
- // This notification is just for tests...
- Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-complete");
-
- 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(ss).then(callback).catch(err => {
- Components.utils.reportError(err);
- callback();
- });
- } else {
- callback();
- }
- };
-
- let request = new XMLHttpRequest();
- // This notification is just for tests...
- Services.obs.notifyObservers(request, SEARCH_SERVICE_TOPIC, "geoip-lookup-xhr-starting");
- request.timeout = 100000; // 100 seconds as the last-chance fallback
- request.onload = function(event) {
- let took = Date.now() - startTime;
- let cc = event.target.response && event.target.response.country_code;
- LOG("_fetchCountryCode got success response in " + took + "ms: " + cc);
- Services.telemetry.getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS").add(took);
- let reason = cc ? TELEMETRY_RESULT_ENUM.SUCCESS : TELEMETRY_RESULT_ENUM.SUCCESS_WITHOUT_DATA;
- resolveAndReportSuccess(cc, reason);
- };
- request.ontimeout = function(event) {
- LOG("_fetchCountryCode: XHR finally timed-out fetching country information");
- resolveAndReportSuccess(null, TELEMETRY_RESULT_ENUM.XHRTIMEOUT);
- };
- request.onerror = function(event) {
- LOG("_fetchCountryCode: failed to retrieve country information");
- resolveAndReportSuccess(null, TELEMETRY_RESULT_ENUM.ERROR);
- };
- request.open("POST", endpoint, true);
- request.setRequestHeader("Content-Type", "application/json");
- request.responseType = "json";
- request.send("{}");
- });
-}
-
-// 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.
-var fetchRegionDefault = (ss) => 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 = Services.prefs.getCharPref(cohortPref, "");
- 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) {
- ss.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;
- ss.setVerifiedGlobalAttr("searchDefault", defaultEngine);
- LOG("fetchRegionDefault saved searchDefault: " + defaultEngine);
- }
-
- if (response.settings && response.settings.visibleDefaultEngines) {
- let visibleDefaultEngines = response.settings.visibleDefaultEngines;
- let string = visibleDefaultEngines.join(",");
- ss.setVerifiedGlobalAttr("visibleDefaultEngines", string);
- LOG("fetchRegionDefault saved visibleDefaultEngines: " + string);
- }
-
- let interval = response.interval || SEARCH_GEO_DEFAULT_UPDATE_INTERVAL;
- let milliseconds = interval * 1000; // |interval| is in seconds.
- ss.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 " +
@@ -2685,7 +2290,6 @@ SearchService.prototype = {
_syncInit: function SRCH_SVC__syncInit() {
LOG("_syncInit start");
this._initStarted = true;
- migrateRegionPrefs();
let cache = this._readCacheFile();
if (cache.metaData)
@@ -2720,8 +2324,6 @@ SearchService.prototype = {
_asyncInit: Task.async(function* () {
LOG("_asyncInit start");
- migrateRegionPrefs();
-
// See if we have a cache file so we don't have to parse a bunch of XML.
let cache = {};
// Not using checkForSyncCompletion here because we want to ensure we
@@ -2733,14 +2335,6 @@ SearchService.prototype = {
this._metaData = cache.metaData;
try {
- yield checkForSyncCompletion(ensureKnownCountryCode(this));
- } catch (ex) {
- if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
- throw ex;
- }
- LOG("_asyncInit: failure determining country code: " + ex);
- }
- try {
yield checkForSyncCompletion(this._asyncLoadEngines(cache));
} catch (ex) {
if (ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
@@ -2800,9 +2394,8 @@ SearchService.prototype = {
let defaultPrefB = Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF);
let nsIPLS = Ci.nsIPrefLocalizedString;
- let defPref = getGeoSpecificPrefName("defaultenginename");
try {
- defaultEngine = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
+ defaultEngine = defaultPrefB.getComplexValue("defaultenginename", 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.
@@ -3117,11 +2710,7 @@ SearchService.prototype = {
if (!gInitialized && cache.metaData)
this._metaData = cache.metaData;
- yield ensureKnownCountryCode(this);
- // 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(cache);
+ yield this._asyncLoadEngines(cache);
// Typically we'll re-init as a result of a pref observer,
// so signal to 'callers' that we're done.
@@ -3797,10 +3386,8 @@ SearchService.prototype = {
}
catch (e) { }
- let prefNameBase = getGeoSpecificPrefName(BROWSER_SEARCH_PREF + "order");
while (true) {
- prefName = prefNameBase + "." + (++i);
- engineName = getLocalizedPref(prefName);
+ engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
if (!engineName)
break;
@@ -3934,10 +3521,8 @@ SearchService.prototype = {
}
// Now look through the "browser.search.order" branch.
- let prefNameBase = getGeoSpecificPrefName(BROWSER_SEARCH_PREF + "order");
for (var j = 1; ; j++) {
- let prefName = prefNameBase + "." + j;
- engineName = getLocalizedPref(prefName);
+ engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + j);
if (!engineName)
break;
@@ -4291,11 +3876,9 @@ SearchService.prototype = {
} catch (e) {}
}
- let prefNameBase = getGeoSpecificPrefName(BROWSER_SEARCH_PREF + "order");
let i = 0;
while (!sendSubmissionURL) {
- let prefName = prefNameBase + "." + (++i);
- let engineName = getLocalizedPref(prefName);
+ let engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
if (!engineName)
break;
if (result.name == engineName) {