Bug 1279501 - add telemetry for the number of bookmarks, history visits and logins are imported from another browser, r=bsmedberg,mak
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Sat, 22 Oct 2016 14:40:48 +0100
changeset 320430 b99f3a05db461e9a4384d95a95c7ecbfde863bc4
parent 320429 3e2b2444b4e66b3d7211c1c3fae13cc5ada93979
child 320431 36105103e8699a45893b16745e8b8f85daebf82a
push id30901
push userkwierso@gmail.com
push dateThu, 03 Nov 2016 00:16:45 +0000
treeherdermozilla-central@ac55a6776435 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, mak
bugs1279501
milestone52.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
Bug 1279501 - add telemetry for the number of bookmarks, history visits and logins are imported from another browser, r=bsmedberg,mak MozReview-Commit-ID: EiZfkj6AsVL
browser/components/migration/360seProfileMigrator.js
browser/components/migration/ChromeProfileMigrator.js
browser/components/migration/EdgeProfileMigrator.js
browser/components/migration/IEProfileMigrator.js
browser/components/migration/MSMigrationUtils.jsm
browser/components/migration/MigrationUtils.jsm
browser/components/migration/SafariProfileMigrator.js
browser/components/migration/tests/unit/test_Chrome_passwords.js
browser/components/migration/tests/unit/test_Edge_db_migration.js
browser/components/migration/tests/unit/test_IE7_passwords.js
browser/components/migration/tests/unit/test_IE_bookmarks.js
browser/components/migration/tests/unit/test_Safari_bookmarks.js
toolkit/components/telemetry/Histograms.json
--- a/browser/components/migration/360seProfileMigrator.js
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -150,25 +150,25 @@ Bookmarks.prototype = {
               parentGuid =
                 yield MigrationUtils.createImportedBookmarksFolder("360se", parentGuid);
             }
             idToGuid.set("fallback", parentGuid);
           }
 
           try {
             if (is_folder == 1) {
-              let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
+              let newFolderGuid = (yield MigrationUtils.insertBookmarkWrapper({
                 parentGuid,
                 type: PlacesUtils.bookmarks.TYPE_FOLDER,
                 title
               })).guid;
 
               idToGuid.set(id, newFolderGuid);
             } else {
-              yield PlacesUtils.bookmarks.insert({
+              yield MigrationUtils.insertBookmarkWrapper({
                 parentGuid,
                 url,
                 title
               });
             }
           } catch (ex) {
             Cu.reportError(ex);
           }
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -83,21 +83,21 @@ function* insertBookmarkItems(parentGuid
   for (let item of items) {
     try {
       if (item.type == "url") {
         if (item.url.trim().startsWith("chrome:")) {
           // Skip invalid chrome URIs. Creating an actual URI always reports
           // messages to the console, so we avoid doing that.
           continue;
         }
-        yield PlacesUtils.bookmarks.insert({
+        yield MigrationUtils.insertBookmarkWrapper({
           parentGuid, url: item.url, title: item.name
         });
       } else if (item.type == "folder") {
-        let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
+        let newFolderGuid = (yield MigrationUtils.insertBookmarkWrapper({
           parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title: item.name
         })).guid;
 
         yield insertBookmarkItems(newFolderGuid, item.children, errorAccumulator);
       }
     } catch (e) {
       Cu.reportError(e);
       errorAccumulator(e);
@@ -334,17 +334,17 @@ function GetHistoryResource(aProfileFold
             });
           } catch (e) {
             Cu.reportError(e);
           }
         }
 
         if (places.length > 0) {
           yield new Promise((resolve, reject) => {
-            PlacesUtils.asyncHistory.updatePlaces(places, {
+            MigrationUtils.insertVisitsWrapper(places, {
               _success: false,
               handleResult: function() {
                 // Importing any entry is considered a successful import.
                 this._success = true;
               },
               handleError: function() {},
               handleCompletion: function() {
                 if (this._success) {
@@ -442,17 +442,17 @@ function GetWindowsPasswordsResource(aPr
       let crypto = new OSCrypto();
 
       for (let row of rows) {
         let loginInfo = {
           username: row.getResultByName("username_value"),
           password: crypto.
                     decryptData(crypto.arrayToString(row.getResultByName("password_value")),
                                                      null),
-          hostName: NetUtil.newURI(row.getResultByName("origin_url")).prePath,
+          hostname: NetUtil.newURI(row.getResultByName("origin_url")).prePath,
           submitURL: null,
           httpRealm: null,
           usernameElement: row.getResultByName("username_element"),
           passwordElement: row.getResultByName("password_element"),
           timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
           timesUsed: row.getResultByName("times_used") + 0,
         };
 
@@ -460,42 +460,23 @@ function GetWindowsPasswordsResource(aPr
           switch (row.getResultByName("scheme")) {
             case AUTH_TYPE.SCHEME_HTML:
               loginInfo.submitURL = NetUtil.newURI(row.getResultByName("action_url")).prePath;
               break;
             case AUTH_TYPE.SCHEME_BASIC:
             case AUTH_TYPE.SCHEME_DIGEST:
               // signon_realm format is URIrealm, so we need remove URI
               loginInfo.httpRealm = row.getResultByName("signon_realm")
-                                    .substring(loginInfo.hostName.length + 1);
+                                       .substring(loginInfo.hostname.length + 1);
               break;
             default:
               throw new Error("Login data scheme type not supported: " +
                               row.getResultByName("scheme"));
           }
-          let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
-
-          login.init(loginInfo.hostName, loginInfo.submitURL, loginInfo.httpRealm,
-                     loginInfo.username, loginInfo.password, loginInfo.usernameElement,
-                     loginInfo.passwordElement);
-          login.QueryInterface(Ci.nsILoginMetaInfo);
-          login.timeCreated = loginInfo.timeCreated;
-          login.timeLastUsed = loginInfo.timeCreated;
-          login.timePasswordChanged = loginInfo.timeCreated;
-          login.timesUsed = loginInfo.timesUsed;
-
-          // Add the login only if there's not an existing entry
-          let logins = Services.logins.findLogins({}, login.hostname,
-                                                  login.formSubmitURL,
-                                                  login.httpRealm);
-
-          // Bug 1187190: Password changes should be propagated depending on timestamps.
-          if (!logins.some(l => login.matches(l, true))) {
-            Services.logins.addLogin(login);
-          }
+          MigrationUtils.insertLoginWrapper(loginInfo);
         } catch (e) {
           Cu.reportError(e);
         }
       }
       crypto.finalize();
       aCallback(true);
     }),
   };
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -133,17 +133,17 @@ EdgeTypedURLMigrator.prototype = {
       });
     }
 
     if (places.length == 0) {
       aCallback(typedURLs.size == 0);
       return;
     }
 
-    PlacesUtils.asyncHistory.updatePlaces(places, {
+    MigrationUtils.insertVisitsWrapper(places, {
       _success: false,
       handleResult: function() {
         // Importing any entry is considered a successful import.
         this._success = true;
       },
       handleError: function() {},
       handleCompletion: function() {
         aCallback(this._success);
@@ -196,17 +196,17 @@ EdgeReadingListMigrator.prototype = {
     if (!readingListItems.length) {
       return;
     }
 
     let destFolderGuid = yield this._ensureReadingListFolder(parentGuid);
     let exceptionThrown;
     for (let item of readingListItems) {
       let dateAdded = item.AddedDate || new Date();
-      yield PlacesUtils.bookmarks.insert({
+      yield MigrationUtils.insertBookmarkWrapper({
         parentGuid: destFolderGuid, url: item.URL, title: item.Title, dateAdded
       }).catch(ex => {
         if (!exceptionThrown) {
           exceptionThrown = ex;
         }
         Cu.reportError(ex);
       });
     }
@@ -214,17 +214,17 @@ EdgeReadingListMigrator.prototype = {
       throw exceptionThrown;
     }
   }),
 
   _ensureReadingListFolder: Task.async(function*(parentGuid) {
     if (!this.__readingListFolderGuid) {
       let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
       let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
-      this.__readingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid;
+      this.__readingListFolderGuid = (yield MigrationUtils.insertBookmarkWrapper(folderSpec)).guid;
     }
     return this.__readingListFolderGuid;
   }),
 };
 
 function EdgeBookmarksMigrator(dbOverride) {
   this.dbOverride = dbOverride;
 }
@@ -317,17 +317,17 @@ EdgeBookmarksMigrator.prototype = {
       }
       let placesInfo = {
         parentGuid,
         url: bookmark.URL,
         dateAdded: bookmark.DateUpdated || new Date(),
         title: bookmark.Title,
       };
 
-      yield PlacesUtils.bookmarks.insert(placesInfo).catch(ex => {
+      yield MigrationUtils.insertBookmarkWrapper(placesInfo).catch(ex => {
         if (!exceptionThrown) {
           exceptionThrown = ex;
         }
         Cu.reportError(ex);
       });
     }
 
     if (exceptionThrown) {
@@ -385,17 +385,17 @@ EdgeBookmarksMigrator.prototype = {
     let parentGuid = yield this._getGuidForFolder(folder.ParentId, folderMap, rootGuid);
     let folderInfo = {
       title: folder.Title,
       type: PlacesUtils.bookmarks.TYPE_FOLDER,
       dateAdded: folder.DateUpdated || new Date(),
       parentGuid,
     };
     // and add ourselves as a kid, and return the guid we got.
-    let parentBM = yield PlacesUtils.bookmarks.insert(folderInfo);
+    let parentBM = yield MigrationUtils.insertBookmarkWrapper(folderInfo);
     folder._guid = parentBM.guid;
     return folder._guid;
   }),
 };
 
 function EdgeProfileMigrator() {
   this.wrappedJSObject = this;
 }
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -14,17 +14,16 @@ const kMainKey = "Software\\Microsoft\\I
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
 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"); /* globals MigratorPrototype */
 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",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
@@ -88,17 +87,17 @@ History.prototype = {
     }
 
     // Check whether there is any history to import.
     if (places.length == 0) {
       aCallback(true);
       return;
     }
 
-    PlacesUtils.asyncHistory.updatePlaces(places, {
+    MigrationUtils.insertVisitsWrapper(places, {
       _success: false,
       handleResult: function() {
         // Importing any entry is considered a successful import.
         this._success = true;
       },
       handleError: function() {},
       handleCompletion: function() {
         aCallback(this._success);
@@ -244,17 +243,17 @@ IE7FormPasswords.prototype = {
       try {
         // create a new login
         let login = {
           username: ieLogin.username,
           password: ieLogin.password,
           hostname: ieLogin.url,
           timeCreated: ieLogin.creation,
         };
-        LoginHelper.maybeImportLogin(login);
+        MigrationUtils.insertLoginWrapper(login);
       } catch (e) {
         Cu.reportError(e);
       }
     }
   },
 
   /**
    * Extract the details of one or more logins from the raw decrypted data.
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -8,17 +8,16 @@ this.EXPORTED_SYMBOLS = ["MSMigrationUti
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 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/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
-Cu.import("resource://gre/modules/LoginHelper.jsm");
 
 Cu.importGlobalProperties(["FileReader"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
@@ -402,17 +401,17 @@ Bookmarks.prototype = {
             folderGuid = PlacesUtils.bookmarks.toolbarGuid;
             if (!MigrationUtils.isStartupMigration) {
               folderGuid =
                 yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
             }
           }
           else {
             // Import to a new folder.
-            folderGuid = (yield PlacesUtils.bookmarks.insert({
+            folderGuid = (yield MigrationUtils.insertBookmarkWrapper({
               type: PlacesUtils.bookmarks.TYPE_FOLDER,
               parentGuid: aDestFolderGuid,
               title: entry.leafName
             })).guid;
           }
 
           if (entry.isReadable()) {
             // Recursively import the folder.
@@ -424,17 +423,17 @@ Bookmarks.prototype = {
           // and get the associated title.
           let matches = entry.leafName.match(/(.+)\.url$/i);
           if (matches) {
             let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
                               getService(Ci.nsIFileProtocolHandler);
             let uri = fileHandler.readURLFile(entry);
             let title = matches[1];
 
-            yield PlacesUtils.bookmarks.insert({
+            yield MigrationUtils.insertBookmarkWrapper({
               parentGuid: aDestFolderGuid, url: uri, title
             });
           }
         }
       } catch (ex) {
         Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex);
         succeeded = false;
       }
@@ -833,17 +832,17 @@ WindowsVaultFormPasswords.prototype = {
             // Ignore exceptions in the dates and just create the login for right now.
           }
           // create a new login
           let login = {
             username, password,
             hostname: realURL.prePath,
             timeCreated: creation,
           };
-          LoginHelper.maybeImportLogin(login);
+          MigrationUtils.insertLoginWrapper(login);
 
           // close current item
           error = ctypesVaultHelpers._functions.VaultFree(credential);
           if (error == FREE_CLOSE_FAILED) {
             throw new Error("Unable to free item: " + error);
           }
         } catch (e) {
           migrationSucceeded = false;
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -15,16 +15,18 @@ Cu.import("resource://gre/modules/AppCon
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
                                   "resource:///modules/AutoMigrate.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+                                  "resource://gre/modules/LoginHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
@@ -249,16 +251,27 @@ this.MigratorPrototype = {
     };
     let maybeStopTelemetryStopwatch = (resourceType, resource) => {
       let histogram = getHistogramForResourceType(resourceType);
       if (histogram) {
         TelemetryStopwatch.finishKeyed(histogram, this.getKey(), resource);
       }
     };
 
+    let collectQuantityTelemetry = () => {
+      try {
+        for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
+          let histogramId =
+            "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
+          let histogram = Services.telemetry.getKeyedHistogram(histogramId);
+          histogram.add(this.getKey(), MigrationUtils._importQuantities[resourceType]);
+        }
+      } catch (ex) { /* Telemetry is exception-happy */ }
+    };
+
     // Called either directly or through the bookmarks import callback.
     let doMigrate = Task.async(function*() {
       let resourcesGroupedByItems = new Map();
       resources.forEach(function(resource) {
         if (!resourcesGroupedByItems.has(resource.type)) {
           resourcesGroupedByItems.set(resource.type, new Set());
         }
         resourcesGroupedByItems.get(resource.type).add(resource);
@@ -266,16 +279,19 @@ this.MigratorPrototype = {
 
       if (resourcesGroupedByItems.size == 0)
         throw new Error("No items to import");
 
       let notify = function(aMsg, aItemType) {
         Services.obs.notifyObservers(null, aMsg, aItemType);
       };
 
+      for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
+        MigrationUtils._importQuantities[resourceType] = 0;
+      }
       notify("Migration:Started");
       for (let [key, value] of resourcesGroupedByItems) {
         // Workaround bug 449811.
         let migrationType = key, itemResources = value;
 
         notify("Migration:ItemBeforeMigrate", migrationType);
 
         let itemSuccess = false;
@@ -289,16 +305,17 @@ this.MigratorPrototype = {
             itemResources.delete(resource);
             itemSuccess |= aSuccess;
             if (itemResources.size == 0) {
               notify(itemSuccess ?
                      "Migration:ItemAfterMigrate" : "Migration:ItemError",
                      migrationType);
               resourcesGroupedByItems.delete(migrationType);
               if (resourcesGroupedByItems.size == 0) {
+                collectQuantityTelemetry();
                 notify("Migration:Ended");
               }
             }
             completeDeferred.resolve();
           };
 
           // If migrate throws, an error occurred, and the callback
           // (itemMayBeDone) might haven't been called.
@@ -907,16 +924,37 @@ this.MigrationUtils = Object.freeze({
       migrator,
       aProfileStartup,
       skipSourcePage,
       aProfileToMigrate,
     ];
     this.showMigrationWizard(null, params);
   },
 
+  _importQuantities: {
+    bookmarks: 0,
+    logins: 0,
+    history: 0,
+  },
+
+  insertBookmarkWrapper(bookmark) {
+    this._importQuantities.bookmarks++;
+    return PlacesUtils.bookmarks.insert(bookmark);
+  },
+
+  insertVisitsWrapper(places, options) {
+    this._importQuantities.history += places.length;
+    return PlacesUtils.asyncHistory.updatePlaces(places, options);
+  },
+
+  insertLoginWrapper(login) {
+    this._importQuantities.logins++;
+    return LoginHelper.maybeImportLogin(login);
+  },
+
   /**
    * Cleans up references to migrators and nsIProfileInstance instances.
    */
   finishMigration: function MU_finishMigration() {
     gMigrators = null;
     gProfileStartup = null;
     gMigrationBundle = null;
   },
--- a/browser/components/migration/SafariProfileMigrator.js
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -126,17 +126,17 @@ Bookmarks.prototype = {
             yield MigrationUtils.createImportedBookmarksFolder("Safari", folderGuid);
         }
         break;
       }
       case this.READING_LIST_COLLECTION: {
         // Reading list items are imported as regular bookmarks.
         // They are imported under their own folder, created either under the
         // bookmarks menu (in the case of startup migration).
-        folderGuid = (yield PlacesUtils.bookmarks.insert({
+        folderGuid = (yield MigrationUtils.insertBookmarkWrapper({
           parentGuid: PlacesUtils.bookmarks.menuGuid,
           type: PlacesUtils.bookmarks.TYPE_FOLDER,
           title: MigrationUtils.getLocalizedString("importedSafariReadingList"),
         })).guid;
         break;
       }
       default:
         throw new Error("Unexpected value for aCollection!");
@@ -149,31 +149,31 @@ Bookmarks.prototype = {
 
   // migrate the given array of safari bookmarks to the given places
   // folder.
   _migrateEntries: Task.async(function* (entries, parentGuid) {
     for (let entry of entries) {
       let type = entry.get("WebBookmarkType");
       if (type == "WebBookmarkTypeList" && entry.has("Children")) {
         let title = entry.get("Title");
-        let newFolderGuid = (yield PlacesUtils.bookmarks.insert({
+        let newFolderGuid = (yield MigrationUtils.insertBookmarkWrapper({
           parentGuid, type: PlacesUtils.bookmarks.TYPE_FOLDER, title
         })).guid;
 
         // Empty folders may not have a children array.
         if (entry.has("Children"))
           yield this._migrateEntries(entry.get("Children"), newFolderGuid, false);
       }
       else if (type == "WebBookmarkTypeLeaf" && entry.has("URLString")) {
         let title;
         if (entry.has("URIDictionary"))
           title = entry.get("URIDictionary").get("title");
 
         try {
-          yield PlacesUtils.bookmarks.insert({
+          yield MigrationUtils.insertBookmarkWrapper({
             parentGuid, url: entry.get("URLString"), title
           });
         } catch (ex) {
           Cu.reportError("Invalid Safari bookmark: " + ex);
         }
       }
     }
   })
@@ -225,17 +225,17 @@ History.prototype = {
             catch (ex) {
               // Safari's History file may contain malformed URIs which
               // will be ignored.
               Cu.reportError(ex);
             }
           }
         }
         if (places.length > 0) {
-          PlacesUtils.asyncHistory.updatePlaces(places, {
+          MigrationUtils.insertVisitsWrapper(places, {
             _success: false,
             handleResult: function() {
               // Importing any entry is considered a successful import.
               this._success = true;
             },
             handleError: function() {},
             handleCompletion: function() {
               aCallback(this._success);
--- a/browser/components/migration/tests/unit/test_Chrome_passwords.js
+++ b/browser/components/migration/tests/unit/test_Chrome_passwords.js
@@ -168,16 +168,18 @@ add_task(function* test_importIntoEmptyD
   let logins = Services.logins.getAllLogins({});
   Assert.equal(logins.length, 0, "There are no logins initially");
 
   // Migrate the logins.
   yield promiseMigration(migrator, MigrationUtils.resourceTypes.PASSWORDS, PROFILE);
 
   logins = Services.logins.getAllLogins({});
   Assert.equal(logins.length, TEST_LOGINS.length, "Check login count after importing the data");
+  Assert.equal(logins.length, MigrationUtils._importQuantities.logins,
+               "Check telemetry matches the actual import.");
 
   for (let i = 0; i < TEST_LOGINS.length; i++) {
     checkLoginsAreEqual(logins[i], TEST_LOGINS[i], i + 1);
   }
 });
 
 // Test that existing logins for the same primary key don't get overwritten
 add_task(function* test_importExistingLogins() {
@@ -203,13 +205,15 @@ add_task(function* test_importExistingLo
     checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
   }
   // Migrate the logins.
   yield promiseMigration(migrator, MigrationUtils.resourceTypes.PASSWORDS, PROFILE);
 
   logins = Services.logins.getAllLogins({});
   Assert.equal(logins.length, TEST_LOGINS.length,
                "Check there are still the same number of logins after re-importing the data");
+  Assert.equal(logins.length, MigrationUtils._importQuantities.logins,
+               "Check telemetry matches the actual import.");
 
   for (let i = 0; i < newLogins.length; i++) {
     checkLoginsAreEqual(logins[i], newLogins[i], i + 1);
   }
 });
--- a/browser/components/migration/tests/unit/test_Edge_db_migration.js
+++ b/browser/components/migration/tests/unit/test_Edge_db_migration.js
@@ -382,16 +382,19 @@ add_task(function*() {
     {type: COLUMN_TYPES.JET_coltypGUID, name: "ParentId"},
   ], itemsInDB);
 
   let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=edge"]
                  .createInstance(Ci.nsIBrowserProfileMigrator);
   let bookmarksMigrator = migrator.wrappedJSObject.getESEMigratorForTesting(db);
   Assert.ok(bookmarksMigrator.exists, "Should recognize table we just created");
 
+  let source = MigrationUtils.getLocalizedString("sourceNameEdge");
+  let sourceLabel = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
+
   let seenBookmarks = [];
   let bookmarkObserver = {
     onItemAdded(itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid) {
       if (title.startsWith("Deleted")) {
         ok(false, "Should not see deleted items being bookmarked!");
       }
       seenBookmarks.push({itemId, parentId, index, itemType, url, title, dateAdded, itemGuid, parentGuid});
     },
@@ -407,16 +410,19 @@ add_task(function*() {
   let migrateResult = yield new Promise(resolve => bookmarksMigrator.migrate(resolve)).catch(ex => {
     Cu.reportError(ex);
     Assert.ok(false, "Got an exception trying to migrate data! " + ex);
     return false;
   });
   PlacesUtils.bookmarks.removeObserver(bookmarkObserver);
   Assert.ok(migrateResult, "Migration should succeed");
   Assert.equal(seenBookmarks.length, 7, "Should have seen 7 items being bookmarked.");
+  Assert.equal(seenBookmarks.filter(bm => bm.title != sourceLabel).length,
+               MigrationUtils._importQuantities.bookmarks,
+               "Telemetry should have items except for 'From Microsoft Edge' folders");
 
   let menuParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.menuGuid);
   Assert.equal(menuParents.length, 1, "Should have a single folder added to the menu");
   let toolbarParents = seenBookmarks.filter(item => item.parentGuid == PlacesUtils.bookmarks.toolbarGuid);
   Assert.equal(toolbarParents.length, 1, "Should have a single item added to the toolbar");
   let menuParentGuid = menuParents[0].itemGuid;
   let toolbarParentGuid = toolbarParents[0].itemGuid;
 
--- a/browser/components/migration/tests/unit/test_IE7_passwords.js
+++ b/browser/components/migration/tests/unit/test_IE7_passwords.js
@@ -373,16 +373,25 @@ add_task(function* test_passwordsAvailab
 
     migrator._migrateURIs(uris);
     logins = Services.logins.getAllLogins({});
     // check that the number of logins in the password manager has increased as expected which means
     // that all the values for the current website were imported
     loginCount += website.logins.length;
     Assert.equal(logins.length, loginCount,
                  "The number of logins has increased after the migration");
+    // NB: because telemetry records any login data passed to the login manager, it
+    // also gets told about logins that are duplicates or invalid (for one reason
+    // or another) and so its counts might exceed those of the login manager itself.
+    Assert.greaterOrEqual(MigrationUtils._importQuantities.logins, loginCount,
+                          "Telemetry quantities equal or exceed the actual import.");
+    // Reset - this normally happens at the start of a new migration, but we're calling
+    // the migrator directly so can't rely on that:
+    MigrationUtils._importQuantities.logins = 0;
+
     let startIndex = loginCount - website.logins.length;
     // compares the imported password manager logins with their expected logins
     for (let i = 0; i < website.logins.length; i++) {
       checkLoginsAreEqual(logins[startIndex + i], website.logins[i],
                           " " + current + " - " + i + " ");
     }
   }
 });
--- a/browser/components/migration/tests/unit/test_IE_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_IE_bookmarks.js
@@ -8,31 +8,37 @@ add_task(function* () {
   // Wait for the imported bookmarks.  Check that "From Internet Explorer"
   // folders are created in the menu and on the toolbar.
   let source = MigrationUtils.getLocalizedString("sourceNameIE");
   let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
 
   let expectedParents = [ PlacesUtils.bookmarksMenuFolderId,
                           PlacesUtils.toolbarFolderId ];
 
-  PlacesUtils.bookmarks.addObserver({
+  let itemCount = 0;
+  let bmObserver = {
     onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
-      if (aTitle == label) {
+      if (aTitle != label) {
+        itemCount++;
+      }
+      if (expectedParents.length > 0 && aTitle == label) {
         let index = expectedParents.indexOf(aParentId);
         Assert.notEqual(index, -1);
         expectedParents.splice(index, 1);
-        if (expectedParents.length == 0)
-          PlacesUtils.bookmarks.removeObserver(this);
       }
     },
     onBeginUpdateBatch() {},
     onEndUpdateBatch() {},
     onItemRemoved() {},
     onItemChanged() {},
     onItemVisited() {},
     onItemMoved() {},
-  }, false);
+  };
+  PlacesUtils.bookmarks.addObserver(bmObserver, false);
 
   yield promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
+  PlacesUtils.bookmarks.removeObserver(bmObserver);
+  Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount,
+               "Ensure telemetry matches actual number of imported items.");
 
   // Check the bookmarks have been imported to all the expected parents.
-  Assert.equal(expectedParents.length, 0);
+  Assert.equal(expectedParents.length, 0, "Got all the expected parents");
 });
--- a/browser/components/migration/tests/unit/test_Safari_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_Safari_bookmarks.js
@@ -8,32 +8,39 @@ add_task(function* () {
   Assert.ok(migrator.sourceExists);
 
   // Wait for the imported bookmarks.  Check that "From Safari"
   // folders are created on the toolbar.
   let source = MigrationUtils.getLocalizedString("sourceNameSafari");
   let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
 
   let expectedParents = [ PlacesUtils.toolbarFolderId ];
+  let itemCount = 0;
 
-  PlacesUtils.bookmarks.addObserver({
+  let bmObserver = {
     onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
-      if (aTitle == label) {
+      if (aTitle != label) {
+        itemCount++;
+      }
+      if (expectedParents.length > 0 && aTitle == label) {
         let index = expectedParents.indexOf(aParentId);
-        Assert.notEqual(index, -1);
+        Assert.ok(index != -1, "Found expected parent");
         expectedParents.splice(index, 1);
-        if (expectedParents.length == 0)
-          PlacesUtils.bookmarks.removeObserver(this);
       }
     },
     onBeginUpdateBatch() {},
     onEndUpdateBatch() {},
     onItemRemoved() {},
     onItemChanged() {},
     onItemVisited() {},
     onItemMoved() {},
-  }, false);
+  };
+  PlacesUtils.bookmarks.addObserver(bmObserver, false);
 
   yield promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
+  PlacesUtils.bookmarks.removeObserver(bmObserver);
 
   // Check the bookmarks have been imported to all the expected parents.
-  Assert.equal(expectedParents.length, 0);
+  Assert.ok(!expectedParents.length, "No more expected parents");
+  Assert.equal(itemCount, 13, "Should import all 13 items.");
+  // Check that the telemetry matches:
+  Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount, "Telemetry reporting correct.");
 });
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4837,16 +4837,49 @@
     "expires_in_version": "54",
     "kind": "exponential",
     "n_buckets": 70,
     "high": 100000,
     "releaseChannelCollection": "opt-out",
     "keyed": true,
     "description": "How long it took to import logins (passwords) from another browser, keyed by the name of the browser."
   },
+  "FX_MIGRATION_BOOKMARKS_QUANTITY": {
+    "bug_numbers": [1279501],
+    "alert_emails": ["gijs@mozilla.com"],
+    "expires_in_version": "56",
+    "kind": "exponential",
+    "n_buckets": 20,
+    "high": 1000,
+    "releaseChannelCollection": "opt-out",
+    "keyed": true,
+    "description": "How many bookmarks we imported from another browser, keyed by the name of the browser."
+  },
+  "FX_MIGRATION_HISTORY_QUANTITY": {
+    "bug_numbers": [1279501],
+    "alert_emails": ["gijs@mozilla.com"],
+    "expires_in_version": "56",
+    "kind": "exponential",
+    "n_buckets": 40,
+    "high": 10000,
+    "releaseChannelCollection": "opt-out",
+    "keyed": true,
+    "description": "How many history visits we imported from another browser, keyed by the name of the browser."
+  },
+  "FX_MIGRATION_LOGINS_QUANTITY": {
+    "bug_numbers": [1279501],
+    "alert_emails": ["gijs@mozilla.com"],
+    "expires_in_version": "56",
+    "kind": "exponential",
+    "n_buckets": 20,
+    "high": 1000,
+    "releaseChannelCollection": "opt-out",
+    "keyed": true,
+    "description": "How many logins (passwords) we imported from another browser, keyed by the name of the browser."
+  },
   "FX_STARTUP_MIGRATION_BROWSER_COUNT": {
     "bug_numbers": [1275114],
     "alert_emails": ["gijs@mozilla.com"],
     "expires_in_version": "53",
     "kind": "enumerated",
     "n_values": 15,
     "releaseChannelCollection": "opt-out",
     "description": "Number of browsers from which the user could migrate on initial profile migration. Only available on release builds during firstrun."