mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			687 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			687 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
 | 
						|
 | 
						|
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
 | 
						|
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
 | 
						|
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
 | 
						|
  PropertyListUtils: "resource://gre/modules/PropertyListUtils.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
// NSDate epoch is Jan 1, 2001 UTC
 | 
						|
const NS_DATE_EPOCH_MS = new Date("2001-01-01T00:00:00-00:00").getTime();
 | 
						|
 | 
						|
// Convert NSDate timestamp to UNIX timestamp.
 | 
						|
function parseNSDate(cocoaDateStr) {
 | 
						|
  let asDouble = parseFloat(cocoaDateStr);
 | 
						|
  if (!isNaN(asDouble)) {
 | 
						|
    return new Date(NS_DATE_EPOCH_MS + asDouble * 1000);
 | 
						|
  }
 | 
						|
  return new Date();
 | 
						|
}
 | 
						|
 | 
						|
// Convert UNIX timestamp to NSDate timestamp.
 | 
						|
function msToNSDate(ms) {
 | 
						|
  return parseFloat(ms - NS_DATE_EPOCH_MS) / 1000;
 | 
						|
}
 | 
						|
 | 
						|
function Bookmarks(aBookmarksFile) {
 | 
						|
  this._file = aBookmarksFile;
 | 
						|
}
 | 
						|
Bookmarks.prototype = {
 | 
						|
  type: MigrationUtils.resourceTypes.BOOKMARKS,
 | 
						|
 | 
						|
  migrate: function B_migrate(aCallback) {
 | 
						|
    return (async () => {
 | 
						|
      let dict = await new Promise(resolve =>
 | 
						|
        lazy.PropertyListUtils.read(this._file, resolve)
 | 
						|
      );
 | 
						|
      if (!dict) {
 | 
						|
        throw new Error("Could not read Bookmarks.plist");
 | 
						|
      }
 | 
						|
      let children = dict.get("Children");
 | 
						|
      if (!children) {
 | 
						|
        throw new Error("Invalid Bookmarks.plist format");
 | 
						|
      }
 | 
						|
 | 
						|
      let collection =
 | 
						|
        dict.get("Title") == "com.apple.ReadingList"
 | 
						|
          ? this.READING_LIST_COLLECTION
 | 
						|
          : this.ROOT_COLLECTION;
 | 
						|
      await this._migrateRootCollection(children, collection);
 | 
						|
    })().then(
 | 
						|
      () => aCallback(true),
 | 
						|
      e => {
 | 
						|
        console.error(e);
 | 
						|
        aCallback(false);
 | 
						|
      }
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  // Bookmarks collections in Safari.  Constants for migrateCollection.
 | 
						|
  ROOT_COLLECTION: 0,
 | 
						|
  MENU_COLLECTION: 1,
 | 
						|
  TOOLBAR_COLLECTION: 2,
 | 
						|
  READING_LIST_COLLECTION: 3,
 | 
						|
 | 
						|
  /**
 | 
						|
   * Start the migration of a Safari collection of bookmarks by retrieving favicon data.
 | 
						|
   *
 | 
						|
   * @param {object[]} aEntries
 | 
						|
   *   The collection's children
 | 
						|
   * @param {number} aCollection
 | 
						|
   *   One of the _COLLECTION values above.
 | 
						|
   */
 | 
						|
  async _migrateRootCollection(aEntries, aCollection) {
 | 
						|
    // First, try to get the favicon data of a user's bookmarks.
 | 
						|
    // In Safari, Favicons are stored as files with a unique name:
 | 
						|
    // the MD5 hash of the UUID of an SQLite entry in favicons.db.
 | 
						|
    // Thus, we must create a map from bookmark URLs -> their favicon entry's UUID.
 | 
						|
    let bookmarkURLToUUIDMap = new Map();
 | 
						|
 | 
						|
    const faviconFolder = FileUtils.getDir("ULibDir", [
 | 
						|
      "Safari",
 | 
						|
      "Favicon Cache",
 | 
						|
    ]).path;
 | 
						|
    let dbPath = PathUtils.join(faviconFolder, "favicons.db");
 | 
						|
 | 
						|
    try {
 | 
						|
      // If there is an error getting favicon data, we catch the error and move on.
 | 
						|
      // In this case, the bookmarkURLToUUIDMap will be left empty.
 | 
						|
      let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | 
						|
        dbPath,
 | 
						|
        "Safari favicons",
 | 
						|
        `SELECT I.uuid, I.url AS favicon_url, P.url
 | 
						|
        FROM icon_info I
 | 
						|
        INNER JOIN page_url P ON I.uuid = P.uuid;`
 | 
						|
      );
 | 
						|
 | 
						|
      if (rows) {
 | 
						|
        // Convert the rows from our SQLite database into a map from bookmark url to uuid
 | 
						|
        for (let row of rows) {
 | 
						|
          let uniqueURL = new URL(row.getResultByName("url")).href;
 | 
						|
 | 
						|
          // Normalize the URL by removing any trailing slashes. We'll make sure to do
 | 
						|
          // the same when doing look-ups during a migration.
 | 
						|
          if (uniqueURL.endsWith("/")) {
 | 
						|
            uniqueURL = uniqueURL.replace(/\/+$/, "");
 | 
						|
          }
 | 
						|
          bookmarkURLToUUIDMap.set(uniqueURL, row.getResultByName("uuid"));
 | 
						|
        }
 | 
						|
      }
 | 
						|
    } catch (ex) {
 | 
						|
      console.error(ex);
 | 
						|
    }
 | 
						|
 | 
						|
    await this._migrateCollection(aEntries, aCollection, bookmarkURLToUUIDMap);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Recursively migrate a Safari collection of bookmarks.
 | 
						|
   *
 | 
						|
   * @param {object[]} aEntries
 | 
						|
   *   The collection's children
 | 
						|
   * @param {number} aCollection
 | 
						|
   *   One of the _COLLECTION values above
 | 
						|
   * @param {Map} bookmarkURLToUUIDMap
 | 
						|
   *   A map from a bookmark's URL to the UUID of its entry in the favicons.db database
 | 
						|
   * @returns {Promise<undefined>}
 | 
						|
   *   Resolves after the bookmarks and favicons have been inserted into the
 | 
						|
   *   appropriate databases.
 | 
						|
   */
 | 
						|
  async _migrateCollection(aEntries, aCollection, bookmarkURLToUUIDMap) {
 | 
						|
    // A collection of bookmarks in Safari resembles places roots.  In the
 | 
						|
    // property list files (Bookmarks.plist, ReadingList.plist) they are
 | 
						|
    // stored as regular bookmarks folders, and thus can only be distinguished
 | 
						|
    // from by their names and places in the hierarchy.
 | 
						|
 | 
						|
    let entriesFiltered = [];
 | 
						|
    if (aCollection == this.ROOT_COLLECTION) {
 | 
						|
      for (let entry of aEntries) {
 | 
						|
        let type = entry.get("WebBookmarkType");
 | 
						|
        if (type == "WebBookmarkTypeList" && entry.has("Children")) {
 | 
						|
          let title = entry.get("Title");
 | 
						|
          let children = entry.get("Children");
 | 
						|
          if (title == "BookmarksBar") {
 | 
						|
            await this._migrateCollection(
 | 
						|
              children,
 | 
						|
              this.TOOLBAR_COLLECTION,
 | 
						|
              bookmarkURLToUUIDMap
 | 
						|
            );
 | 
						|
          } else if (title == "BookmarksMenu") {
 | 
						|
            await this._migrateCollection(
 | 
						|
              children,
 | 
						|
              this.MENU_COLLECTION,
 | 
						|
              bookmarkURLToUUIDMap
 | 
						|
            );
 | 
						|
          } else if (title == "com.apple.ReadingList") {
 | 
						|
            await this._migrateCollection(
 | 
						|
              children,
 | 
						|
              this.READING_LIST_COLLECTION,
 | 
						|
              bookmarkURLToUUIDMap
 | 
						|
            );
 | 
						|
          } else if (entry.get("ShouldOmitFromUI") !== true) {
 | 
						|
            entriesFiltered.push(entry);
 | 
						|
          }
 | 
						|
        } else if (type == "WebBookmarkTypeLeaf") {
 | 
						|
          entriesFiltered.push(entry);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      entriesFiltered = aEntries;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!entriesFiltered.length) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let folderGuid = -1;
 | 
						|
    switch (aCollection) {
 | 
						|
      case this.ROOT_COLLECTION: {
 | 
						|
        // In Safari, it is possible (though quite cumbersome) to move
 | 
						|
        // bookmarks to the bookmarks root, which is the parent folder of
 | 
						|
        // all bookmarks "collections".  That is somewhat in parallel with
 | 
						|
        // both the places root and the unfiled-bookmarks root.
 | 
						|
        // Because the former is only an implementation detail in our UI,
 | 
						|
        // the unfiled root seems to be the best choice.
 | 
						|
        folderGuid = lazy.PlacesUtils.bookmarks.unfiledGuid;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case this.MENU_COLLECTION: {
 | 
						|
        folderGuid = lazy.PlacesUtils.bookmarks.menuGuid;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case this.TOOLBAR_COLLECTION: {
 | 
						|
        folderGuid = lazy.PlacesUtils.bookmarks.toolbarGuid;
 | 
						|
        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).
 | 
						|
        let readingListTitle = await MigrationUtils.getLocalizedString(
 | 
						|
          "migration-imported-safari-reading-list"
 | 
						|
        );
 | 
						|
        folderGuid = (
 | 
						|
          await MigrationUtils.insertBookmarkWrapper({
 | 
						|
            parentGuid: lazy.PlacesUtils.bookmarks.menuGuid,
 | 
						|
            type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
 | 
						|
            title: readingListTitle,
 | 
						|
          })
 | 
						|
        ).guid;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      default:
 | 
						|
        throw new Error("Unexpected value for aCollection!");
 | 
						|
    }
 | 
						|
    if (folderGuid == -1) {
 | 
						|
      throw new Error("Invalid folder GUID");
 | 
						|
    }
 | 
						|
 | 
						|
    await this._migrateEntries(
 | 
						|
      entriesFiltered,
 | 
						|
      folderGuid,
 | 
						|
      bookmarkURLToUUIDMap
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Migrates bookmarks and favicons from Safari to Firefox.
 | 
						|
   *
 | 
						|
   * @param {object[]} entries
 | 
						|
   *   The Safari collection's children
 | 
						|
   * @param {number} parentGuid
 | 
						|
   *   GUID of the collection folder
 | 
						|
   * @param {Map} bookmarkURLToUUIDMap
 | 
						|
   *   A map from a bookmark's URL to the UUID of its entry in the favicons.db database
 | 
						|
   */
 | 
						|
  async _migrateEntries(entries, parentGuid, bookmarkURLToUUIDMap) {
 | 
						|
    let { convertedEntries, favicons } = await this._convertEntries(
 | 
						|
      entries,
 | 
						|
      bookmarkURLToUUIDMap
 | 
						|
    );
 | 
						|
 | 
						|
    await MigrationUtils.insertManyBookmarksWrapper(
 | 
						|
      convertedEntries,
 | 
						|
      parentGuid
 | 
						|
    );
 | 
						|
 | 
						|
    MigrationUtils.insertManyFavicons(favicons).catch(console.error);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Converts Safari collection entries into a suitable format for
 | 
						|
   * inserting bookmarks and favicons.
 | 
						|
   *
 | 
						|
   * @param {object[]} entries
 | 
						|
   *   The collection's children
 | 
						|
   * @param {Map} bookmarkURLToUUIDMap
 | 
						|
   *   A map from a bookmark's URL to the UUID of its entry in the favicons.db database
 | 
						|
   * @returns {object[]}
 | 
						|
   *   Returns an object with an array of converted bookmark entries and favicons
 | 
						|
   */
 | 
						|
  async _convertEntries(entries, bookmarkURLToUUIDMap) {
 | 
						|
    let favicons = [];
 | 
						|
    let convertedEntries = [];
 | 
						|
 | 
						|
    const faviconFolder = FileUtils.getDir("ULibDir", [
 | 
						|
      "Safari",
 | 
						|
      "Favicon Cache",
 | 
						|
    ]).path;
 | 
						|
 | 
						|
    for (const entry of entries) {
 | 
						|
      let type = entry.get("WebBookmarkType");
 | 
						|
      if (type == "WebBookmarkTypeList" && entry.has("Children")) {
 | 
						|
        let convertedChildren = await this._convertEntries(
 | 
						|
          entry.get("Children"),
 | 
						|
          bookmarkURLToUUIDMap
 | 
						|
        );
 | 
						|
        favicons.push(...convertedChildren.favicons);
 | 
						|
        convertedEntries.push({
 | 
						|
          title: entry.get("Title"),
 | 
						|
          type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
 | 
						|
          children: convertedChildren.convertedEntries,
 | 
						|
        });
 | 
						|
      } else if (type == "WebBookmarkTypeLeaf" && entry.has("URLString")) {
 | 
						|
        // Check we understand this URL before adding it:
 | 
						|
        let url = entry.get("URLString");
 | 
						|
        if (!URL.canParse(url)) {
 | 
						|
          console.error(
 | 
						|
            `Ignoring ${url} when importing from Safari because it is not a valid URL.`
 | 
						|
          );
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
        let title;
 | 
						|
        if (entry.has("URIDictionary")) {
 | 
						|
          title = entry.get("URIDictionary").get("title");
 | 
						|
        }
 | 
						|
        convertedEntries.push({ url, title });
 | 
						|
 | 
						|
        try {
 | 
						|
          // Try to get the favicon data for each bookmark we have.
 | 
						|
          // We use uri.spec as our unique identifier since bookmark links
 | 
						|
          // don't completely match up in the Safari data.
 | 
						|
          let uri = Services.io.newURI(url);
 | 
						|
          let uriSpec = uri.spec;
 | 
						|
 | 
						|
          // Safari's favicon database doesn't include forward slashes for
 | 
						|
          // the page URLs, despite adding them in the Bookmarks.plist file.
 | 
						|
          // We'll strip any off here for our favicon lookup.
 | 
						|
          if (uriSpec.endsWith("/")) {
 | 
						|
            uriSpec = uriSpec.replace(/\/+$/, "");
 | 
						|
          }
 | 
						|
 | 
						|
          let uuid = bookmarkURLToUUIDMap.get(uriSpec);
 | 
						|
          if (uuid) {
 | 
						|
            // Hash the UUID with md5 to give us the favicon file name.
 | 
						|
            let hashedUUID = lazy.PlacesUtils.md5(uuid, {
 | 
						|
              format: "hex",
 | 
						|
            }).toUpperCase();
 | 
						|
            let faviconFile = PathUtils.join(
 | 
						|
              faviconFolder,
 | 
						|
              "favicons",
 | 
						|
              hashedUUID
 | 
						|
            );
 | 
						|
            let faviconData = await IOUtils.read(faviconFile);
 | 
						|
            favicons.push({ faviconData, uri });
 | 
						|
          }
 | 
						|
        } catch (error) {
 | 
						|
          // Even if we fail, still continue the import process
 | 
						|
          // since favicons aren't as essential as the bookmarks themselves.
 | 
						|
          console.error(error);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return { convertedEntries, favicons };
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
async function GetHistoryResource() {
 | 
						|
  let dbPath = FileUtils.getDir("ULibDir", ["Safari", "History.db"]).path;
 | 
						|
  let maxAge = msToNSDate(
 | 
						|
    Date.now() - MigrationUtils.HISTORY_MAX_AGE_IN_MILLISECONDS
 | 
						|
  );
 | 
						|
 | 
						|
  // If we have read access to the Safari profile directory, check to
 | 
						|
  // see if there's any history to import. If we can't access the profile
 | 
						|
  // directory, let's assume that there's history to import and give the
 | 
						|
  // user the option to migrate it.
 | 
						|
  let canReadHistory = false;
 | 
						|
  try {
 | 
						|
    // 'stat' is always allowed, but reading is somehow not, if the user hasn't
 | 
						|
    // allowed it:
 | 
						|
    await IOUtils.read(dbPath, { maxBytes: 1 });
 | 
						|
    canReadHistory = true;
 | 
						|
  } catch (ex) {
 | 
						|
    console.error(
 | 
						|
      "Cannot yet read from Safari profile directory. Will presume history exists for import."
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  if (canReadHistory) {
 | 
						|
    let countQuery = `
 | 
						|
      SELECT COUNT(*)
 | 
						|
      FROM history_items LEFT JOIN history_visits
 | 
						|
      ON history_items.id = history_visits.history_item
 | 
						|
      WHERE history_visits.visit_time > ${maxAge}
 | 
						|
      LIMIT 1;`;
 | 
						|
 | 
						|
    let countResult = await MigrationUtils.getRowsFromDBWithoutLocks(
 | 
						|
      dbPath,
 | 
						|
      "Safari history",
 | 
						|
      countQuery
 | 
						|
    );
 | 
						|
 | 
						|
    if (!countResult[0].getResultByName("COUNT(*)")) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  let selectQuery = `
 | 
						|
    SELECT
 | 
						|
      history_items.url as history_url,
 | 
						|
      history_visits.title as history_title,
 | 
						|
      history_visits.visit_time as history_time
 | 
						|
    FROM history_items LEFT JOIN history_visits
 | 
						|
    ON history_items.id = history_visits.history_item
 | 
						|
    WHERE history_visits.visit_time > ${maxAge};`;
 | 
						|
 | 
						|
  return {
 | 
						|
    type: MigrationUtils.resourceTypes.HISTORY,
 | 
						|
 | 
						|
    async migrate(callback) {
 | 
						|
      callback(await this._migrate());
 | 
						|
    },
 | 
						|
 | 
						|
    async _migrate() {
 | 
						|
      let historyRows;
 | 
						|
 | 
						|
      try {
 | 
						|
        historyRows = await MigrationUtils.getRowsFromDBWithoutLocks(
 | 
						|
          dbPath,
 | 
						|
          "Safari history",
 | 
						|
          selectQuery
 | 
						|
        );
 | 
						|
 | 
						|
        if (!historyRows.length) {
 | 
						|
          console.log("No history found");
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
      } catch (ex) {
 | 
						|
        console.error(ex);
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      let pageInfos = [];
 | 
						|
      for (let row of historyRows) {
 | 
						|
        try {
 | 
						|
          pageInfos.push({
 | 
						|
            title: row.getResultByName("history_title"),
 | 
						|
            url: new URL(row.getResultByName("history_url")),
 | 
						|
            visits: [
 | 
						|
              {
 | 
						|
                transition: lazy.PlacesUtils.history.TRANSITIONS.TYPED,
 | 
						|
                date: parseNSDate(row.getResultByName("history_time")),
 | 
						|
              },
 | 
						|
            ],
 | 
						|
          });
 | 
						|
        } catch (e) {
 | 
						|
          console.error("Could not create a history row: ", e);
 | 
						|
        }
 | 
						|
      }
 | 
						|
      await MigrationUtils.insertVisitsWrapper(pageInfos);
 | 
						|
 | 
						|
      return true;
 | 
						|
    },
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Safari's preferences property list is independently used for three purposes:
 | 
						|
 * (a) importation of preferences
 | 
						|
 * (b) importation of search strings
 | 
						|
 * (c) retrieving the home page.
 | 
						|
 *
 | 
						|
 * So, rather than reading it three times, it's cached and managed here.
 | 
						|
 *
 | 
						|
 * @param {nsIFile} aPreferencesFile
 | 
						|
 *   The .plist file to be read.
 | 
						|
 */
 | 
						|
function MainPreferencesPropertyList(aPreferencesFile) {
 | 
						|
  this._file = aPreferencesFile;
 | 
						|
  this._callbacks = [];
 | 
						|
}
 | 
						|
MainPreferencesPropertyList.prototype = {
 | 
						|
  /**
 | 
						|
   * @see PropertyListUtils.read
 | 
						|
   * @param {Function} aCallback
 | 
						|
   *   A callback called with an Object representing the key-value pairs
 | 
						|
   *   read out of the .plist file.
 | 
						|
   */
 | 
						|
  read: function MPPL_read(aCallback) {
 | 
						|
    if ("_dict" in this) {
 | 
						|
      aCallback(this._dict);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let alreadyReading = !!this._callbacks.length;
 | 
						|
    this._callbacks.push(aCallback);
 | 
						|
    if (!alreadyReading) {
 | 
						|
      lazy.PropertyListUtils.read(this._file, aDict => {
 | 
						|
        this._dict = aDict;
 | 
						|
        for (let callback of this._callbacks) {
 | 
						|
          try {
 | 
						|
            callback(aDict);
 | 
						|
          } catch (ex) {
 | 
						|
            console.error(ex);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        this._callbacks.splice(0);
 | 
						|
      });
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
function SearchStrings(aMainPreferencesPropertyListInstance) {
 | 
						|
  this._mainPreferencesPropertyList = aMainPreferencesPropertyListInstance;
 | 
						|
}
 | 
						|
SearchStrings.prototype = {
 | 
						|
  type: MigrationUtils.resourceTypes.OTHERDATA,
 | 
						|
 | 
						|
  migrate: function SS_migrate(aCallback) {
 | 
						|
    this._mainPreferencesPropertyList.read(
 | 
						|
      MigrationUtils.wrapMigrateFunction(function migrateSearchStrings(aDict) {
 | 
						|
        if (!aDict) {
 | 
						|
          throw new Error("Could not get preferences dictionary");
 | 
						|
        }
 | 
						|
 | 
						|
        if (aDict.has("RecentSearchStrings")) {
 | 
						|
          let recentSearchStrings = aDict.get("RecentSearchStrings");
 | 
						|
          if (recentSearchStrings && recentSearchStrings.length) {
 | 
						|
            let changes = recentSearchStrings.map(searchString => ({
 | 
						|
              op: "add",
 | 
						|
              fieldname: "searchbar-history",
 | 
						|
              value: searchString,
 | 
						|
            }));
 | 
						|
            lazy.FormHistory.update(changes);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }, aCallback)
 | 
						|
    );
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Safari migrator
 | 
						|
 */
 | 
						|
export class SafariProfileMigrator extends MigratorBase {
 | 
						|
  static get key() {
 | 
						|
    return "safari";
 | 
						|
  }
 | 
						|
 | 
						|
  static get displayNameL10nID() {
 | 
						|
    return "migration-wizard-migrator-display-name-safari";
 | 
						|
  }
 | 
						|
 | 
						|
  static get brandImage() {
 | 
						|
    return "chrome://browser/content/migration/brands/safari.png";
 | 
						|
  }
 | 
						|
 | 
						|
  async getResources() {
 | 
						|
    let profileDir = FileUtils.getDir("ULibDir", ["Safari"]);
 | 
						|
    if (!profileDir.exists()) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    let resources = [];
 | 
						|
    let pushProfileFileResource = function (aFileName, aConstructor) {
 | 
						|
      let file = profileDir.clone();
 | 
						|
      file.append(aFileName);
 | 
						|
      if (file.exists()) {
 | 
						|
        resources.push(new aConstructor(file));
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    pushProfileFileResource("Bookmarks.plist", Bookmarks);
 | 
						|
 | 
						|
    // The Reading List feature was introduced at the same time in Windows and
 | 
						|
    // Mac versions of Safari.  Not surprisingly, they are stored in the same
 | 
						|
    // format in both versions.  Surpsingly, only on Windows there is a
 | 
						|
    // separate property list for it.  This code is used on mac too, because
 | 
						|
    // Apple may fix this at some point.
 | 
						|
    pushProfileFileResource("ReadingList.plist", Bookmarks);
 | 
						|
 | 
						|
    let prefs = this.mainPreferencesPropertyList;
 | 
						|
    if (prefs) {
 | 
						|
      resources.push(new SearchStrings(prefs));
 | 
						|
    }
 | 
						|
 | 
						|
    resources.push(GetHistoryResource());
 | 
						|
 | 
						|
    resources = await Promise.all(resources);
 | 
						|
 | 
						|
    return resources.filter(r => r != null);
 | 
						|
  }
 | 
						|
 | 
						|
  async getLastUsedDate() {
 | 
						|
    const profileDir = FileUtils.getDir("ULibDir", ["Safari"]);
 | 
						|
    const dates = await Promise.all(
 | 
						|
      ["Bookmarks.plist", "History.db"].map(file => {
 | 
						|
        const path = PathUtils.join(profileDir.path, file);
 | 
						|
        return IOUtils.stat(path)
 | 
						|
          .then(info => info.lastModified)
 | 
						|
          .catch(() => 0);
 | 
						|
      })
 | 
						|
    );
 | 
						|
 | 
						|
    return new Date(Math.max(...dates));
 | 
						|
  }
 | 
						|
 | 
						|
  async hasPermissions() {
 | 
						|
    if (this._hasPermissions) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    // Check if we have access to some key files, but only if they exist.
 | 
						|
    let historyTarget = FileUtils.getDir("ULibDir", ["Safari", "History.db"]);
 | 
						|
    let bookmarkTarget = FileUtils.getDir("ULibDir", [
 | 
						|
      "Safari",
 | 
						|
      "Bookmarks.plist",
 | 
						|
    ]);
 | 
						|
    let faviconTarget = FileUtils.getDir("ULibDir", [
 | 
						|
      "Safari",
 | 
						|
      "Favicon Cache",
 | 
						|
      "favicons.db",
 | 
						|
    ]);
 | 
						|
    try {
 | 
						|
      let historyExists = await IOUtils.exists(historyTarget.path);
 | 
						|
      let bookmarksExists = await IOUtils.exists(bookmarkTarget.path);
 | 
						|
      let faviconsExists = await IOUtils.exists(faviconTarget.path);
 | 
						|
      // We now know which files exist, which is always allowed.
 | 
						|
      // To determine if we have read permissions, try to read a single byte
 | 
						|
      // from each file that exists, which will throw if we need permissions.
 | 
						|
      if (historyExists) {
 | 
						|
        await IOUtils.read(historyTarget.path, { maxBytes: 1 });
 | 
						|
      }
 | 
						|
      if (bookmarksExists) {
 | 
						|
        await IOUtils.read(bookmarkTarget.path, { maxBytes: 1 });
 | 
						|
      }
 | 
						|
      if (faviconsExists) {
 | 
						|
        await IOUtils.read(faviconTarget.path, { maxBytes: 1 });
 | 
						|
      }
 | 
						|
      this._hasPermissions = true;
 | 
						|
      return true;
 | 
						|
    } catch (ex) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  async getPermissions(win) {
 | 
						|
    // Keep prompting the user until they pick something that grants us access
 | 
						|
    // to Safari's bookmarks and favicons or they cancel out of the file open panel.
 | 
						|
    while (!(await this.hasPermissions())) {
 | 
						|
      let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
 | 
						|
      // The title (second arg) is not displayed on macOS, so leave it blank.
 | 
						|
      fp.init(win?.browsingContext, "", Ci.nsIFilePicker.modeGetFolder);
 | 
						|
      fp.filterIndex = 1;
 | 
						|
      fp.displayDirectory = FileUtils.getDir("ULibDir", [""]);
 | 
						|
      // Now wait for the filepicker to open and close. If the user picks
 | 
						|
      // the Safari folder, macOS will grant us read access to everything
 | 
						|
      // inside, so we don't need to check or do anything else with what's
 | 
						|
      // returned by the filepicker.
 | 
						|
      let result = await new Promise(resolve => fp.open(resolve));
 | 
						|
      // Bail if the user cancels the dialog:
 | 
						|
      if (result == Ci.nsIFilePicker.returnCancel) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  async canGetPermissions() {
 | 
						|
    if (await MigrationUtils.canGetPermissionsOnPlatform()) {
 | 
						|
      const profileDir = FileUtils.getDir("ULibDir", ["Safari"]);
 | 
						|
      if (await IOUtils.exists(profileDir.path)) {
 | 
						|
        return profileDir.path;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * For Safari on macOS, we show a specialized flow for importing passwords
 | 
						|
   * from a CSV file.
 | 
						|
   *
 | 
						|
   * @returns {boolean}
 | 
						|
   */
 | 
						|
  get showsManualPasswordImport() {
 | 
						|
    // Since this migrator will only ever be used on macOS, all conditions are
 | 
						|
    // met and we can always return true.
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  get mainPreferencesPropertyList() {
 | 
						|
    if (this._mainPreferencesPropertyList === undefined) {
 | 
						|
      let file = FileUtils.getDir("UsrPrfs", []);
 | 
						|
      if (file.exists()) {
 | 
						|
        file.append("com.apple.Safari.plist");
 | 
						|
        if (file.exists()) {
 | 
						|
          this._mainPreferencesPropertyList = new MainPreferencesPropertyList(
 | 
						|
            file
 | 
						|
          );
 | 
						|
          return this._mainPreferencesPropertyList;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      this._mainPreferencesPropertyList = null;
 | 
						|
      return this._mainPreferencesPropertyList;
 | 
						|
    }
 | 
						|
    return this._mainPreferencesPropertyList;
 | 
						|
  }
 | 
						|
}
 |