Skip to content

Commit

Permalink
Bug 1236916 - Part 1: Fix Safari history migration r=mconley
Browse files Browse the repository at this point in the history
This patch based on work that was started by Evan Liang <[email protected]>.

Differential Revision: https://phabricator.services.mozilla.com/D179104
  • Loading branch information
mstriemer committed May 26, 2023
1 parent 99b7607 commit c545394
Showing 1 changed file with 99 additions and 72 deletions.
171 changes: 99 additions & 72 deletions browser/components/migration/SafariProfileMigrator.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ ChromeUtils.defineESModuleGetters(lazy, {
PropertyListUtils: "resource://gre/modules/PropertyListUtils.sys.mjs",
});

// Helper method for converting the visit date property to a PRTime value.
// The visit date is stored as a string, so it's not read as a Date
// object by PropertyListUtils.
function parseCocoaDate(cocoaDateStr) {
let asDouble = parseFloat(cocoaDateStr);
if (!isNaN(asDouble)) {
// reference date of NSDate.
let date = new Date("1 January 2001, GMT");
date.setMilliseconds(asDouble * 1000);
return date;
}
return new Date();
}

function msToCocoaTime(ms) {
let referenceDate = new Date("1 January 2001, GMT");
let date = new Date(ms);
return parseFloat(date - referenceDate) / 1000;
}

function Bookmarks(aBookmarksFile) {
this._file = aBookmarksFile;
}
Expand Down Expand Up @@ -329,82 +349,86 @@ Bookmarks.prototype = {
},
};

function History(aHistoryFile) {
this._file = aHistoryFile;
}
History.prototype = {
type: MigrationUtils.resourceTypes.HISTORY,

// Helper method for converting the visit date property to a PRTime value.
// The visit date is stored as a string, so it's not read as a Date
// object by PropertyListUtils.
_parseCocoaDate: function H___parseCocoaDate(aCocoaDateStr) {
let asDouble = parseFloat(aCocoaDateStr);
if (!isNaN(asDouble)) {
// reference date of NSDate.
let date = new Date("1 January 2001, GMT");
date.setMilliseconds(asDouble * 1000);
return date;
}
return new Date();
},
async function GetHistoryResource() {
let dbPath = FileUtils.getDir(
"ULibDir",
["Safari", "History.db"],
false
).path;
let maxAge = msToCocoaTime(
Date.now() - MigrationUtils.HISTORY_MAX_AGE_IN_MILLISECONDS
);

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 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};`;

let countResult = await MigrationUtils.getRowsFromDBWithoutLocks(
dbPath,
"Safari history",
countQuery
);

if (!countResult[0].getResultByName("COUNT(*)")) {
return null;
}

migrate: function H_migrate(aCallback) {
lazy.PropertyListUtils.read(this._file, aDict => {
try {
if (!aDict) {
throw new Error("Could not read history property list");
}
if (!aDict.has("WebHistoryDates")) {
throw new Error("Unexpected history-property list format");
}
return {
type: MigrationUtils.resourceTypes.HISTORY,

let pageInfos = [];
let entries = aDict.get("WebHistoryDates");
let failedOnce = false;
for (let entry of entries) {
if (entry.has("lastVisitedDate")) {
let date = this._parseCocoaDate(entry.get("lastVisitedDate"));
try {
pageInfos.push({
url: new URL(entry.get("")),
title: entry.get("title"),
visits: [
{
// Safari's History file contains only top-level urls. It does not
// distinguish between typed urls and linked urls.
transition: lazy.PlacesUtils.history.TRANSITIONS.LINK,
date,
},
],
});
} catch (ex) {
// Safari's History file may contain malformed URIs which
// will be ignored.
console.error(ex);
failedOnce = true;
}
}
}
if (!pageInfos.length) {
// If we failed at least once, then we didn't succeed in importing,
// otherwise we didn't actually have anything to import, so we'll
// report it as a success.
aCallback(!failedOnce);
return;
}
async migrate(callback) {
callback(await this._migrate());
},

MigrationUtils.insertVisitsWrapper(pageInfos).then(
() => aCallback(true),
() => aCallback(false)
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);
aCallback(false);
return false;
}
});
},
};

let pageInfos = [];
for (let row of historyRows) {
pageInfos.push({
title: row.getResultByName("history_title"),
url: new URL(row.getResultByName("history_url")),
visits: [
{
transition: lazy.PlacesUtils.history.TRANSITIONS.TYPED,
date: parseCocoaDate(row.getResultByName("history_time")),
},
],
});
}
await MigrationUtils.insertVisitsWrapper(pageInfos);

return true;
},
};
}

/**
* Safari's preferences property list is independently used for three purposes:
Expand Down Expand Up @@ -497,7 +521,7 @@ export class SafariProfileMigrator extends MigratorBase {
return "chrome://browser/content/migration/brands/safari.png";
}

getResources() {
async getResources() {
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
if (!profileDir.exists()) {
return null;
Expand All @@ -512,7 +536,6 @@ export class SafariProfileMigrator extends MigratorBase {
}
};

pushProfileFileResource("History.plist", History);
pushProfileFileResource("Bookmarks.plist", Bookmarks);

// The Reading List feature was introduced at the same time in Windows and
Expand All @@ -527,7 +550,11 @@ export class SafariProfileMigrator extends MigratorBase {
resources.push(new SearchStrings(prefs));
}

return resources;
resources.push(GetHistoryResource());

resources = await Promise.all(resources);

return resources.filter(r => r != null);
}

async getLastUsedDate() {
Expand Down

0 comments on commit c545394

Please sign in to comment.