Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* Any copyright is dedicated to the Public Domain.
add_task(async function test_complex_orphaning() {
let now = Date.now();
let mergeTelemetryCounts;
let buf = await openMirror("complex_orphaning", {
recordStepTelemetry(name, took, counts) {
if (name == "merge") {
mergeTelemetryCounts = counts.filter(({ count }) => count > 0);
}
},
});
// On iOS, the mirror exists as a separate table. On Desktop, we have a
// shadow mirror of synced local bookmarks without new changes.
info("Set up mirror: ((Toolbar > A > B) (Menu > G > C > D))");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.toolbarGuid,
children: [
{
guid: "folderAAAAAA",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "A",
children: [
{
guid: "folderBBBBBB",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "B",
},
],
},
],
});
await storeRecords(
buf,
shuffle([
{
id: "toolbar",
parentid: "places",
type: "folder",
children: ["folderAAAAAA"],
},
{
id: "folderAAAAAA",
parentid: "toolbar",
type: "folder",
title: "A",
children: ["folderBBBBBB"],
},
{
id: "folderBBBBBB",
parentid: "folderAAAAAA",
type: "folder",
title: "B",
},
]),
{ needsMerge: false }
);
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "folderGGGGGG",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "G",
children: [
{
guid: "folderCCCCCC",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "C",
children: [
{
guid: "folderDDDDDD",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "D",
},
],
},
],
},
],
});
await storeRecords(
buf,
shuffle([
{
id: "menu",
parentid: "places",
type: "folder",
children: ["folderGGGGGG"],
},
{
id: "folderGGGGGG",
parentid: "menu",
type: "folder",
title: "G",
children: ["folderCCCCCC"],
},
{
id: "folderCCCCCC",
parentid: "folderGGGGGG",
type: "folder",
title: "C",
children: ["folderDDDDDD"],
},
{
id: "folderDDDDDD",
parentid: "folderCCCCCC",
type: "folder",
title: "D",
},
]),
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
info("Make local changes: delete D, add B > E");
await PlacesUtils.bookmarks.remove("folderDDDDDD");
await PlacesUtils.bookmarks.insert({
guid: "bookmarkEEEE",
parentGuid: "folderBBBBBB",
title: "E",
});
info("Make remote changes: delete B, add D > F");
await storeRecords(
buf,
shuffle([
{
id: "folderBBBBBB",
deleted: true,
},
{
id: "folderAAAAAA",
parentid: "toolbar",
type: "folder",
title: "A",
},
{
id: "folderDDDDDD",
parentid: "folderCCCCCC",
type: "folder",
children: ["bookmarkFFFF"],
},
{
id: "bookmarkFFFF",
parentid: "folderDDDDDD",
type: "bookmark",
title: "F",
},
])
);
info("Apply remote");
let changesToUpload = await buf.apply();
deepEqual(
await buf.fetchUnmergedGuids(),
["bookmarkFFFF", "folderAAAAAA", "folderDDDDDD"],
"Should leave deleted D; A and F with new remote structure unmerged"
);
deepEqual(
mergeTelemetryCounts,
[
{ name: "items", count: 10 },
{ name: "localDeletes", count: 1 },
{ name: "remoteDeletes", count: 1 },
],
"Should record telemetry with structure change counts"
);
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: ["bookmarkEEEE", "bookmarkFFFF", "folderAAAAAA", "folderCCCCCC"],
deleted: ["folderDDDDDD"],
},
"Should upload new records for (A > E), (C > F); tombstone for D"
);
await assertLocalTree(
PlacesUtils.bookmarks.rootGuid,
{
guid: PlacesUtils.bookmarks.rootGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "",
children: [
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
children: [
{
guid: "folderGGGGGG",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "G",
children: [
{
guid: "folderCCCCCC",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "C",
children: [
{
// D was deleted, so F moved to C, the closest surviving parent.
guid: "bookmarkFFFF",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "F",
},
],
},
],
},
],
},
{
guid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 1,
title: BookmarksToolbarTitle,
children: [
{
guid: "folderAAAAAA",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "A",
children: [
{
// B was deleted, so E moved to A.
guid: "bookmarkEEEE",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "E",
},
],
},
],
},
{
guid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 3,
title: UnfiledBookmarksTitle,
},
{
guid: PlacesUtils.bookmarks.mobileGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 4,
title: MobileBookmarksTitle,
},
],
},
"Should move orphans to closest surviving parent"
);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(
tombstones.map(({ guid }) => guid),
["folderDDDDDD"],
"Should store local tombstone for D"
);
Assert.ok(
is_time_ordered(now, tombstones[0].dateRemoved.getTime()),
"Tombstone timestamp should be recent"
);
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_locally_modified_remotely_deleted() {
let mergeTelemetryCounts;
let buf = await openMirror("locally_modified_remotely_deleted", {
recordStepTelemetry(name, took, counts) {
if (name == "merge") {
mergeTelemetryCounts = counts.filter(({ count }) => count > 0);
}
},
});
info("Set up mirror");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "bookmarkAAAA",
title: "A",
},
{
guid: "folderBBBBBB",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "B",
children: [
{
guid: "bookmarkCCCC",
title: "C",
},
{
guid: "folderDDDDDD",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "D",
children: [
{
guid: "bookmarkEEEE",
title: "E",
},
],
},
],
},
],
});
await storeRecords(
buf,
[
{
id: "menu",
parentid: "places",
type: "folder",
children: ["bookmarkAAAA", "folderBBBBBB"],
},
{
id: "bookmarkAAAA",
parentid: "menu",
type: "bookmark",
title: "A",
},
{
id: "folderBBBBBB",
parentid: "menu",
type: "folder",
title: "B",
children: ["bookmarkCCCC", "folderDDDDDD"],
},
{
id: "bookmarkCCCC",
parentid: "folderBBBBBB",
type: "bookmark",
title: "C",
},
{
id: "folderDDDDDD",
parentid: "folderBBBBBB",
type: "folder",
title: "D",
children: ["bookmarkEEEE"],
},
{
id: "bookmarkEEEE",
parentid: "folderDDDDDD",
type: "bookmark",
title: "E",
},
],
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
info("Make local changes: change A; B > ((D > F) G)");
await PlacesUtils.bookmarks.update({
guid: "bookmarkAAAA",
title: "A (local)",
});
await PlacesUtils.bookmarks.insert({
guid: "bookmarkFFFF",
parentGuid: "folderDDDDDD",
title: "F (local)",
});
await PlacesUtils.bookmarks.insert({
guid: "bookmarkGGGG",
parentGuid: "folderBBBBBB",
title: "G (local)",
});
info("Make remote changes: delete A, B");
await storeRecords(buf, [
{
id: "menu",
parentid: "places",
type: "folder",
children: [],
},
{
id: "bookmarkAAAA",
deleted: true,
},
{
id: "folderBBBBBB",
deleted: true,
},
{
id: "bookmarkCCCC",
deleted: true,
},
{
id: "folderDDDDDD",
deleted: true,
},
{
id: "bookmarkEEEE",
deleted: true,
},
]);
info("Apply remote");
let changesToUpload = await buf.apply();
deepEqual(
await buf.fetchUnmergedGuids(),
["bookmarkAAAA", PlacesUtils.bookmarks.menuGuid],
"Should leave revived A and menu with new remote structure unmerged"
);
deepEqual(
mergeTelemetryCounts,
[
{ name: "items", count: 8 },
{ name: "localRevives", count: 1 },
{ name: "remoteDeletes", count: 2 },
],
"Should record telemetry for local item and remote folder deletions"
);
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: ["bookmarkAAAA", "bookmarkFFFF", "bookmarkGGGG", "menu"],
deleted: [],
},
"Should upload A, relocated local orphans, and menu"
);
await assertLocalTree(
PlacesUtils.bookmarks.menuGuid,
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
children: [
{
guid: "bookmarkAAAA",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "A (local)",
},
{
guid: "bookmarkFFFF",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 1,
title: "F (local)",
},
{
guid: "bookmarkGGGG",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 2,
title: "G (local)",
},
],
},
"Should restore A and relocate (F G) to menu"
);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(tombstones, [], "Should not store local tombstones");
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_locally_deleted_remotely_modified() {
let now = Date.now();
let mergeTelemetryCounts;
let buf = await openMirror("locally_deleted_remotely_modified", {
recordStepTelemetry(name, took, counts) {
if (name == "merge") {
mergeTelemetryCounts = counts.filter(({ count }) => count > 0);
}
},
});
info("Set up mirror");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "bookmarkAAAA",
title: "A",
},
{
guid: "folderBBBBBB",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "B",
children: [
{
guid: "bookmarkCCCC",
title: "C",
},
{
guid: "folderDDDDDD",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "D",
children: [
{
guid: "bookmarkEEEE",
title: "E",
},
],
},
],
},
],
});
await storeRecords(
buf,
[
{
id: "menu",
parentid: "places",
type: "folder",
children: ["bookmarkAAAA", "folderBBBBBB"],
},
{
id: "bookmarkAAAA",
parentid: "menu",
type: "bookmark",
title: "A",
},
{
id: "folderBBBBBB",
parentid: "menu",
type: "folder",
title: "B",
children: ["bookmarkCCCC", "folderDDDDDD"],
},
{
id: "bookmarkCCCC",
parentid: "folderBBBBBB",
type: "bookmark",
title: "C",
},
{
id: "folderDDDDDD",
parentid: "folderBBBBBB",
type: "folder",
title: "D",
children: ["bookmarkEEEE"],
},
{
id: "bookmarkEEEE",
parentid: "folderDDDDDD",
type: "bookmark",
title: "E",
},
],
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
info("Make local changes: delete A, B");
await PlacesUtils.bookmarks.remove("bookmarkAAAA");
await PlacesUtils.bookmarks.remove("folderBBBBBB");
info("Make remote changes: change A; B > ((D > F) G)");
await storeRecords(buf, [
{
id: "bookmarkAAAA",
parentid: "menu",
type: "bookmark",
title: "A (remote)",
},
{
id: "folderBBBBBB",
parentid: "menu",
type: "folder",
title: "B (remote)",
children: ["bookmarkCCCC", "folderDDDDDD", "bookmarkGGGG"],
},
{
id: "folderDDDDDD",
parentid: "folderBBBBBB",
type: "folder",
title: "D",
children: ["bookmarkEEEE", "bookmarkFFFF"],
},
{
id: "bookmarkFFFF",
parentid: "folderDDDDDD",
type: "bookmark",
title: "F (remote)",
},
{
id: "bookmarkGGGG",
parentid: "folderBBBBBB",
type: "bookmark",
title: "G (remote)",
},
]);
info("Apply remote");
let changesToUpload = await buf.apply();
deepEqual(
await buf.fetchUnmergedGuids(),
["bookmarkFFFF", "bookmarkGGGG", "folderBBBBBB", "folderDDDDDD"],
"Should leave deleted B and D; relocated F and G unmerged"
);
deepEqual(
mergeTelemetryCounts,
[
{ name: "items", count: 8 },
{ name: "remoteRevives", count: 1 },
{ name: "localDeletes", count: 2 },
],
"Should record telemetry for remote item and local folder deletions"
);
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: ["bookmarkFFFF", "bookmarkGGGG", "menu"],
deleted: ["bookmarkCCCC", "bookmarkEEEE", "folderBBBBBB", "folderDDDDDD"],
},
"Should upload relocated remote orphans and menu"
);
await assertLocalTree(
PlacesUtils.bookmarks.menuGuid,
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
children: [
{
guid: "bookmarkAAAA",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "A (remote)",
},
{
guid: "bookmarkFFFF",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 1,
title: "F (remote)",
},
{
guid: "bookmarkGGGG",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 2,
title: "G (remote)",
},
],
},
"Should restore A and relocate (F G) to menu"
);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(
tombstones.map(({ guid }) => guid),
["bookmarkCCCC", "bookmarkEEEE", "folderBBBBBB", "folderDDDDDD"],
"Should store local tombstones for deleted items; remove for undeleted"
);
Assert.ok(
tombstones.every(({ dateRemoved }) =>
is_time_ordered(now, dateRemoved.getTime())
),
"Local tombstone timestamps should be recent"
);
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_move_to_new_then_delete() {
let buf = await openMirror("move_to_new_then_delete");
info("Set up mirror: A > B > (C D)");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "folderAAAAAA",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "A",
children: [
{
guid: "folderBBBBBB",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "B",
children: [
{
guid: "bookmarkCCCC",
title: "C",
},
{
guid: "bookmarkDDDD",
title: "D",
},
],
},
],
},
],
});
await storeRecords(
buf,
shuffle([
{
id: "menu",
parentid: "places",
type: "folder",
children: ["folderAAAAAA"],
},
{
id: "folderAAAAAA",
parentid: "menu",
type: "folder",
title: "A",
children: ["folderBBBBBB"],
},
{
id: "folderBBBBBB",
parentid: "folderAAAAAA",
type: "folder",
title: "B",
children: ["bookmarkCCCC", "bookmarkDDDD"],
},
{
id: "bookmarkCCCC",
parentid: "folderBBBBBB",
type: "bookmark",
title: "C",
},
{
id: "bookmarkDDDD",
parentid: "folderBBBBBB",
type: "bookmark",
title: "D",
},
]),
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
info("Make local changes: E > A, delete E");
await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
guid: "folderEEEEEE",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "E",
});
await PlacesUtils.bookmarks.update({
guid: "folderAAAAAA",
parentGuid: "folderEEEEEE",
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
});
// E isn't synced, so we shouldn't upload a tombstone.
await PlacesUtils.bookmarks.remove("folderEEEEEE");
info("Make remote changes");
await storeRecords(buf, [
{
id: "bookmarkCCCC",
parentid: "folderBBBBBB",
type: "bookmark",
title: "C (remote)",
},
]);
info("Apply remote");
let changesToUpload = await buf.apply();
deepEqual(
await buf.fetchUnmergedGuids(),
["bookmarkCCCC", PlacesUtils.bookmarks.toolbarGuid],
"Should leave revived C and toolbar with new remote structure unmerged"
);
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: ["bookmarkCCCC", "menu", "toolbar"],
deleted: ["bookmarkDDDD", "folderAAAAAA", "folderBBBBBB"],
},
"Should upload records for Menu > C, Toolbar"
);
await assertLocalTree(
PlacesUtils.bookmarks.rootGuid,
{
guid: PlacesUtils.bookmarks.rootGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "",
children: [
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
children: [
{
guid: "bookmarkCCCC",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "C (remote)",
},
],
},
{
guid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 1,
title: BookmarksToolbarTitle,
},
{
guid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 3,
title: UnfiledBookmarksTitle,
},
{
guid: PlacesUtils.bookmarks.mobileGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 4,
title: MobileBookmarksTitle,
},
],
},
"Should move C to closest surviving parent"
);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(
tombstones.map(({ guid }) => guid),
["bookmarkDDDD", "folderAAAAAA", "folderBBBBBB"],
"Should store local tombstones for (D A B)"
);
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_nonexistent_on_one_side() {
let buf = await openMirror("nonexistent_on_one_side");
info("Set up empty mirror");
await PlacesTestUtils.markBookmarksAsSynced();
// A doesn't exist in the mirror.
info("Create local tombstone for nonexistent remote item A");
await PlacesUtils.bookmarks.insert({
guid: "bookmarkAAAA",
parentGuid: PlacesUtils.bookmarks.menuGuid,
title: "A",
// Pretend a bookmark restore added A, so that we'll write a tombstone when
// we remove it.
source: PlacesUtils.bookmarks.SOURCES.RESTORE,
});
await PlacesUtils.bookmarks.remove("bookmarkAAAA");
// B doesn't exist in Places, and we don't currently persist tombstones (bug
// 1343103), so we should ignore it.
info("Create remote tombstone for nonexistent local item B");
await storeRecords(buf, [
{
id: "bookmarkBBBB",
deleted: true,
},
]);
info("Apply remote");
let changesToUpload = await buf.apply();
deepEqual(
await buf.fetchUnmergedGuids(),
[PlacesUtils.bookmarks.menuGuid],
"Should leave menu with new remote structure unmerged"
);
let menuInfo = await PlacesUtils.bookmarks.fetch(
PlacesUtils.bookmarks.menuGuid
);
// We should still upload a record for the menu, since we changed its
// children when we added then removed A.
deepEqual(changesToUpload, {
menu: {
tombstone: false,
counter: 2,
synced: false,
cleartext: {
id: "menu",
type: "folder",
parentid: "places",
hasDupe: true,
parentName: "",
dateAdded: menuInfo.dateAdded.getTime(),
title: BookmarksMenuTitle,
children: [],
},
},
});
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_clear_folder_then_delete() {
let buf = await openMirror("clear_folder_then_delete");
info("Set up mirror");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "folderAAAAAA",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "A",
children: [
{
guid: "bookmarkBBBB",
title: "B",
},
{
guid: "bookmarkCCCC",
title: "C",
},
],
},
{
guid: "folderDDDDDD",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "D",
children: [
{
guid: "bookmarkEEEE",
title: "E",
},
{
guid: "bookmarkFFFF",
title: "F",
},
],
},
],
});
await storeRecords(
buf,
[
{
id: "menu",
parentid: "places",
type: "folder",
children: ["folderAAAAAA", "folderDDDDDD"],
},
{
id: "folderAAAAAA",
parentid: "menu",
type: "folder",
title: "A",
children: ["bookmarkBBBB", "bookmarkCCCC"],
},
{
id: "bookmarkBBBB",
parentid: "folderAAAAAA",
type: "bookmark",
title: "B",
},
{
id: "bookmarkCCCC",
parentid: "folderAAAAAA",
type: "bookmark",
title: "C",
},
{
id: "folderDDDDDD",
parentid: "menu",
type: "folder",
title: "D",
children: ["bookmarkEEEE", "bookmarkFFFF"],
},
{
id: "bookmarkEEEE",
parentid: "folderDDDDDD",
type: "bookmark",
title: "E",
},
{
id: "bookmarkFFFF",
parentid: "folderDDDDDD",
type: "bookmark",
title: "F",
},
],
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
info("Make local changes: Menu > E, Mobile > F, delete D");
await PlacesUtils.bookmarks.update({
guid: "bookmarkEEEE",
parentGuid: PlacesUtils.bookmarks.menuGuid,
index: 0,
});
await PlacesUtils.bookmarks.update({
guid: "bookmarkFFFF",
parentGuid: PlacesUtils.bookmarks.mobileGuid,
index: 0,
});
await PlacesUtils.bookmarks.remove("folderDDDDDD");
info("Make remote changes: Menu > B, Unfiled > C, delete A");
await storeRecords(buf, [
{
id: "menu",
parentid: "places",
type: "folder",
children: ["bookmarkBBBB", "folderDDDDDD"],
},
{
id: "bookmarkBBBB",
parentid: "menu",
type: "bookmark",
title: "B",
},
{
id: "unfiled",
parentid: "places",
type: "folder",
children: ["bookmarkCCCC"],
},
{
id: "bookmarkCCCC",
parentid: "unfiled",
type: "bookmark",
title: "C",
},
{
id: "folderAAAAAA",
deleted: true,
},
]);
info("Apply remote");
let changesToUpload = await buf.apply();
deepEqual(
await buf.fetchUnmergedGuids(),
[PlacesUtils.bookmarks.menuGuid, PlacesUtils.bookmarks.mobileGuid],
"Should leave menu and mobile with new remote structure unmerged"
);
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: ["bookmarkEEEE", "bookmarkFFFF", "menu", "mobile"],
deleted: ["folderDDDDDD"],
},
"Should upload locally moved and deleted items"
);
await assertLocalTree(
PlacesUtils.bookmarks.rootGuid,
{
guid: PlacesUtils.bookmarks.rootGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "",
children: [
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
children: [
{
guid: "bookmarkBBBB",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "B",
},
{
guid: "bookmarkEEEE",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 1,
title: "E",
},
],
},
{
guid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 1,
title: BookmarksToolbarTitle,
},
{
guid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 3,
title: UnfiledBookmarksTitle,
children: [
{
guid: "bookmarkCCCC",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "C",
},
],
},
{
guid: PlacesUtils.bookmarks.mobileGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 4,
title: MobileBookmarksTitle,
children: [
{
guid: "bookmarkFFFF",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "F",
},
],
},
],
},
"Should not orphan moved children of a deleted folder"
);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(
tombstones.map(({ guid }) => guid),
["folderDDDDDD"],
"Should store local tombstone for D"
);
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_newer_move_to_deleted() {
let buf = await openMirror("test_newer_move_to_deleted");
info("Set up mirror");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "folderAAAAAA",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "A",
children: [
{
guid: "bookmarkBBBB",
title: "B",
},
],
},
{
guid: "folderCCCCCC",
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "C",
children: [
{
guid: "bookmarkDDDD",
title: "D",
},
],
},
],
});
await storeRecords(
buf,
[
{
id: "menu",
parentid: "places",
type: "folder",
children: ["folderAAAAAA", "folderCCCCCC"],
},
{
id: "folderAAAAAA",
parentid: "menu",
type: "folder",
title: "A",
children: ["bookmarkBBBB"],
},
{
id: "bookmarkBBBB",
parentid: "folderAAAAAA",
type: "bookmark",
title: "B",
},
{
id: "folderCCCCCC",
parentid: "menu",
type: "folder",
title: "C",
children: ["bookmarkDDDD"],
},
{
id: "bookmarkDDDD",
parentid: "folderCCCCCC",
type: "bookmark",
title: "D",
},
],
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
let now = Date.now();
// A will have a newer local timestamp. However, we should *not* revert
// remotely moving B to the toolbar. (Locally, B exists in A, but we
// deleted the now-empty A remotely).
info("Make local changes: A > E, Toolbar > D, delete C");
await PlacesUtils.bookmarks.insert({
guid: "bookmarkEEEE",
parentGuid: "folderAAAAAA",
title: "E",
dateAdded: new Date(now),
lastModified: new Date(now),
});
await PlacesUtils.bookmarks.update({
guid: "bookmarkDDDD",
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
index: 0,
lastModified: new Date(now),
});
await PlacesUtils.bookmarks.remove("folderCCCCCC");
// C will have a newer remote timestamp. However, we should *not* revert
// locally moving D to the toolbar. (Locally, D exists in C, but we
// deleted the now-empty C locally).
info("Make remote changes: C > F, Toolbar > B, delete A");
await storeRecords(buf, [
{
id: "menu",
parentid: "places",
type: "folder",
children: ["folderCCCCCC"],
},
{
id: "folderCCCCCC",
parentid: "menu",
type: "folder",
title: "C",
children: ["bookmarkDDDD", "bookmarkFFFF"],
modified: now / 1000 + 5,
},
{
id: "bookmarkFFFF",
parentid: "folderCCCCCC",
type: "bookmark",
title: "F",
},
{
id: "toolbar",
parentid: "places",
type: "folder",
children: ["bookmarkBBBB"],
modified: now / 1000 - 5,
},
{
id: "bookmarkBBBB",
parentid: "toolbar",
type: "bookmark",
title: "B",
modified: now / 1000 - 5,
},
{
id: "folderAAAAAA",
deleted: true,
},
]);
info("Apply remote");
let changesToUpload = await buf.apply({
localTimeSeconds: now / 1000,
remoteTimeSeconds: now / 1000,
});
deepEqual(
await buf.fetchUnmergedGuids(),
[
"bookmarkFFFF",
"folderCCCCCC",
PlacesUtils.bookmarks.menuGuid,
PlacesUtils.bookmarks.toolbarGuid,
],
"Should leave deleted C; revived F and roots with new remote structure unmerged"
);
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: [
"bookmarkDDDD",
"bookmarkEEEE",
"bookmarkFFFF",
"menu",
"toolbar",
],
deleted: ["folderCCCCCC"],
},
"Should upload new and moved items"
);
await assertLocalTree(
PlacesUtils.bookmarks.rootGuid,
{
guid: PlacesUtils.bookmarks.rootGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: "",
children: [
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
children: [
{
guid: "bookmarkEEEE",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "E",
},
{
guid: "bookmarkFFFF",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 1,
title: "F",
},
],
},
{
guid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 1,
title: BookmarksToolbarTitle,
children: [
{
guid: "bookmarkDDDD",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 0,
title: "D",
},
{
guid: "bookmarkBBBB",
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
index: 1,
title: "B",
},
],
},
{
guid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 3,
title: UnfiledBookmarksTitle,
},
{
guid: PlacesUtils.bookmarks.mobileGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 4,
title: MobileBookmarksTitle,
},
],
},
"Should not decide to keep newly moved items in deleted parents"
);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(
tombstones.map(({ guid }) => guid),
["folderCCCCCC"],
"Should store local tombstone for C"
);
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});
add_task(async function test_remotely_deleted_also_removes_keyword() {
let buf = await openMirror("remotely_deleted_removes_keyword");
info("Set up mirror");
await PlacesUtils.bookmarks.insertTree({
guid: PlacesUtils.bookmarks.menuGuid,
children: [
{
guid: "bookmarkAAAA",
title: "A",
keyword: "keyworda",
},
{
guid: "bookmarkBBBB",
title: "B",
keyword: "keywordb",
},
],
});
await storeRecords(
buf,
[
{
id: "menu",
parentid: "places",
type: "folder",
children: ["bookmarkAAAA", "bookmarkBBBB"],
},
{
id: "bookmarkAAAA",
parentid: "menu",
type: "bookmark",
title: "A",
keyword: "keyworda",
},
{
id: "bookmarkBBBB",
parentid: "menu",
type: "bookmark",
title: "B",
keyword: "keywordb",
},
],
{ needsMerge: false }
);
await PlacesTestUtils.markBookmarksAsSynced();
// Validate the keywords exists
let has_keyword_a = await PlacesUtils.keywords.fetch({
});
Assert.equal(has_keyword_a.keyword, "keyworda");
let has_keyword_b = await PlacesUtils.keywords.fetch({
});
Assert.equal(has_keyword_b.keyword, "keywordb");
info("Make remote changes: delete A & B");
await storeRecords(buf, [
{
id: "menu",
parentid: "places",
type: "folder",
children: [],
},
{
id: "bookmarkAAAA",
deleted: true,
},
{
id: "bookmarkBBBB",
deleted: true,
},
]);
info("Apply remote");
let changesToUpload = await buf.apply();
let idsToUpload = inspectChangeRecords(changesToUpload);
deepEqual(
idsToUpload,
{
updated: [],
deleted: [],
},
"No local changes done"
);
await assertLocalTree(
PlacesUtils.bookmarks.menuGuid,
{
guid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
index: 0,
title: BookmarksMenuTitle,
},
"Should've remove A & B from menu"
);
// Validate the keyword no longer exists after removing the bookmark
let no_keyword_a = await PlacesUtils.keywords.fetch({
});
Assert.equal(no_keyword_a, null);
// Both keywords should've been removed after the sync
let no_keyword_b = await PlacesUtils.keywords.fetch({
});
Assert.equal(no_keyword_b, null);
let tombstones = await PlacesTestUtils.fetchSyncTombstones();
deepEqual(tombstones, [], "Should not store local tombstones");
await storeChangesInMirror(buf, changesToUpload);
deepEqual(await buf.fetchUnmergedGuids(), [], "Should merge all items");
await buf.finalize();
await PlacesUtils.bookmarks.eraseEverything();
await PlacesSyncUtils.bookmarks.reset();
});