Bug 1205053 - use registry typedURLs to import rudimentary history from MS Edge, r=dolske, a=sylvestre
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Tue, 15 Sep 2015 22:18:41 +0100
changeset 296311 52a759d2958447f4bde3a882afe211b2bd7ba1d7
parent 296310 319cc68b36586e68c74fbe8f29f0b77f4f7d96a5
child 296312 8b59e77a2c713cc6a84c5baea6cef70954dc1ff2
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, sylvestre
bugs1205053
milestone43.0a2
Bug 1205053 - use registry typedURLs to import rudimentary history from MS Edge, r=dolske, a=sylvestre
browser/components/migration/EdgeProfileMigrator.js
browser/components/migration/IEProfileMigrator.js
browser/components/migration/MSMigrationUtils.jsm
browser/components/migration/tests/unit/test_Edge_availability.js
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -1,28 +1,99 @@
 /* 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/Services.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+
+const kEdgeRegistryRoot = "SOFTWARE\\Classes\\Local Settings\\Software\\" +
+  "Microsoft\\Windows\\CurrentVersion\\AppContainer\\Storage\\" +
+  "microsoft.microsoftedge_8wekyb3d8bbwe\\MicrosoftEdge";
+
+function EdgeTypedURLMigrator() {
+}
+
+EdgeTypedURLMigrator.prototype = {
+  type: MigrationUtils.resourceTypes.HISTORY,
+
+  get _typedURLs() {
+    if (!this.__typedURLs) {
+      this.__typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
+    }
+    return this.__typedURLs;
+  },
+
+  get exists() {
+    return this._typedURLs.size > 0;
+  },
+
+  migrate: function(aCallback) {
+    let rv = true;
+    let typedURLs = this._typedURLs;
+    let places = [];
+    for (let [urlString, time] of typedURLs) {
+      let uri;
+      try {
+        uri = Services.io.newURI(urlString, null, null);
+        if (["http", "https", "ftp"].indexOf(uri.scheme) == -1) {
+          continue;
+        }
+      } catch (ex) {
+        Cu.reportError(ex);
+        continue;
+      }
+
+      // Note that the time will be in microseconds (PRTime),
+      // and Date.now() returns milliseconds. Places expects PRTime,
+      // so we multiply the Date.now return value to make up the difference.
+      let visitDate = time || (Date.now() * 1000);
+      places.push({
+        uri,
+        visits: [{ transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED,
+                   visitDate}]
+      });
+    }
+
+    if (places.length == 0) {
+      aCallback(typedURLs.size == 0);
+      return;
+    }
+
+    PlacesUtils.asyncHistory.updatePlaces(places, {
+      _success: false,
+      handleResult: function() {
+        // Importing any entry is considered a successful import.
+        this._success = true;
+      },
+      handleError: function() {},
+      handleCompletion: function() {
+        aCallback(this._success);
+      }
+    });
+  },
+}
 
 function EdgeProfileMigrator() {
 }
 
 EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 EdgeProfileMigrator.prototype.getResources = function() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
     MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+    new EdgeTypedURLMigrator(),
   ];
   let windowsVaultFormPasswordsMigrator =
     MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
   windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
   resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -41,44 +41,19 @@ let CtypesKernelHelpers = MSMigrationUti
 function History() {
 }
 
 History.prototype = {
   type: MigrationUtils.resourceTypes.HISTORY,
 
   get exists() true,
 
-  __typedURLs: null,
-  get _typedURLs() {
-    if (!this.__typedURLs) {
-      // The list of typed URLs is a sort of annotation stored in the registry.
-      // Currently, IE stores 25 entries and this value is not configurable,
-      // but we just keep reading up to the first non-existing entry to support
-      // possible future bumps of this limit.
-      this.__typedURLs = {};
-      let registry = Cc["@mozilla.org/windows-registry-key;1"].
-                     createInstance(Ci.nsIWindowsRegKey);
-      try {
-        registry.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                      "Software\\Microsoft\\Internet Explorer\\TypedURLs",
-                      Ci.nsIWindowsRegKey.ACCESS_READ);
-        for (let entry = 1; registry.hasValue("url" + entry); entry++) {
-          let url = registry.readStringValue("url" + entry);
-          this.__typedURLs[url] = true;
-        }
-      } catch (ex) {
-      } finally {
-        registry.close();
-      }
-    }
-    return this.__typedURLs;
-  },
-
   migrate: function H_migrate(aCallback) {
     let places = [];
+    let typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
     let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
                             createInstance(Ci.nsISimpleEnumerator);
     while (historyEnumerator.hasMoreElements()) {
       let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
       let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
       // MSIE stores some types of URLs in its history that we don't handle,
       // like HTMLHelp and others.  Since we don't properly map handling for
       // all of them we just avoid importing them.
@@ -88,21 +63,24 @@ History.prototype = {
 
       let title = entry.get("title");
       // Embed visits have no title and don't need to be imported.
       if (title.length == 0) {
         continue;
       }
 
       // The typed urls are already fixed-up, so we can use them for comparison.
-      let transitionType = this._typedURLs[uri.spec] ?
+      let transitionType = typedURLs.has(uri.spec) ?
                              Ci.nsINavHistoryService.TRANSITION_TYPED :
                              Ci.nsINavHistoryService.TRANSITION_LINK;
-      // use the current date if we have no visits for this entry
-      let lastVisitTime = entry.get("time") || Date.now();
+      // use the current date if we have no visits for this entry.
+      // Note that the entry will have a time in microseconds (PRTime),
+      // and Date.now() returns milliseconds. Places expects PRTime,
+      // so we multiply the Date.now return value to make up the difference.
+      let lastVisitTime = entry.get("time") || (Date.now() * 1000);
 
       places.push(
         { uri: uri,
           title: title,
           visits: [{ transitionType: transitionType,
                      visitDate: lastVisitTime }]
         }
       );
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -681,30 +681,100 @@ Cookies.prototype = {
       if (host.length > 0) {
         // Fist delete any possible extant matching host cookie.
         Services.cookies.remove(host, name, path, false);
         // Now make it a domain cookie.
         if (host[0] != "." && !hostIsIPAddress(host))
           host = "." + host;
       }
 
-      let expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
-                                                                      Number(expireTimeLo));
+      // Fallback: expire in 1h
+      let expireTime = (Date.now() + 3600 * 1000) * 1000;
+      try {
+        expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+                                                                          Number(expireTimeLo));
+      } catch (ex) {
+        Cu.reportError("Failed to get expiry time for cookie for " + host);
+      }
+
       Services.cookies.add(host,
                            path,
                            name,
                            value,
                            Number(flags) & 0x1, // secure
                            false, // httpOnly
                            false, // session
                            expireTime);
     }
   }
 };
 
+function getTypedURLs(registryKeyPath) {
+  // The list of typed URLs is a sort of annotation stored in the registry.
+  // The number of entries stored is not UI-configurable, but has changed
+  // between different Windows versions. We just keep reading up to the first
+  // non-existing entry to support different limits / states of the registry.
+  let typedURLs = new Map();
+  let typedURLKey = Cc["@mozilla.org/windows-registry-key;1"].
+                    createInstance(Ci.nsIWindowsRegKey);
+  let typedURLTimeKey = Cc["@mozilla.org/windows-registry-key;1"].
+                        createInstance(Ci.nsIWindowsRegKey);
+  let cTypes = new CtypesHelpers();
+  try {
+    typedURLKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                     registryKeyPath + "\\TypedURLs",
+                     Ci.nsIWindowsRegKey.ACCESS_READ);
+    try {
+      typedURLTimeKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                           registryKeyPath + "\\TypedURLsTime",
+                           Ci.nsIWindowsRegKey.ACCESS_READ);
+    } catch (ex) {
+      typedURLTimeKey = null;
+    }
+    let entryName;
+    for (let entry = 1; typedURLKey.hasValue((entryName = "url" + entry)); entry++) {
+      let url = typedURLKey.readStringValue(entryName);
+      let timeTyped = 0;
+      if (typedURLTimeKey && typedURLTimeKey.hasValue(entryName)) {
+        let urlTime = "";
+        try {
+          urlTime = typedURLTimeKey.readBinaryValue(entryName);
+        } catch (ex) {
+          Cu.reportError("Couldn't read url time for " + entryName);
+        }
+        if (urlTime.length == 8) {
+          let urlTimeHex = [];
+          for (let i = 0; i < 8; i++) {
+            let c = urlTime.charCodeAt(i).toString(16);
+            if (c.length == 1)
+              c = "0" + c;
+            urlTimeHex.unshift(c);
+          }
+          try {
+            let hi = parseInt(urlTimeHex.slice(0, 4).join(''), 16);
+            let lo = parseInt(urlTimeHex.slice(4, 8).join(''), 16);
+            timeTyped = cTypes.fileTimeToSecondsSinceEpoch(hi, lo);
+            // Callers expect PRTime, which is microseconds since epoch:
+            timeTyped *= 1000 * 1000;
+          } catch (ex) {}
+        }
+      }
+      typedURLs.set(url, timeTyped);
+    }
+  } catch (ex) {
+    Cu.reportError("Error reading typed URL history: " + ex);
+  } finally {
+    typedURLKey.close();
+    typedURLTimeKey.close();
+    cTypes.finalize();
+  }
+  return typedURLs;
+}
+
+
 // Migrator for form passwords on Windows 8 and higher.
 function WindowsVaultFormPasswords () {
 }
 
 WindowsVaultFormPasswords.prototype = {
   type: MigrationUtils.resourceTypes.PASSWORDS,
 
   get exists() {
@@ -845,9 +915,10 @@ var MSMigrationUtils = {
     return new Bookmarks(migrationType);
   },
   getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
     return new Cookies(migrationType);
   },
   getWindowsVaultFormPasswordsMigrator() {
     return new WindowsVaultFormPasswords();
   },
+  getTypedURLs,
 };
--- a/browser/components/migration/tests/unit/test_Edge_availability.js
+++ b/browser/components/migration/tests/unit/test_Edge_availability.js
@@ -1,11 +1,12 @@
 const EDGE_AVAILABLE_MIGRATIONS = 
   MigrationUtils.resourceTypes.COOKIES |
   MigrationUtils.resourceTypes.BOOKMARKS |
+  MigrationUtils.resourceTypes.HISTORY |
   MigrationUtils.resourceTypes.PASSWORDS;
 
 add_task(function* () {
   let migrator = MigrationUtils.getMigrator("edge");
   Cu.import("resource://gre/modules/AppConstants.jsm");
   Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
                "Edge should be available for migration if and only if we're on Win 10+");
   if (migrator) {