browser/components/migration/src/ChromeProfileMigrator.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     2  * vim: sw=2 ts=2 sts=2 et */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 "use strict";
     9 const Cc = Components.classes;
    10 const Ci = Components.interfaces;
    11 const Cu = Components.utils;
    12 const Cr = Components.results;
    14 const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
    16 const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
    17 const S100NS_PER_MS = 10;
    19 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    20 Cu.import("resource://gre/modules/Services.jsm");
    21 Cu.import("resource://gre/modules/NetUtil.jsm");
    22 Cu.import("resource://gre/modules/FileUtils.jsm");
    23 Cu.import("resource:///modules/MigrationUtils.jsm");
    25 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
    26                                   "resource://gre/modules/PlacesUtils.jsm");
    28 /**
    29  * Convert Chrome time format to Date object
    30  *
    31  * @param   aTime
    32  *          Chrome time 
    33  * @return  converted Date object
    34  * @note    Google Chrome uses FILETIME / 10 as time.
    35  *          FILETIME is based on same structure of Windows.
    36  */
    37 function chromeTimeToDate(aTime)
    38 {
    39   return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970 ) / 10000);
    40 }
    42 /**
    43  * Insert bookmark items into specific folder.
    44  *
    45  * @param   aFolderId
    46  *          id of folder where items will be inserted
    47  * @param   aItems
    48  *          bookmark items to be inserted
    49  */
    50 function insertBookmarkItems(aFolderId, aItems)
    51 {
    52   for (let i = 0; i < aItems.length; i++) {
    53     let item = aItems[i];
    55     try {
    56       if (item.type == "url") {
    57         PlacesUtils.bookmarks.insertBookmark(aFolderId,
    58                                              NetUtil.newURI(item.url),
    59                                              PlacesUtils.bookmarks.DEFAULT_INDEX,
    60                                              item.name);
    61       } else if (item.type == "folder") {
    62         let newFolderId =
    63           PlacesUtils.bookmarks.createFolder(aFolderId,
    64                                              item.name,
    65                                              PlacesUtils.bookmarks.DEFAULT_INDEX);
    67         insertBookmarkItems(newFolderId, item.children);
    68       }
    69     } catch (e) {
    70       Cu.reportError(e);
    71     }
    72   }
    73 }
    76 function ChromeProfileMigrator() {
    77   let chromeUserDataFolder = FileUtils.getDir(
    78 #ifdef XP_WIN
    79     "LocalAppData", ["Google", "Chrome", "User Data"]
    80 #elifdef XP_MACOSX
    81     "ULibDir", ["Application Support", "Google", "Chrome"]
    82 #else
    83     "Home", [".config", "google-chrome"]
    84 #endif
    85     , false);
    86   this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
    87     chromeUserDataFolder : null;
    88 }
    90 ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
    92 ChromeProfileMigrator.prototype.getResources =
    93   function Chrome_getResources(aProfile) {
    94     if (this._chromeUserDataFolder) {
    95       let profileFolder = this._chromeUserDataFolder.clone();
    96       profileFolder.append(aProfile);
    97       if (profileFolder.exists()) {
    98         let possibleResources = [GetBookmarksResource(profileFolder),
    99                                  GetHistoryResource(profileFolder),
   100                                  GetCookiesResource(profileFolder)];
   101         return [r for each (r in possibleResources) if (r != null)];
   102       }
   103     }
   104     return [];
   105   };
   107 Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
   108   get: function Chrome_sourceProfiles() {
   109     if ("__sourceProfiles" in this)
   110       return this.__sourceProfiles;
   112     if (!this._chromeUserDataFolder)
   113       return [];
   115     let profiles;
   116     try {
   117       // Local State is a JSON file that contains profile info.
   118       let localState = this._chromeUserDataFolder.clone();
   119       localState.append("Local State");
   120       if (!localState.exists())
   121         throw new Error("Chrome's 'Local State' file does not exist.");
   122       if (!localState.isReadable())
   123         throw new Error("Chrome's 'Local State' file could not be read.");
   125       let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
   126       fstream.init(localState, -1, 0, 0);
   127       let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
   128                                                         { charset: "UTF-8" });
   129       let info_cache = JSON.parse(inputStream).profile.info_cache;
   130       if (info_cache)
   131         profiles = Object.keys(info_cache);
   132     } catch (e) {
   133       Cu.reportError("Error detecting Chrome profiles: " + e);
   134       // If we weren't able to detect any profiles above, fallback to the Default profile.
   135       let defaultProfileFolder = this._chromeUserDataFolder.clone();
   136       defaultProfileFolder.append("Default");
   137       if (defaultProfileFolder.exists())
   138         profiles = ["Default"];
   139     }
   141     // Only list profiles from which any data can be imported
   142     return this.__sourceProfiles = profiles.filter(function(profileName) {
   143       let resources = this.getResources(profileName);
   144       return resources && resources.length > 0;
   145     }, this);
   146   }
   147 });
   149 Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
   150   get: function Chrome_sourceHomePageURL() {
   151     let prefsFile = this._chromeUserDataFolder.clone();
   152     prefsFile.append("Preferences");
   153     if (prefsFile.exists()) {
   154       // XXX reading and parsing JSON is synchronous.
   155       let fstream = Cc[FILE_INPUT_STREAM_CID].
   156                     createInstance(Ci.nsIFileInputStream);
   157       fstream.init(file, -1, 0, 0);
   158       try {
   159         return JSON.parse(
   160           NetUtil.readInputStreamToString(fstream, fstream.available(),
   161                                           { charset: "UTF-8" })
   162             ).homepage;
   163       }
   164       catch(e) {
   165         Cu.reportError("Error parsing Chrome's preferences file: " + e);
   166       }
   167     }
   168     return "";
   169   }
   170 });
   172 function GetBookmarksResource(aProfileFolder) {
   173   let bookmarksFile = aProfileFolder.clone();
   174   bookmarksFile.append("Bookmarks");
   175   if (!bookmarksFile.exists())
   176     return null;
   178   return {
   179     type: MigrationUtils.resourceTypes.BOOKMARKS,
   181     migrate: function(aCallback) {
   182       NetUtil.asyncFetch(bookmarksFile, MigrationUtils.wrapMigrateFunction(
   183         function(aInputStream, aResultCode) {
   184           if (!Components.isSuccessCode(aResultCode))
   185             throw new Error("Could not read Bookmarks file");
   187           // Parse Chrome bookmark file that is JSON format
   188           let bookmarkJSON = NetUtil.readInputStreamToString(
   189             aInputStream, aInputStream.available(), { charset : "UTF-8" });
   190           let roots = JSON.parse(bookmarkJSON).roots;
   191           PlacesUtils.bookmarks.runInBatchMode({
   192             runBatched: function() {
   193               // Importing bookmark bar items
   194               if (roots.bookmark_bar.children &&
   195                   roots.bookmark_bar.children.length > 0) {
   196                 // Toolbar
   197                 let parentId = PlacesUtils.toolbarFolderId;
   198                 if (!MigrationUtils.isStartupMigration) { 
   199                   parentId = MigrationUtils.createImportedBookmarksFolder(
   200                     "Chrome", parentId);
   201                 }
   202                 insertBookmarkItems(parentId, roots.bookmark_bar.children);
   203               }
   205               // Importing bookmark menu items
   206               if (roots.other.children &&
   207                   roots.other.children.length > 0) {
   208                 // Bookmark menu
   209                 let parentId = PlacesUtils.bookmarksMenuFolderId;
   210                 if (!MigrationUtils.isStartupMigration) { 
   211                   parentId = MigrationUtils.createImportedBookmarksFolder(
   212                     "Chrome", parentId);
   213                 }
   214                 insertBookmarkItems(parentId, roots.other.children);
   215               }
   216             }
   217           }, null);
   218         }, aCallback));
   219     }
   220   };
   221 }
   223 function GetHistoryResource(aProfileFolder) {
   224   let historyFile = aProfileFolder.clone();
   225   historyFile.append("History");
   226   if (!historyFile.exists())
   227     return null;
   229   return {
   230     type: MigrationUtils.resourceTypes.HISTORY,
   232     migrate: function(aCallback) {
   233       let dbConn = Services.storage.openUnsharedDatabase(historyFile);
   234       let stmt = dbConn.createAsyncStatement(
   235         "SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0");
   237       stmt.executeAsync({
   238         handleResult : function(aResults) {
   239           let places = [];
   240           for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
   241             try {
   242               // if having typed_count, we changes transition type to typed.
   243               let transType = PlacesUtils.history.TRANSITION_LINK;
   244               if (row.getResultByName("typed_count") > 0)
   245                 transType = PlacesUtils.history.TRANSITION_TYPED;
   247               places.push({
   248                 uri: NetUtil.newURI(row.getResultByName("url")),
   249                 title: row.getResultByName("title"),
   250                 visits: [{
   251                   transitionType: transType,
   252                   visitDate: chromeTimeToDate(
   253                                row.getResultByName(
   254                                  "last_visit_time")) * 1000,
   255                 }],
   256               });
   257             } catch (e) {
   258               Cu.reportError(e);
   259             }
   260           }
   262           try {
   263             PlacesUtils.asyncHistory.updatePlaces(places);
   264           } catch (e) {
   265             Cu.reportError(e);
   266           }
   267         },
   269         handleError : function(aError) {
   270           Cu.reportError("Async statement execution returned with '" +
   271                          aError.result + "', '" + aError.message + "'");
   272         },
   274         handleCompletion : function(aReason) {
   275           dbConn.asyncClose();
   276           aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
   277         }
   278       });
   279       stmt.finalize();
   280     }
   281   };
   282 }
   284 function GetCookiesResource(aProfileFolder) {
   285   let cookiesFile = aProfileFolder.clone();
   286   cookiesFile.append("Cookies");
   287   if (!cookiesFile.exists())
   288     return null;
   290   return {
   291     type: MigrationUtils.resourceTypes.COOKIES,
   293     migrate: function(aCallback) {
   294       let dbConn = Services.storage.openUnsharedDatabase(cookiesFile);
   295       let stmt = dbConn.createAsyncStatement(
   296           "SELECT host_key, path, name, value, secure, httponly, expires_utc FROM cookies");
   298       stmt.executeAsync({
   299         handleResult : function(aResults) {
   300           for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
   301             let host_key = row.getResultByName("host_key");
   302             if (host_key.match(/^\./)) {
   303               // 1st character of host_key may be ".", so we have to remove it
   304               host_key = host_key.substr(1);
   305             }
   307             try {
   308               let expiresUtc =
   309                 chromeTimeToDate(row.getResultByName("expires_utc")) / 1000;
   310               Services.cookies.add(host_key,
   311                                    row.getResultByName("path"),
   312                                    row.getResultByName("name"),
   313                                    row.getResultByName("value"),
   314                                    row.getResultByName("secure"),
   315                                    row.getResultByName("httponly"),
   316                                    false,
   317                                    parseInt(expiresUtc));
   318             } catch (e) {
   319               Cu.reportError(e);
   320             }
   321           }
   322         },
   324         handleError : function(aError) {
   325           Cu.reportError("Async statement execution returned with '" +
   326                          aError.result + "', '" + aError.message + "'");
   327         },
   329         handleCompletion : function(aReason) {
   330           dbConn.asyncClose();
   331           aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
   332         },
   333       });
   334       stmt.finalize();
   335     }
   336   }
   337 }
   339 ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
   340 ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
   341 ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
   343 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeProfileMigrator]);

mercurial