Bug 1395332 - perform some post-profile-migration steps to ensure Sync works correctly in the new profile. r?Gijs draft
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 08 Sep 2017 16:28:37 +1000
changeset 663403 6bc819ce23967833b9eae113fc3e941ba5104983
parent 662594 15128312c02a238ac158589c59fd3ee3e384ce80
child 731197 97c8b6ff4ecad0138d7410b86da208faf19c6567
push id79429
push userbmo:markh@mozilla.com
push dateTue, 12 Sep 2017 23:51:43 +0000
reviewersGijs
bugs1395332
milestone57.0a1
Bug 1395332 - perform some post-profile-migration steps to ensure Sync works correctly in the new profile. r?Gijs MozReview-Commit-ID: EcFdDeqzzCY
browser/components/migration/FirefoxProfileMigrator.js
browser/components/migration/tests/marionette/test_refresh_firefox.py
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -121,23 +121,31 @@ FirefoxProfileMigrator.prototype._getRes
         for (let file of files) {
           file.copyTo(currentProfileDir, "");
         }
         aCallback(true);
       }
     };
   };
 
+  function savePrefs() {
+    // If we've used the pref service to write prefs for the new profile, it's too
+    // early in startup for the service to have a profile directory, so we have to
+    // manually tell it where to save the prefs file.
+    let newPrefsFile = currentProfileDir.clone();
+    newPrefsFile.append("prefs.js");
+    Services.prefs.savePrefFile(newPrefsFile);
+  }
+
   let types = MigrationUtils.resourceTypes;
   let places = getFileResource(types.HISTORY, ["places.sqlite", "places.sqlite-wal"]);
   let favicons = getFileResource(types.HISTORY, ["favicons.sqlite", "favicons.sqlite-wal"]);
   let cookies = getFileResource(types.COOKIES, ["cookies.sqlite", "cookies.sqlite-wal"]);
   let passwords = getFileResource(types.PASSWORDS,
-    ["signons.sqlite", "logins.json", "key3.db", "key4.db",
-     "signedInUser.json"]);
+    ["signons.sqlite", "logins.json", "key3.db", "key4.db"]);
   let formData = getFileResource(types.FORMDATA, [
     "formhistory.sqlite",
     "autofill-profiles.json",
   ]);
   let bookmarksBackups = getFileResource(types.OTHERDATA,
     [PlacesBackups.profileRelativeFolderPath]);
   let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
 
@@ -163,30 +171,57 @@ FirefoxProfileMigrator.prototype._getRes
             let buildID = Services.appinfo.platformBuildID;
             let mstone = Services.appinfo.platformVersion;
             // Force the browser to one-off resume the session that we give it:
             Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
             // Reset the homepage_override prefs so that the browser doesn't override our
             // session with the "what's new" page:
             Services.prefs.setCharPref("browser.startup.homepage_override.mstone", mstone);
             Services.prefs.setCharPref("browser.startup.homepage_override.buildID", buildID);
-            // It's too early in startup for the pref service to have a profile directory,
-            // so we have to manually tell it where to save the prefs file.
-            let newPrefsFile = currentProfileDir.clone();
-            newPrefsFile.append("prefs.js");
-            Services.prefs.savePrefFile(newPrefsFile);
+            savePrefs();
             aCallback(true);
           }, function() {
             aCallback(false);
           });
         }
       };
     }
   }
 
+  // Sync/FxA related data
+  let sync = {
+    name: "sync", // name is used only by tests.
+    type: types.OTHERDATA,
+    migrate: async aCallback => {
+        // Try and parse a signedInUser.json file from the source directory and
+        // if we can, copy it to the new profile and set sync's username pref
+        // (which acts as a de-facto flag to indicate if sync is configured)
+      try {
+        let oldPath = OS.Path.join(sourceProfileDir.path, "signedInUser.json");
+        let exists = await OS.File.exists(oldPath);
+        if (exists) {
+          let raw = await OS.File.read(oldPath, {encoding: "utf-8"});
+          let data = JSON.parse(raw);
+          if (data && data.accountData && data.accountData.email) {
+            let username = data.accountData.email;
+            // Write it to prefs.js and flush the file.
+            Services.prefs.setStringPref("services.sync.username", username);
+            savePrefs();
+            // and copy the file itself.
+            await OS.File.copy(oldPath, OS.Path.join(currentProfileDir.path, "signedInUser.json"));
+          }
+        }
+      } catch (ex) {
+        aCallback(false);
+        return;
+      }
+      aCallback(true);
+    }
+  };
+
   // Telemetry related migrations.
   let times = {
     name: "times", // name is used only by tests.
     type: types.OTHERDATA,
     migrate: aCallback => {
       let file = this._getFileObject(sourceProfileDir, "times.json");
       if (file) {
         file.copyTo(currentProfileDir, "");
@@ -247,17 +282,17 @@ FirefoxProfileMigrator.prototype._getRes
         }
       }
 
       aCallback(true);
     }
   };
 
   return [places, cookies, passwords, formData, dictionary, bookmarksBackups,
-          session, times, telemetry, favicons].filter(r => r);
+          session, sync, times, telemetry, favicons].filter(r => r);
 };
 
 Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
   get: () => true
 });
 
 
 FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -166,32 +166,44 @@ class TestFirefoxRefresh(MarionetteTestC
           let allTabs = Array.from(gBrowser.tabs);
           for (let tab of allTabs) {
             if (!expectedTabs.has(tab)) {
               gBrowser.removeTab(tab);
             }
           }
         """, script_args=(self._expectedURLs,))
 
+    def createSync(self):
+        # This script will write an entry to the login manager and create
+        # a signedInUser.json in the profile dir.
+        self.runAsyncCode("""
+          Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
+          let storage = new FxAccountsStorageManager();
+          let data = {email: "test@test.com", uid: "uid", keyFetchToken: "top-secret"};
+          storage.initialize(data);
+          storage.finalize().then(marionetteScriptFinished);
+        """);
+
     def checkPassword(self):
         loginInfo = self.marionette.execute_script("""
           let ary = Services.logins.findLogins({},
             "test.marionette.mozilla.com",
             "http://test.marionette.mozilla.com/some/form/",
             null, {});
           return ary.length ? ary : {username: "null", password: "null"};
         """)
         self.assertEqual(len(loginInfo), 1)
         self.assertEqual(loginInfo[0]['username'], self._username)
         self.assertEqual(loginInfo[0]['password'], self._password)
 
         loginCount = self.marionette.execute_script("""
           return Services.logins.getAllLogins().length;
         """)
-        self.assertEqual(loginCount, 1, "No other logins are present")
+        # Note that we expect 2 logins - one from us, one from sync.
+        self.assertEqual(loginCount, 2, "No other logins are present")
 
     def checkBookmark(self):
         titleInBookmarks = self.marionette.execute_script("""
           let url = arguments[0];
           let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(makeURI(url), {}, {});
           return bookmarkIds.length == 1 ? PlacesUtils.bookmarks.getItemTitle(bookmarkIds[0]) : "";
         """, script_args=(self._bookmarkURL,))
         self.assertEqual(titleInBookmarks, self._bookmarkText)
@@ -331,34 +343,64 @@ class TestFirefoxRefresh(MarionetteTestC
               }, { once: true });
             }
           };
 
           mm.loadFrameScript("data:application/javascript,(" + fs.toString() + ")()", true);
         """)
         self.assertSequenceEqual(tabURIs, self._expectedURLs)
 
+    def checkSync(self, hasMigrated):
+        result = self.runAsyncCode("""
+          Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
+          let prefs = new global.Preferences("services.sync.");
+          let storage = new FxAccountsStorageManager();
+          let result = {};
+          storage.initialize();
+          storage.getAccountData().then(data => {
+            result.accountData = data;
+            return storage.finalize();
+          }).then(() => {
+            result.prefUsername = prefs.get("username");
+            marionetteScriptFinished(result);
+          }).catch(err => {
+            marionetteScriptFinished(err.toString());
+          });
+        """);
+        if type(result) != dict:
+            self.fail(result)
+            return
+        self.assertEqual(result["accountData"]["email"], "test@test.com");
+        self.assertEqual(result["accountData"]["uid"], "uid");
+        self.assertEqual(result["accountData"]["keyFetchToken"], "top-secret");
+        if hasMigrated:
+          # This test doesn't actually configure sync itself, so the username
+          # pref only exists after migration.
+          self.assertEqual(result["prefUsername"], "test@test.com");
+
     def checkProfile(self, hasMigrated=False):
         self.checkPassword()
         self.checkBookmark()
         self.checkHistory()
         self.checkFormHistory()
         self.checkFormAutofill()
         self.checkCookie()
+        self.checkSync(hasMigrated);
         if hasMigrated:
             self.checkSession()
 
     def createProfileData(self):
         self.savePassword()
         self.createBookmark()
         self.createHistory()
         self.createFormHistory()
         self.createFormAutofill()
         self.createCookie()
         self.createSession()
+        self.createSync()
 
     def setUpScriptData(self):
         self.marionette.set_context(self.marionette.CONTEXT_CHROME)
         self.runCode("""
           window.global = {};
           global.LoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", "nsILoginInfo", "init");
           global.profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
           global.Preferences = Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;