Skip to content

Commit 1ec1ae9

Browse files
committed
Properly handle inconsistent indexes in the bookmarks DB
Normalize the indexes we use to insert child bookmarks into their parent folders, so that we show bookmarks in the same order as Firefox, even if Firefox returns inconsistent index values (e.g. two bookmarks with the same index, indexes which are negative, etc.). Closes #542.
1 parent 8ca73b7 commit 1ec1ae9

File tree

2 files changed

+33
-7
lines changed

2 files changed

+33
-7
lines changed

src/model/bookmarks.test.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,23 @@ describe("model/bookmarks", () => {
141141

142142
describe("loads bookmarks from the browser", () => {
143143
it("creates bookmark objects", () => {
144-
// Pick a few at random inside the stash, since bookmarks ones outside the
144+
// Pick a few at random inside the stash, since bookmarks outside the
145145
// stash are only loaded as needed.
146-
for (const l of ["one", "nested_child", "helen"]) {
146+
for (const l of ["nested_child", "helen", "undyne"]) {
147147
const template = bms[l as keyof typeof bms];
148148
const bm = model.node(template.id);
149149
if (template.url) {
150-
expect(bm).to.deep.include({
150+
expect(bm, `${l} URL/title to be set`).to.deep.include({
151151
id: template.id,
152152
title: template.title,
153153
url: template.url,
154154
});
155155
}
156156

157157
const parent = model.folder(template.parentId!)!;
158-
expect(parent).to.have.property("children");
158+
expect(parent, `${l} parent.children to exist`).to.have.property(
159+
"children",
160+
);
159161
expect(parent.children[template.index!]).to.equal(bm);
160162
}
161163
});
@@ -167,8 +169,8 @@ describe("model/bookmarks", () => {
167169

168170
const bm = model.folder(template.id)!;
169171
expect(bm.id).to.equal(template.id);
170-
expect(bm.children.map(bm => bm?.id)).to.deep.equal(
171-
template.children.map(c => c.id),
172+
expect(bm.children.map(bm => `${bm?.id} ${bm?.title}`)).to.deep.equal(
173+
template.children.map(c => `${c.id} ${c.title}`),
172174
);
173175
}
174176
});

src/model/bookmarks.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ export class Model {
263263

264264
return this._run_loader(folder, async () => {
265265
const children = await browser.bookmarks.getChildren(folder.id);
266+
fixupIndexes(children);
266267
for (const c of children) this._upsertNode(c);
267268

268269
folder.isLoaded = true;
@@ -318,6 +319,7 @@ export class Model {
318319
// we might allow for multiple loaders for the same child to run.
319320
// Worse, the other loader might be non-recursive, so loadedSubtree()
320321
// wouldn't actually perform as expected.
322+
fixupIndexes(children);
321323
for (const n of children) {
322324
const f = this._upsertNode(n) as Folder;
323325
const c = n.children;
@@ -528,6 +530,8 @@ export class Model {
528530
if (toIndex > position.index) toIndex--;
529531
}
530532
}
533+
const oldParent = node.position?.parent;
534+
const oldIndex = node.position?.index;
531535
await browser.bookmarks.move(node.id, {
532536
parentId: toParent.id,
533537
index: toIndex,
@@ -536,7 +540,19 @@ export class Model {
536540
const pos = node.position;
537541
/* c8 ignore next -- race avoidance */
538542
if (!pos) tryAgain();
539-
if (pos.parent !== toParent || pos.index !== toIndex) tryAgain();
543+
544+
// We assume the bookmark move has happened even if the bookmark did not
545+
// move to where we expect. This is because the Firefox bookmark DB might
546+
// have incorrect indexes in it, so the bookmark might not move to the
547+
// position we expect even if no other concurrent moves are happening.
548+
// There is unfortunately little we can do about this, so we ignore it.
549+
if (pos.parent !== oldParent || pos.index !== oldIndex) return;
550+
551+
// It's possible the old and new parents/indexes are the same. If so, the
552+
// bookmark will have "moved", but the check above will still fail.
553+
if (pos.parent === toParent && pos.index === toIndex) return;
554+
555+
tryAgain();
540556
});
541557

542558
await this.maybeCleanupEmptyFolder(position.parent);
@@ -1054,3 +1070,11 @@ function isBrowserBTNFolder(bm: Bookmarks.BookmarkTreeNode): boolean {
10541070
if (!("type" in bm) && !("url" in bm)) return true; // for Chrome
10551071
return false;
10561072
}
1073+
1074+
/** Firefox sometimes returns a list of bookmark children that have negative
1075+
* indexes, indexes with gaps, or duplicate indexes. We try to normalize these
1076+
* here so the rest of Tab Stash doesn't complain. See:
1077+
* https://github.com/josh-berry/tab-stash/issues/542 */
1078+
function fixupIndexes(nodes: Bookmarks.BookmarkTreeNode[]): void {
1079+
for (let i = 0; i < nodes.length; ++i) nodes[i].index = i;
1080+
}

0 commit comments

Comments
 (0)