☠☠ backed out by 63b7d4bbaab6 ☠ ☠ | |
author | Richard Newman <rnewman@mozilla.com> |
Tue, 15 Oct 2013 16:53:45 -0700 | |
changeset 164739 | 3d0582ab04175c06e6f4f5601606ed40e3c41497 |
parent 164738 | d333b85c805de705b0303be5a3c0b44cdd3768c7 |
child 164740 | 51185a26a7b7660550d6ab5e857459542d34b222 |
push id | 3066 |
push user | akeybl@mozilla.com |
push date | Mon, 09 Dec 2013 19:58:46 +0000 |
treeherder | mozilla-beta@a31a0dce83aa [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mcomella |
bugs | 922694 |
milestone | 27.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -112,16 +112,17 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; abstract public class GeckoApp extends GeckoActivity implements GeckoEventListener, SensorEventListener, LocationListener, @@ -1286,17 +1287,26 @@ abstract public class GeckoApp editor.commit(); // The lifecycle of mHealthRecorder is "shortly after onCreate" // through "onDestroy" -- essentially the same as the lifecycle // of the activity itself. final String profilePath = getProfile().getDir().getAbsolutePath(); final EventDispatcher dispatcher = GeckoAppShell.getEventDispatcher(); Log.i(LOGTAG, "Creating BrowserHealthRecorder."); - mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this, profilePath, dispatcher, + final String osLocale = Locale.getDefault().toString(); + Log.d(LOGTAG, "Locale is " + osLocale); + + // Replace the duplicate `osLocale` argument when we support switchable + // application locales. + mHealthRecorder = new BrowserHealthRecorder(GeckoApp.this, + profilePath, + dispatcher, + osLocale, + osLocale, // Placeholder. previousSession); } }); GeckoAppShell.setNotificationClient(makeNotificationClient()); } protected void initializeChrome() { @@ -1550,18 +1560,25 @@ abstract public class GeckoApp // Kick off our background services. We do this by invoking the broadcast // receiver, which uses the system alarm infrastructure to perform tasks at // intervals. GeckoPreferences.broadcastAnnouncementsPref(context); GeckoPreferences.broadcastHealthReportUploadPref(context); /* - XXXX see bug 635342 - We want to disable this code if possible. It is about 145ms in runtime + XXXX see Bug 635342. + We want to disable this code if possible. It is about 145ms in runtime. + + If this code ever becomes live again, you'll need to chain the + new locale into BrowserHealthRecorder correctly. See + GeckoAppShell.setSelectedLocale. + We pass the OS locale into the BHR constructor: we need to grab + that *before* we modify the current locale! + SharedPreferences settings = getPreferences(Activity.MODE_PRIVATE); String localeCode = settings.getString(getPackageName() + ".locale", ""); if (localeCode != null && localeCode.length() > 0) GeckoAppShell.setSelectedLocale(localeCode); */ if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.Launched)) { return;
--- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -1520,16 +1520,22 @@ public class GeckoAppShell @GeneratableAndroidBridgeTarget public static void setSelectedLocale(String localeCode) { /* Bug 713464: This method is still called from Gecko side. Earlier we had an option to run Firefox in a language other than system's language. However, this is not supported as of now. Gecko resets the locale to en-US by calling this function with an empty string. This affects GeckoPreferences activity in multi-locale builds. + N.B., if this code ever becomes live again, you need to hook it up to locale + recording in BrowserHealthRecorder: we track the current app and OS locales + as part of the recorded environment. + + See similar note in GeckoApp.java for the startup path. + //We're not using this, not need to save it (see bug 635342) SharedPreferences settings = getContext().getPreferences(Activity.MODE_PRIVATE); settings.edit().putString(getContext().getPackageName() + ".locale", localeCode).commit(); Locale locale; int index; if ((index = localeCode.indexOf('-')) != -1 ||
--- a/mobile/android/base/health/BrowserHealthRecorder.java +++ b/mobile/android/base/health/BrowserHealthRecorder.java @@ -11,18 +11,16 @@ import android.content.Context; import android.content.ContentProviderClient; import android.content.SharedPreferences; import android.util.Log; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; -import org.mozilla.gecko.PrefsHelper; -import org.mozilla.gecko.PrefsHelper.PrefHandler; import org.mozilla.gecko.background.healthreport.EnvironmentBuilder; import org.mozilla.gecko.background.healthreport.HealthReportDatabaseStorage; import org.mozilla.gecko.background.healthreport.HealthReportStorage.Field; import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields; import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec; import org.mozilla.gecko.background.healthreport.ProfileInformationCache; @@ -33,40 +31,41 @@ import org.mozilla.gecko.util.ThreadUtil import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Iterator; import java.util.Scanner; import java.util.concurrent.atomic.AtomicBoolean; /** * BrowserHealthRecorder is the browser's interface to the Firefox Health * Report storage system. It manages environments (a collection of attributes * that are tracked longitudinally) on the browser's behalf, exposing a simpler * interface for recording changes. * * Keep an instance of this class around. * * Tell it when an environment attribute has changed: call {@link - * #onBlocklistPrefChanged(boolean)} or {@link - * #onTelemetryPrefChanged(boolean)}, followed by {@link + * #onAppLocaleChanged(String)} followed by {@link * #onEnvironmentChanged()}. * * Use it to record events: {@link #recordSearch(String, String)}. * * Shut it down when you're done being a browser: {@link #close()}. */ public class BrowserHealthRecorder implements GeckoEventListener { private static final String LOG_TAG = "GeckoHealthRec"; + private static final String PREF_ACCEPT_LANG = "intl.accept_languages"; private static final String PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; - private static final String EVENT_ADDONS_ALL = "Addons:All"; + private static final String EVENT_SNAPSHOT = "HealthReport:Snapshot"; private static final String EVENT_ADDONS_CHANGE = "Addons:Change"; private static final String EVENT_ADDONS_UNINSTALLING = "Addons:Uninstalling"; private static final String EVENT_PREF_CHANGE = "Pref:Change"; // This is raised from Gecko and signifies a search via the URL bar (not a bookmarks keyword // search). Using this event (rather than passing the invocation location as an arg) avoids // browser.js having to know about the invocation location. public static final String EVENT_KEYWORD_SEARCH = "Search:Keyword"; @@ -237,18 +236,25 @@ public class BrowserHealthRecorder imple * This changes in certain circumstances; be sure to use the current value when recording data. */ private void setHealthEnvironment(final int env) { this.env = env; } /** * This constructor does IO. Run it on a background thread. + * + * appLocale can be null, which indicates that it will be provided later. */ - public BrowserHealthRecorder(final Context context, final String profilePath, final EventDispatcher dispatcher, SessionInformation previousSession) { + public BrowserHealthRecorder(final Context context, + final String profilePath, + final EventDispatcher dispatcher, + final String osLocale, + final String appLocale, + SessionInformation previousSession) { Log.d(LOG_TAG, "Initializing. Dispatcher is " + dispatcher); this.dispatcher = dispatcher; this.previousSession = previousSession; this.client = EnvironmentBuilder.getContentProviderClient(context); if (this.client == null) { throw new IllegalStateException("Could not fetch Health Report content provider."); } @@ -258,19 +264,22 @@ public class BrowserHealthRecorder imple // Stick around even if we don't have storage: eventually we'll // want to report total failures of FHR storage itself, and this // way callers don't need to worry about whether their health // recorder didn't initialize. this.client.release(); this.client = null; } + // Note that the PIC is not necessarily fully initialized at this point: + // we haven't set the app locale. This must be done before an environment + // is recorded. this.profileCache = new ProfileInformationCache(profilePath); try { - this.initialize(context, profilePath); + this.initialize(context, profilePath, osLocale, appLocale); } catch (Exception e) { Log.e(LOG_TAG, "Exception initializing.", e); } } /** * Shut down database connections, unregister event listeners, and perform * provider-specific uninitialization. @@ -294,32 +303,27 @@ public class BrowserHealthRecorder imple this.storage = null; if (this.client != null) { this.client.release(); this.client = null; } } private void unregisterEventListeners() { - this.dispatcher.unregisterEventListener(EVENT_ADDONS_ALL, this); + this.dispatcher.unregisterEventListener(EVENT_SNAPSHOT, this); this.dispatcher.unregisterEventListener(EVENT_ADDONS_CHANGE, this); this.dispatcher.unregisterEventListener(EVENT_ADDONS_UNINSTALLING, this); this.dispatcher.unregisterEventListener(EVENT_PREF_CHANGE, this); this.dispatcher.unregisterEventListener(EVENT_KEYWORD_SEARCH, this); this.dispatcher.unregisterEventListener(EVENT_SEARCH, this); } - public void onBlocklistPrefChanged(boolean to) { + public void onAppLocaleChanged(String to) { this.profileCache.beginInitialization(); - this.profileCache.setBlocklistEnabled(to); - } - - public void onTelemetryPrefChanged(boolean to) { - this.profileCache.beginInitialization(); - this.profileCache.setTelemetryEnabled(to); + this.profileCache.setAppLocale(to); } public void onAddonChanged(String id, JSONObject json) { this.profileCache.beginInitialization(); try { this.profileCache.updateJSONForAddon(id, json); } catch (IllegalStateException e) { Log.w(LOG_TAG, "Attempted to update add-on cache prior to full init.", e); @@ -335,18 +339,17 @@ public class BrowserHealthRecorder imple } } /** * Call this when a material change might have occurred in the running * environment, such that a new environment should be computed and prepared * for use in future events. * - * Invoke this method after calls that mutate the environment, such as - * {@link #onBlocklistPrefChanged(boolean)}. + * Invoke this method after calls that mutate the environment. * * If this change resulted in a transition between two environments, {@link * #onEnvironmentTransition(int, int)} will be invoked on the background * thread. */ public synchronized void onEnvironmentChanged() { final int previousEnv = this.env; this.env = -1; @@ -486,24 +489,46 @@ public class BrowserHealthRecorder imple Log.w(LOG_TAG, "Couldn't write times.json.", e); } } Log.d(LOG_TAG, "Incorporating environment: profile creation = " + time); return time; } - private void handlePrefValue(final String pref, final boolean value) { - Log.d(LOG_TAG, "Incorporating environment: " + pref + " = " + value); - if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) { - profileCache.setTelemetryEnabled(value); + private void onPrefMessage(final String pref, final JSONObject message) { + Log.d(LOG_TAG, "Incorporating environment: " + pref); + if (PREF_ACCEPT_LANG.equals(pref)) { + // We only record whether this is user-set. + try { + this.profileCache.beginInitialization(); + this.profileCache.setAcceptLangUserSet(message.getBoolean("isUserSet")); + } catch (JSONException ex) { + Log.w(LOG_TAG, "Unexpected JSONException fetching isUserSet for " + pref); + } return; } - if (PREF_BLOCKLIST_ENABLED.equals(pref)) { - profileCache.setBlocklistEnabled(value); + + // (We only handle boolean prefs right now.) + try { + boolean value = message.getBoolean("value"); + + if (AppConstants.TELEMETRY_PREF_NAME.equals(pref)) { + this.profileCache.beginInitialization(); + this.profileCache.setTelemetryEnabled(value); + return; + } + + if (PREF_BLOCKLIST_ENABLED.equals(pref)) { + this.profileCache.beginInitialization(); + this.profileCache.setBlocklistEnabled(value); + return; + } + } catch (JSONException ex) { + Log.w(LOG_TAG, "Unexpected JSONException fetching boolean value for " + pref); return; } Log.w(LOG_TAG, "Unexpected pref: " + pref); } /** * Background init helper. */ @@ -566,57 +591,43 @@ public class BrowserHealthRecorder imple } }); } /** * Add provider-specific initialization in this method. */ private synchronized void initialize(final Context context, - final String profilePath) + final String profilePath, + final String osLocale, + final String appLocale) throws java.io.IOException { Log.d(LOG_TAG, "Initializing profile cache."); this.state = State.INITIALIZING; // If we can restore state from last time, great. if (this.profileCache.restoreUnlessInitialized()) { + this.profileCache.updateLocales(osLocale, appLocale); + this.profileCache.completeInitialization(); + Log.d(LOG_TAG, "Successfully restored state. Initializing storage."); initializeStorage(); return; } // Otherwise, let's initialize it from scratch. this.profileCache.beginInitialization(); this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath)); - - final BrowserHealthRecorder self = this; - - PrefHandler handler = new PrefsHelper.PrefHandlerBase() { - @Override - public void prefValue(String pref, boolean value) { - handlePrefValue(pref, value); - } + this.profileCache.setOSLocale(osLocale); + this.profileCache.setAppLocale(appLocale); - @Override - public void finish() { - Log.d(LOG_TAG, "Requesting all add-ons from Gecko."); - dispatcher.registerEventListener(EVENT_ADDONS_ALL, self); - GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Addons:FetchAll", null)); - // Wait for the broadcast event which completes our initialization. - } - }; - - // Oh, singletons. - PrefsHelper.getPrefs(new String[] { - AppConstants.TELEMETRY_PREF_NAME, - PREF_BLOCKLIST_ENABLED - }, - handler); - Log.d(LOG_TAG, "Requested prefs."); + Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko."); + dispatcher.registerEventListener(EVENT_SNAPSHOT, this); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null)); } /** * Invoked in the background whenever the environment transitions between * two valid values. */ protected void onEnvironmentTransition(int prev, int env) { if (this.state != State.INITIALIZED) { @@ -633,22 +644,32 @@ public class BrowserHealthRecorder imple setCurrentSession(newSession); newSession.recordBegin(editor); editor.commit(); } @Override public void handleMessage(String event, JSONObject message) { try { - if (EVENT_ADDONS_ALL.equals(event)) { - Log.d(LOG_TAG, "Got all add-ons."); + if (EVENT_SNAPSHOT.equals(event)) { + Log.d(LOG_TAG, "Got all add-ons and prefs."); try { - JSONObject addons = message.getJSONObject("json"); + JSONObject json = message.getJSONObject("json"); + JSONObject addons = json.getJSONObject("addons"); Log.i(LOG_TAG, "Persisting " + addons.length() + " add-ons."); profileCache.setJSONForAddons(addons); + + JSONObject prefs = json.getJSONObject("prefs"); + Log.i(LOG_TAG, "Persisting prefs."); + Iterator<?> keys = prefs.keys(); + while (keys.hasNext()) { + String pref = (String) keys.next(); + this.onPrefMessage(pref, prefs.getJSONObject(pref)); + } + profileCache.completeInitialization(); } catch (java.io.IOException e) { Log.e(LOG_TAG, "Error completing profile cache initialization.", e); state = State.INITIALIZATION_FAILED; return; } if (state == State.INITIALIZING) { @@ -670,17 +691,17 @@ public class BrowserHealthRecorder imple this.onAddonChanged(message.getString("id"), message.getJSONObject("json")); this.onEnvironmentChanged(); return; } if (EVENT_PREF_CHANGE.equals(event)) { final String pref = message.getString("pref"); Log.d(LOG_TAG, "Pref changed: " + pref); - handlePrefValue(pref, message.getBoolean("value")); + this.onPrefMessage(pref, message); this.onEnvironmentChanged(); return; } // Searches. if (EVENT_KEYWORD_SEARCH.equals(event)) { // A search via the URL bar. Since we eliminate all other search possibilities // (e.g. bookmarks keyword, search suggestion) when we initially process the
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -5306,17 +5306,20 @@ var FormAssistant = { } }; /** * An object to watch for Gecko status changes -- add-on installs, pref changes * -- and reflect them back to Java. */ let HealthReportStatusListener = { - TELEMETRY_PREF: + PREF_ACCEPT_LANG: "intl.accept_languages", + PREF_BLOCKLIST_ENABLED: "extensions.blocklist.enabled", + + PREF_TELEMETRY_ENABLED: #ifdef MOZ_TELEMETRY_REPORTING // Telemetry pref differs based on build. #ifdef MOZ_TELEMETRY_ON_BY_DEFAULT "toolkit.telemetry.enabledPreRelease", #else "toolkit.telemetry.enabled", #endif #else @@ -5325,40 +5328,62 @@ let HealthReportStatusListener = { init: function () { try { AddonManager.addAddonListener(this); } catch (ex) { console.log("Failed to initialize add-on status listener. FHR cannot report add-on state. " + ex); } - Services.obs.addObserver(this, "Addons:FetchAll", false); - Services.prefs.addObserver("extensions.blocklist.enabled", this, false); - if (this.TELEMETRY_PREF) { - Services.prefs.addObserver(this.TELEMETRY_PREF, this, false); + console.log("Adding HealthReport:RequestSnapshot observer."); + Services.obs.addObserver(this, "HealthReport:RequestSnapshot", false); + Services.prefs.addObserver(this.PREF_ACCEPT_LANG, this, false); + Services.prefs.addObserver(this.PREF_BLOCKLIST_ENABLED, this, false); + if (this.PREF_TELEMETRY_ENABLED) { + Services.prefs.addObserver(this.PREF_TELEMETRY_ENABLED, this, false); } }, uninit: function () { - Services.obs.removeObserver(this, "Addons:FetchAll"); - Services.prefs.removeObserver("extensions.blocklist.enabled", this); - if (this.TELEMETRY_PREF) { - Services.prefs.removeObserver(this.TELEMETRY_PREF, this); + Services.obs.removeObserver(this, "HealthReport:RequestSnapshot"); + Services.prefs.removeObserver(this.PREF_ACCEPT_LANG, this); + Services.prefs.removeObserver(this.PREF_BLOCKLIST_ENABLED, this); + if (this.PREF_TELEMETRY_ENABLED) { + Services.prefs.removeObserver(this.PREF_TELEMETRY_ENABLED, this); } AddonManager.removeAddonListener(this); }, observe: function (aSubject, aTopic, aData) { switch (aTopic) { - case "Addons:FetchAll": - HealthReportStatusListener.sendAllAddonsToJava(); + case "HealthReport:RequestSnapshot": + HealthReportStatusListener.sendSnapshotToJava(); break; case "nsPref:changed": - sendMessageToJava({ type: "Pref:Change", pref: aData, value: Services.prefs.getBoolPref(aData) }); + let response = { + type: "Pref:Change", + pref: aData, + isUserSet: Services.prefs.prefHasUserValue(aData), + }; + + switch (aData) { + case this.PREF_ACCEPT_LANG: + response.value = Services.prefs.getCharPref(aData); + break; + case this.PREF_TELEMETRY_ENABLED: + case this.PREF_BLOCKLIST_ENABLED: + response.value = Services.prefs.getBoolPref(aData); + break; + default: + console.log("Unexpected pref in HealthReportStatusListener: " + aData); + return; + } + + sendMessageToJava(response); break; } }, MILLISECONDS_PER_DAY: 24 * 60 * 60 * 1000, COPY_FIELDS: [ "blocklistState", @@ -5430,35 +5455,64 @@ let HealthReportStatusListener = { }, onPropertyChanged: function (aAddon, aProperties) { this.notifyJava(aAddon); }, onOperationCancelled: function (aAddon) { this.notifyJava(aAddon); }, - sendAllAddonsToJava: function () { + sendSnapshotToJava: function () { AddonManager.getAllAddons(function (aAddons) { - let json = {}; + let jsonA = {}; if (aAddons) { for (let i = 0; i < aAddons.length; ++i) { let addon = aAddons[i]; try { let addonJSON = HealthReportStatusListener.jsonForAddon(addon); if (HealthReportStatusListener._shouldIgnore(addon)) { addonJSON.ignore = true; } - json[addon.id] = addonJSON; + jsonA[addon.id] = addonJSON; } catch (e) { // Just skip this add-on. } } } - sendMessageToJava({ type: "Addons:All", json: json }); - }); + + // Now add prefs. + let jsonP = {}; + for (let pref of [this.PREF_BLOCKLIST_ENABLED, this.PREF_TELEMETRY_ENABLED]) { + if (!pref) { + // This will be the case for PREF_TELEMETRY_ENABLED in developer builds. + continue; + } + jsonP[pref] = { + pref: pref, + value: Services.prefs.getBoolPref(pref), + isUserSet: Services.prefs.prefHasUserValue(pref), + }; + } + for (let pref of [this.PREF_ACCEPT_LANG]) { + jsonP[pref] = { + pref: pref, + value: Services.prefs.getCharPref(pref), + isUserSet: Services.prefs.prefHasUserValue(pref), + }; + } + + console.log("Sending snapshot message."); + sendMessageToJava({ + type: "HealthReport:Snapshot", + json: { + addons: jsonA, + prefs: jsonP, + }, + }); + }.bind(this)); }, }; var XPInstallObserver = { init: function xpi_init() { Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false); Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);