Bug 1276694 - store recency of browser data in telemetry when importing to see how good a predictor default browser is, r?dolske,bsmedberg draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Mon, 30 May 2016 17:10:54 +0100
changeset 380787 fdb3ad8a3429079cb23cddd4a003b03043a95728
parent 380786 dbe8807bcf0db60680a940eaaaf01f4488c6bf22
child 380788 34134ccfb413eae7d25c811008a8dc647dbbeba4
push id21320
push usergijskruitbosch@gmail.com
push dateThu, 23 Jun 2016 08:38:30 +0000
reviewersdolske, bsmedberg
bugs1276694
milestone50.0a1
Bug 1276694 - store recency of browser data in telemetry when importing to see how good a predictor default browser is, r?dolske,bsmedberg MozReview-Commit-ID: 16uPNGGDE68
browser/components/migration/360seProfileMigrator.js
browser/components/migration/ChromeProfileMigrator.js
browser/components/migration/EdgeProfileMigrator.js
browser/components/migration/FirefoxProfileMigrator.js
browser/components/migration/IEProfileMigrator.js
browser/components/migration/MigrationUtils.jsm
browser/components/migration/SafariProfileMigrator.js
browser/components/migration/content/migration.js
browser/components/migration/nsIBrowserProfileMigrator.idl
toolkit/components/telemetry/Histograms.json
--- a/browser/components/migration/360seProfileMigrator.js
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -13,16 +13,18 @@ Cu.import("resource://gre/modules/FileUt
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 
+const kBookmarksFileName = "360sefav.db";
+
 function copyToTempUTF8File(file, charset) {
   let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
                       .createInstance(Ci.nsIFileInputStream);
   inputStream.init(file, -1, -1, 0);
   let inputStr = NetUtil.readInputStreamToString(
     inputStream, inputStream.available(), { charset });
 
   // Use random to reduce the likelihood of a name collision in createUnique.
@@ -90,17 +92,17 @@ function getHash(aStr) {
 
   // convert the binary hash data to a hex string.
   let binary = hasher.finish(false);
   return Array.from(binary, (c, i) => toHexString(binary.charCodeAt(i))).join("").toLowerCase();
 }
 
 function Bookmarks(aProfileFolder) {
   let file = aProfileFolder.clone();
-  file.append("360sefav.db");
+  file.append(kBookmarksFileName);
 
   this._file = file;
 }
 Bookmarks.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
 
   get exists() {
     return this._file.exists() && this._file.isReadable();
@@ -293,13 +295,30 @@ Qihoo360seProfileMigrator.prototype.getR
   }
 
   let resources = [
     new Bookmarks(profileFolder)
   ];
   return resources.filter(r => r.exists);
 };
 
+Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
+  let bookmarksPaths = this.sourceProfiles.map(({id}) => {
+    return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
+  });
+  if (!bookmarksPaths.length) {
+    return Promise.resolve(new Date(0));
+  }
+  let datePromises = bookmarksPaths.map(path => {
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 Qihoo360seProfileMigrator.prototype.classDescription = "360 Secure Browser Profile Migrator";
 Qihoo360seProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=360se";
 Qihoo360seProfileMigrator.prototype.classID = Components.ID("{d0037b95-296a-4a4e-94b2-c3d075d20ab1}");
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Qihoo360seProfileMigrator]);
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -19,16 +19,18 @@ const AUTH_TYPE = {
   SCHEME_DIGEST: 2
 };
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Console.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
@@ -131,16 +133,37 @@ ChromeProfileMigrator.prototype.getResou
           possibleResources.push(GetWindowsPasswordsResource(profileFolder));
         }
         return possibleResources.filter(r => r != null);
       }
     }
     return [];
   };
 
+ChromeProfileMigrator.prototype.getLastUsedDate =
+  function Chrome_getLastUsedDate() {
+    let datePromises = this.sourceProfiles.map(profile => {
+      let profileFolder = this._chromeUserDataFolder.clone();
+      let basePath = OS.Path.join(this._chromeUserDataFolder.path, profile.id);
+      let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(leafName => {
+        let path = OS.Path.join(basePath, leafName);
+        return OS.File.stat(path).catch(_ => null).then(info => {
+          return info ? info.lastModificationDate : 0;
+        });
+      });
+      return Promise.all(fileDatePromises).then(dates => {
+        return Math.max.apply(Math, dates);
+      });
+    });
+    return Promise.all(datePromises).then(dates => {
+      dates.push(0);
+      return new Date(Math.max.apply(Math, dates));
+    });
+  };
+
 Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
   get: function Chrome_sourceProfiles() {
     if ("__sourceProfiles" in this)
       return this.__sourceProfiles;
 
     if (!this._chromeUserDataFolder)
       return [];
 
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ESEDBReader",
                                   "resource:///modules/ESEDBReader.jsm");
 
 const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" +
@@ -415,16 +416,44 @@ EdgeProfileMigrator.prototype.getResourc
   ];
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
+EdgeProfileMigrator.prototype.getLastUsedDate = function() {
+  // Don't do this if we don't have a single profile (see the comment for
+  // sourceProfiles) or if we can't find the database file:
+  if (this.sourceProfiles !== null || !gEdgeDatabase) {
+    return Promise.resolve(new Date(0));
+  }
+  let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
+  let dbPath = gEdgeDatabase.path;
+  let cookieMigrator = MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE);
+  let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
+  let datePromises = [logFilePath, dbPath, ... cookiePaths].map(path => {
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  datePromises.push(new Promise(resolve => {
+    let typedURLs = new Map();
+    try {
+      typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
+    } catch (ex) {}
+    let times = [0, ... typedURLs.values()];
+    resolve(Math.max.apply(Math, times));
+  }));
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 /* Somewhat counterintuitively, this returns:
  * - |null| to indicate "There is only 1 (default) profile" (on win10+)
  * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
  * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
  */
 EdgeProfileMigrator.prototype.__defineGetter__("sourceProfiles", function() {
   let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
   return isWin10OrHigher ? null : [];
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -92,16 +92,23 @@ FirefoxProfileMigrator.prototype.getReso
 
   // Surely data cannot be imported from the current profile.
   if (sourceProfileDir.equals(currentProfileDir))
     return null;
 
   return this._getResourcesInternal(sourceProfileDir, currentProfileDir, aProfile);
 };
 
+FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
+  // We always pretend we're really old, so that we don't mess
+  // up the determination of which browser is the most 'recent'
+  // to import from.
+  return Promise.resolve(new Date(0));
+};
+
 FirefoxProfileMigrator.prototype._getResourcesInternal = function(sourceProfileDir, currentProfileDir, aProfile) {
   let getFileResource = function(aMigrationType, aFileNames) {
     let files = [];
     for (let fileName of aFileNames) {
       let file = this._getFileObject(sourceProfileDir, fileName);
       if (file)
         files.push(file);
     }
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -8,19 +8,20 @@ const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
 const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
 Cu.import("resource://gre/modules/LoginHelper.jsm");
 
 
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@@ -490,16 +491,36 @@ IEProfileMigrator.prototype.getResources
   }
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
+IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
+  let datePromises = ["Favs", "CookD"].map(dirId => {
+    let {path} = Services.dirsvc.get(dirId, Ci.nsIFile);
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  datePromises.push(new Promise(resolve => {
+    let typedURLs = new Map();
+    try {
+      typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
+    } catch (ex) {}
+    let dates = [0, ... typedURLs.values()];
+    resolve(Math.max.apply(Math, dates));
+  }));
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
   get: function IE_get_sourceHomePageURL() {
     let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                                       kMainKey, "Default_Page_URL");
     let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
                                                kMainKey, "Start Page");
     // If the user didn't customize the Start Page, he is still on the default
     // page, that may be considered the equivalent of our about:home.  There's
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -133,16 +133,30 @@ this.MigratorPrototype = {
    *        aProfile is a value returned by the sourceProfiles getter (see
    *        above).
    */
   getResources: function MP_getResources(aProfile) {
     throw new Error("getResources must be overridden");
   },
 
   /**
+   * OVERRIDE in order to provide an estimate of when the last time was
+   * that somebody used the browser. It is OK that this is somewhat fuzzy -
+   * history may not be available (or be wiped or not present due to e.g.
+   * incognito mode).
+   *
+   * @return a Promise that resolves to the last used date.
+   *
+   * @note If not overridden, the promise will resolve to the unix epoch.
+   */
+  getLastUsedDate() {
+    return Promise.resolve(new Date(0));
+  },
+
+  /**
    * OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
    * that is just the Firefox migrator, see bug 737381).  Default: false.
    *
    * Startup-only migrators are different in two ways:
    * - they may only be used during startup.
    * - the user-profile is half baked during migration.  The folder exists,
    *   but it's only accessible through MigrationUtils.profileStartup.
    *   The migrator can call MigrationUtils.profileStartup.doStartup
--- a/browser/components/migration/SafariProfileMigrator.js
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -4,20 +4,21 @@
 
 "use strict";
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyListUtils",
                                   "resource://gre/modules/PropertyListUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
@@ -635,16 +636,34 @@ SafariProfileMigrator.prototype.getResou
     let wfFile = FileUtils.getFile("UsrPrfs", ["com.apple.WebFoundation.plist"]);
     if (wfFile.exists())
       resources.push(new WebFoundationCookieBehavior(wfFile));
   }
 
   return resources;
 };
 
+SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
+  let profileDir;
+  if (AppConstants.platform == "macosx") {
+    profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
+  } else {
+    profileDir = FileUtils.getDir("AppData", ["Apple Computer", "Safari"], false);
+  }
+  let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
+    let path = OS.Path.join(profileDir.path, file);
+    return OS.File.stat(path).catch(_ => null).then(info => {
+      return info ? info.lastModificationDate : 0;
+    });
+  });
+  return Promise.all(datePromises).then(dates => {
+    return new Date(Math.max.apply(Math, dates));
+  });
+};
+
 Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyList", {
   get: function get_mainPreferencesPropertyList() {
     if (this._mainPreferencesPropertyList === undefined) {
       let file;
       if (AppConstants.platform == "macosx") {
         file = FileUtils.getDir("UsrPrfs", [], false);
       } else {
         file = FileUtils.getDir("AppData", ["Apple Computer", "Preferences"], false);
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -78,39 +78,39 @@ var MigrationWizard = {
         let migrator = MigrationUtils.getMigrator(group.selectedItem.id);
         visibility = migrator.sourceLocked ? "visible" : "hidden";
       }
       document.getElementById("closeSourceBrowser").style.visibility = visibility;
     }
     this._wiz.canRewind = false;
 
     var selectedMigrator = null;
+    this._availableMigrators = [];
 
     // Figure out what source apps are are available to import from:
     var group = document.getElementById("importSourceGroup");
-    var availableMigratorCount = 0;
     for (var i = 0; i < group.childNodes.length; ++i) {
       var migratorKey = group.childNodes[i].id;
       if (migratorKey != "nothing") {
         var migrator = MigrationUtils.getMigrator(migratorKey);
         if (migrator) {
           // Save this as the first selectable item, if we don't already have
           // one, or if it is the migrator that was passed to us.
           if (!selectedMigrator || this._source == migratorKey)
             selectedMigrator = group.childNodes[i];
-          availableMigratorCount++;
+          this._availableMigrators.push([migratorKey, migrator]);
         } else {
           // Hide this option
           group.childNodes[i].hidden = true;
         }
       }
     }
     if (this.isInitialMigration) {
       Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_BROWSER_COUNT")
-        .add(availableMigratorCount);
+        .add(this._availableMigrators.length);
       let defaultBrowser = MigrationUtils.getMigratorKeyForDefaultBrowser();
       // This will record 0 for unknown default browser IDs.
       defaultBrowser = MigrationUtils.getSourceIdForTelemetry(defaultBrowser);
       Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_EXISTING_DEFAULT_BROWSER")
         .add(defaultBrowser);
     }
 
     group.addEventListener("command", toggleCloseBrowserWarning);
@@ -443,16 +443,19 @@ var MigrationWizard = {
         label.setAttribute("style", "font-weight: bold");
       break;
     case "Migration:ItemAfterMigrate":
       label = document.getElementById(aData + "_migrated");
       if (label)
         label.removeAttribute("style");
       break;
     case "Migration:Ended":
+      if (this.isInitialMigration) {
+        this.reportDataRecencyTelemetry();
+      }
       if (this._autoMigrate) {
         Services.telemetry.getKeyedHistogramById("FX_MIGRATION_HOMEPAGE_IMPORTED")
                           .add(this._source, !!this._newHomePage);
         if (this._newHomePage) {
           try {
             // set homepage properly
             var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
                                     .getService(Components.interfaces.nsIPrefService);
@@ -527,10 +530,36 @@ var MigrationWizard = {
     }
   },
 
   onDonePageShow: function ()
   {
     this._wiz.getButton("cancel").disabled = true;
     this._wiz.canRewind = false;
     this._listItems("doneItems");
-  }
+  },
+
+  reportDataRecencyTelemetry() {
+    let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_DATA_RECENCY");
+    let lastUsedPromises = [];
+    for (let [key, migrator] of this._availableMigrators) {
+      // No block-scoped let in for...of loop conditions, so get the source:
+      let localKey = key;
+      lastUsedPromises.push(migrator.getLastUsedDate().then(date => {
+        const ONE_YEAR = 24 * 365;
+        let diffInHours = Math.round((Date.now() - date) / (60 * 60 * 1000));
+        if (diffInHours > ONE_YEAR) {
+          diffInHours = ONE_YEAR;
+        }
+        histogram.add(localKey, diffInHours);
+        return [localKey, diffInHours];
+      }));
+    }
+    Promise.all(lastUsedPromises).then(migratorUsedTimeDiff => {
+      // Sort low to high.
+      migratorUsedTimeDiff.sort(([keyA, diffA], [keyB, diffB]) => diffA - diffB);
+      let usedMostRecentBrowser = migratorUsedTimeDiff.length && this._source == migratorUsedTimeDiff[0][0];
+      let usedRecentBrowser =
+        Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_USED_RECENT_BROWSER");
+      usedRecentBrowser.add(this._source, usedMostRecentBrowser);
+    });
+  },
 };
--- a/browser/components/migration/nsIBrowserProfileMigrator.idl
+++ b/browser/components/migration/nsIBrowserProfileMigrator.idl
@@ -39,16 +39,22 @@ interface nsIBrowserProfileMigrator : ns
    *          to import
    * @param   aDoingStartup "true" if the profile is not currently being used.
    * @return  bit field containing profile items (see above)
    * @note    a return value of 0 represents no items rather than ALL.
    */
   unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
 
   /**
+   * Get the last time data from this browser was modified
+   * @return a promise that resolves to a JS Date object
+   */
+  jsval getLastUsedDate();
+
+  /**
    * Whether or not there is any data that can be imported from this
    * browser (i.e. whether or not it is installed, and there exists
    * a user profile)
    */
   readonly attribute boolean          sourceExists;
 
 
   /**
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4441,16 +4441,36 @@
     "bug_numbers": [1271775],
     "alert_emails": ["gijs@mozilla.com"],
     "expires_in_version": "53",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
     "description": "Where automatic migration was attempted, indicates to what degree we succeeded."
   },
+  "FX_STARTUP_MIGRATION_DATA_RECENCY": {
+    "bug_numbers": [1276694],
+    "alert_emails": ["gijs@mozilla.com"],
+    "expires_in_version": "53",
+    "keyed": true,
+    "kind": "exponential",
+    "n_buckets": 50,
+    "high": 8760,
+    "releaseChannelCollection": "opt-out",
+    "description": "The 'last modified' time of the data we imported on the initial profile migration (time delta with 'now' at the time of migration, in hours). Collected for all browsers for which migration data is available, and stored keyed by browser identifier (e.g. 'ie', 'edge', 'safari', etc.)."
+  },
+  "FX_STARTUP_MIGRATION_USED_RECENT_BROWSER": {
+    "bug_numbers": [1276694],
+    "alert_emails": ["gijs@mozilla.com"],
+    "expires_in_version": "53",
+    "keyed": true,
+    "kind": "boolean",
+    "releaseChannelCollection": "opt-out",
+    "description": "Whether the browser we migrated from was the browser with the most recent data. Keyed by that browser's identifier (e.g. 'ie', 'edge', 'safari', etc.)."
+  },
   "FX_STARTUP_EXTERNAL_CONTENT_HANDLER": {
     "bug_numbers": [1276027],
     "alert_emails": ["jaws@mozilla.com"],
     "expires_in_version": "53",
     "kind": "count",
     "description": "Count how often the browser is opened as an external app handler. This is generally used when the browser is set as the default browser."
   },
   "INPUT_EVENT_RESPONSE_MS": {