Bug 1395332 - perform some post-profile-migration steps to ensure Sync works correctly in the new profile. r=Gijs
authorMark Hammond <mhammond@skippinet.com.au>
Fri, 08 Sep 2017 16:28:37 +1000
changeset 429979 080b044b13e3cb3eac465d046f199b82af3c8c17
parent 429978 b393da8b7c59312ab1f5c0978a84272655ecb50a
child 429980 8e8f901c19ca74728fc76cc2f7998b4ee464b6be
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs
bugs1395332
milestone57.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 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;