mirror of
https://github.com/zen-browser/desktop.git
synced 2026-09-19 19:18:03 +00:00
gh-15380: Fixed not identifying applied stamps correctly (gh-15485)
This commit is contained in:
@@ -559,22 +559,24 @@
|
||||
}
|
||||
|
||||
.urlbarView-prettyName,
|
||||
.urlbarView-tabGroup,
|
||||
.urlbarView-shortcutContent {
|
||||
border-radius: 4px;
|
||||
border-radius: 4px !important;
|
||||
font-weight: 600;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-top: auto !important;
|
||||
margin-bottom: auto !important;
|
||||
}
|
||||
|
||||
.urlbarView-prettyName {
|
||||
padding: 4px 6px;
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 90%);
|
||||
margin-left: 6px;
|
||||
.urlbarView-prettyName,
|
||||
.urlbarView-tabGroup {
|
||||
padding: 4px 6px !important;
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 90%) !important;
|
||||
margin-left: 6px !important;
|
||||
font-size: 12px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
display: flex;
|
||||
color: color-mix(in srgb, var(--zen-primary-color), currentColor 95%);
|
||||
color: color-mix(in srgb, var(--zen-primary-color), currentColor 95%) !important;
|
||||
|
||||
& img {
|
||||
-moz-context-properties: fill;
|
||||
@@ -684,8 +686,9 @@
|
||||
background-color: rgba(255, 255, 255, 0.9) !important;
|
||||
}
|
||||
|
||||
& .urlbarView-prettyName {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 80%);
|
||||
& .urlbarView-prettyName,
|
||||
& .urlbarView-tabGroup {
|
||||
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 80%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@
|
||||
|
||||
/* Hide .zen-media-focus-button if it doesn't fit in the toolbar */
|
||||
@container media-controls (max-width: 165px) {
|
||||
.zen-media-focus-button {
|
||||
.zen-media-card:not([media-sharing]) .zen-media-focus-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,12 +156,26 @@ class nsZenSpacesSyncModel {
|
||||
* other device (gh-15426).
|
||||
*/
|
||||
#appliedStamp = new Map();
|
||||
#appliedStampGen = 0;
|
||||
|
||||
/** The projection generation the current sidebar data belongs to. */
|
||||
#currentStamp() {
|
||||
return lazy.ZenSessionStore.getSidebarData()?.lastCollected || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current projection generation, dropping held ids once a newer
|
||||
* collection has caught up (they can no longer be in the skew window).
|
||||
*/
|
||||
#appliedStampNow() {
|
||||
const stamp = this.#currentStamp();
|
||||
if (stamp !== this.#appliedStampGen) {
|
||||
this.#appliedStamp.clear();
|
||||
this.#appliedStampGen = stamp;
|
||||
}
|
||||
return stamp;
|
||||
}
|
||||
|
||||
#data() {
|
||||
if (!this.#file) {
|
||||
this.#file = new JSONFile({
|
||||
@@ -759,11 +773,11 @@ class nsZenSpacesSyncModel {
|
||||
const uploaded = this.#data().uploaded;
|
||||
const current = this.#digestAll();
|
||||
const pending = this.#pendingIds();
|
||||
const stamp = this.#currentStamp();
|
||||
const stamp = this.#appliedStampNow();
|
||||
const now = Date.now() / 1000;
|
||||
const changes = {};
|
||||
for (const [id, digest] of current) {
|
||||
if (uploaded[id] !== digest) {
|
||||
if (uploaded[id] !== digest && this.#appliedStamp.get(id) !== stamp) {
|
||||
changes[id] = now;
|
||||
}
|
||||
}
|
||||
@@ -795,9 +809,9 @@ class nsZenSpacesSyncModel {
|
||||
const uploaded = this.#data().uploaded;
|
||||
const current = this.#digestAll();
|
||||
const pending = this.#pendingIds();
|
||||
const stamp = this.#currentStamp();
|
||||
const stamp = this.#appliedStampNow();
|
||||
for (const [id, digest] of current) {
|
||||
if (uploaded[id] !== digest) {
|
||||
if (uploaded[id] !== digest && this.#appliedStamp.get(id) !== stamp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -849,15 +863,13 @@ class nsZenSpacesSyncModel {
|
||||
*/
|
||||
noteApplied(id, cleartext) {
|
||||
const data = this.#data();
|
||||
const stamp = this.#appliedStampNow();
|
||||
if (!cleartext) {
|
||||
delete data.uploaded[id];
|
||||
this.#appliedStamp.delete(id);
|
||||
} else {
|
||||
data.uploaded[id] = recordDigest(cleartext.kind, cleartext.data);
|
||||
// Hold back a tombstone for this id until the projection is recollected
|
||||
// past the generation it was applied in (gh-15426).
|
||||
this.#appliedStamp.set(id, this.#currentStamp());
|
||||
}
|
||||
this.#appliedStamp.set(id, stamp);
|
||||
syncLog(
|
||||
`acknowledged incoming ${cleartext ? cleartext.kind : "tombstone"} ${id}`
|
||||
);
|
||||
|
||||
@@ -12,6 +12,8 @@ support-files = [
|
||||
"head.js",
|
||||
]
|
||||
|
||||
["browser_apply_skew.js"]
|
||||
|
||||
["browser_folder_delete.js"]
|
||||
|
||||
["browser_normal_tabs_apply.js"]
|
||||
|
||||
51
src/zen/tests/device_sync/browser_apply_skew.js
Normal file
51
src/zen/tests/device_sync/browser_apply_skew.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_AppliedCreateNotTombstonedBeforeCollection() {
|
||||
await gZenWorkspaces.promiseInitialized;
|
||||
await collectProjections();
|
||||
|
||||
const id = "test-skew-create";
|
||||
ZenSpacesSyncModel.noteApplied(id, tabRecord(id, { pinned: true }).cleartext);
|
||||
Assert.ok(
|
||||
!ZenSpacesSyncModel.itemExists(id),
|
||||
"the id is acknowledged but not yet projected (the skew window)"
|
||||
);
|
||||
|
||||
const changes = ZenSpacesSyncModel.computeChangedIDs();
|
||||
Assert.ok(
|
||||
!(id in changes),
|
||||
"a just-applied create is not tombstoned before the projection catches up"
|
||||
);
|
||||
|
||||
ZenSpacesSyncModel.noteApplied(id, null);
|
||||
});
|
||||
|
||||
add_task(async function test_AppliedDeletionNotResurrectedBeforeCollection() {
|
||||
await gZenWorkspaces.promiseInitialized;
|
||||
|
||||
const tab = await openSyncableTab("https://example.com/?skew-delete", {
|
||||
pinned: true,
|
||||
});
|
||||
const id = tab.id;
|
||||
await collectProjections();
|
||||
ZenSpacesSyncModel.markUploaded([id]);
|
||||
Assert.ok(
|
||||
!(id in ZenSpacesSyncModel.computeChangedIDs()),
|
||||
"the uploaded tab is clean before the deletion"
|
||||
);
|
||||
|
||||
const failed = await ZenSpacesSyncApplier.applyBatch([tombstone(id)]);
|
||||
Assert.deepEqual(failed, [], "the tombstone applies cleanly");
|
||||
Assert.ok(!document.getElementById(id), "the tab is removed");
|
||||
|
||||
const changes = ZenSpacesSyncModel.computeChangedIDs();
|
||||
Assert.ok(
|
||||
!(id in changes),
|
||||
"a just-applied deletion is not re-uploaded before the projection catches up"
|
||||
);
|
||||
|
||||
ZenSpacesSyncModel.noteApplied(id, null);
|
||||
});
|
||||
@@ -13,4 +13,6 @@ support-files = [
|
||||
|
||||
["browser_issue_7385.js"]
|
||||
|
||||
["browser_sidebar_provider.js"]
|
||||
|
||||
["browser_urlbar_close_token.js"]
|
||||
|
||||
221
src/zen/tests/urlbar/browser_sidebar_provider.js
Normal file
221
src/zen/tests/urlbar/browser_sidebar_provider.js
Normal file
@@ -0,0 +1,221 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
UrlbarShared: "chrome://browser/content/urlbar/UrlbarShared.mjs",
|
||||
UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.sys.mjs",
|
||||
SessionSaver:
|
||||
"moz-src:///browser/components/sessionstore/SessionSaver.sys.mjs",
|
||||
TabStateFlusher:
|
||||
"moz-src:///browser/components/sessionstore/TabStateFlusher.sys.mjs",
|
||||
});
|
||||
|
||||
const PROVIDER_NAME = "ZenUrlbarProviderSidebar";
|
||||
const TAB_URL = "https://example.com/";
|
||||
|
||||
async function collectSidebarData() {
|
||||
await TabStateFlusher.flushWindow(window);
|
||||
await SessionSaver.run();
|
||||
}
|
||||
|
||||
async function searchSidebarRows(value) {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
waitForFocus,
|
||||
value,
|
||||
});
|
||||
const rows = [];
|
||||
for (let index = 0; index < UrlbarTestUtils.getResultCount(window); index++) {
|
||||
const { result } = await UrlbarTestUtils.getRowAt(window, index);
|
||||
if (result.providerName == PROVIDER_NAME) {
|
||||
rows.push({ index, result });
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function addLabelledTab(label) {
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, TAB_URL, {
|
||||
skipAnimation: true,
|
||||
});
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
tab.zenStaticLabel = label;
|
||||
gBrowser._setTabLabel(tab, label);
|
||||
return tab;
|
||||
}
|
||||
|
||||
async function removeFolder(folder) {
|
||||
const removeEvent = BrowserTestUtils.waitForEvent(folder, "TabGroupRemoved");
|
||||
folder.delete();
|
||||
await removeEvent;
|
||||
}
|
||||
|
||||
add_task(async function test_custom_label_is_searchable() {
|
||||
const tab = await addLabelledTab("Quarterly zeninvoices");
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zeninvoices quarterly");
|
||||
Assert.equal(rows.length, 1, "The renamed tab is the only match");
|
||||
const { result } = rows[0];
|
||||
Assert.equal(result.type, UrlbarShared.RESULT_TYPE.TAB_SWITCH);
|
||||
Assert.equal(result.payload.url, TAB_URL);
|
||||
Assert.equal(
|
||||
result.payload.title,
|
||||
"Quarterly zeninvoices",
|
||||
"The custom label is shown instead of the page title"
|
||||
);
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zeninvoices yearly"),
|
||||
[],
|
||||
"Every token needs to be part of the label"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_current_tab_is_not_suggested() {
|
||||
const tab = await addLabelledTab("zencurrenttab");
|
||||
gBrowser.selectedTab = tab;
|
||||
await collectSidebarData();
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zencurrenttab"),
|
||||
[],
|
||||
"There is no point in switching to the tab we are already in"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_stale_sidebar_url_is_not_suggested() {
|
||||
const tab = await addLabelledTab("zenstaletab");
|
||||
await collectSidebarData();
|
||||
|
||||
BrowserTestUtils.startLoadingURIString(
|
||||
tab.linkedBrowser,
|
||||
"https://example.org/"
|
||||
);
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zenstaletab"),
|
||||
[],
|
||||
"A url that is not open anymore must not be offered as switch to tab"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_only_active_space_tabs() {
|
||||
const originalSpace = gZenWorkspaces.activeWorkspace;
|
||||
const tab = await addLabelledTab("zenotherspace");
|
||||
await gZenWorkspaces.createAndSaveWorkspace("Sidebar Provider Space");
|
||||
Assert.notEqual(
|
||||
gZenWorkspaces.activeWorkspace,
|
||||
originalSpace,
|
||||
"The new space is the active one"
|
||||
);
|
||||
await collectSidebarData();
|
||||
|
||||
Assert.deepEqual(
|
||||
await searchSidebarRows("zenotherspace"),
|
||||
[],
|
||||
"Tabs from other spaces are not matched"
|
||||
);
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
|
||||
await gZenWorkspaces.removeWorkspace(gZenWorkspaces.activeWorkspace);
|
||||
Assert.equal(gZenWorkspaces.activeWorkspace, originalSpace);
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zenotherspace");
|
||||
Assert.equal(rows.length, 1, "The tab is matched again in its own space");
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
||||
|
||||
add_task(async function test_folders_path_and_ranking() {
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const tab2 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const subfolder = await gZenFolders.createFolder([tab], {
|
||||
renameFolder: false,
|
||||
label: "zenfold archive notes",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([tab2], {
|
||||
renameFolder: false,
|
||||
label: "zenfold",
|
||||
});
|
||||
parent.tabs[0].after(subfolder);
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zenfold");
|
||||
Assert.equal(rows.length, 2, "Both folders are matched");
|
||||
for (const { result } of rows) {
|
||||
Assert.equal(result.type, UrlbarShared.RESULT_TYPE.DYNAMIC);
|
||||
}
|
||||
const [best, worst] = rows;
|
||||
const space = gZenWorkspaces.getWorkspaceFromId(
|
||||
gZenWorkspaces.activeWorkspace
|
||||
);
|
||||
Assert.equal(best.result.payload.zenFolderId, parent.id);
|
||||
Assert.equal(
|
||||
best.result.payload.path,
|
||||
[space.name, "zenfold"].join(" / "),
|
||||
"A root folder only shows its space and name"
|
||||
);
|
||||
Assert.equal(best.index, 1, "A full match sits right below the heuristic");
|
||||
Assert.equal(worst.result.payload.zenFolderId, subfolder.id);
|
||||
Assert.equal(
|
||||
worst.result.payload.path,
|
||||
[space.name, "zenfold", "zenfold archive notes"].join(" / "),
|
||||
"A subfolder shows every parent folder"
|
||||
);
|
||||
Assert.greater(
|
||||
worst.index,
|
||||
best.index,
|
||||
"The weaker the match, the further down the folder goes"
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.promisePopupClose(window);
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
|
||||
add_task(async function test_picking_a_folder_reveals_it() {
|
||||
const tab = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab1");
|
||||
const tab2 = BrowserTestUtils.addTab(gBrowser, "data:text/html,tab2");
|
||||
const subfolder = await gZenFolders.createFolder([tab], {
|
||||
renameFolder: false,
|
||||
label: "zenreveal",
|
||||
});
|
||||
const parent = await gZenFolders.createFolder([tab2], {
|
||||
renameFolder: false,
|
||||
label: "parent",
|
||||
});
|
||||
parent.tabs[0].after(subfolder);
|
||||
subfolder.collapsed = true;
|
||||
parent.collapsed = true;
|
||||
await collectSidebarData();
|
||||
|
||||
const rows = await searchSidebarRows("zenreveal");
|
||||
Assert.equal(rows.length, 1, "The subfolder is matched");
|
||||
UrlbarTestUtils.setSelectedRowIndex(window, rows[0].index);
|
||||
await UrlbarTestUtils.promisePopupClose(window, () =>
|
||||
EventUtils.synthesizeKey("KEY_Enter")
|
||||
);
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => !parent.collapsed && !subfolder.collapsed,
|
||||
"The folder and its parent get expanded"
|
||||
);
|
||||
|
||||
await removeFolder(subfolder);
|
||||
await removeFolder(parent);
|
||||
});
|
||||
@@ -49,21 +49,18 @@ export class ZenUrlbarProviderSidebar extends UrlbarProvider {
|
||||
return;
|
||||
}
|
||||
const tokens = queryContext.tokens.map(t => t.lowerCaseValue);
|
||||
const matches = (percentage, text) => {
|
||||
const score = text => {
|
||||
text = text.toLowerCase();
|
||||
const matchCount = tokens.filter(token => text.includes(token)).length;
|
||||
return matchCount / tokens.length >= percentage;
|
||||
if (!tokens.every(token => text.includes(token))) {
|
||||
return 0;
|
||||
}
|
||||
return tokens.reduce((sum, token) => sum + token.length, 0) / text.length;
|
||||
};
|
||||
this.#addFolders(sidebar, matches.bind(undefined, 0.7), addCallback);
|
||||
this.#addTabs(
|
||||
sidebar,
|
||||
matches.bind(undefined, 0.4),
|
||||
queryContext,
|
||||
addCallback
|
||||
);
|
||||
this.#addFolders(sidebar, score, queryContext, addCallback);
|
||||
this.#addTabs(sidebar, score, queryContext, addCallback);
|
||||
}
|
||||
|
||||
#addTabs(sidebar, matches, queryContext, addCallback) {
|
||||
#addTabs(sidebar, score, queryContext, addCallback) {
|
||||
const openTabUrls = lazy.UrlbarProviderOpenTabs.getOpenTabUrls();
|
||||
const activeSpace =
|
||||
lazy.BrowserWindowTracker.getTopWindow().gZenWorkspaces.activeWorkspace;
|
||||
@@ -76,7 +73,7 @@ export class ZenUrlbarProviderSidebar extends UrlbarProvider {
|
||||
const entries = tabData.entries || [];
|
||||
const entry = entries[(tabData.index || entries.length) - 1];
|
||||
const label = tabData.zenStaticLabel || entry?.title;
|
||||
if (typeof label !== "string" || !label || !matches(label)) {
|
||||
if (typeof label !== "string" || !label || !score(label)) {
|
||||
continue;
|
||||
}
|
||||
const url = entry?.url;
|
||||
@@ -120,14 +117,17 @@ export class ZenUrlbarProviderSidebar extends UrlbarProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#addFolders(sidebar, matches, addCallback) {
|
||||
#addFolders(sidebar, score, queryContext, addCallback) {
|
||||
const folders = new Map(
|
||||
(sidebar.folders || []).map(folder => [folder.id, folder])
|
||||
);
|
||||
for (const folder of folders.values()) {
|
||||
if (folder.splitViewGroup || !folder.name || !matches(folder.name)) {
|
||||
continue;
|
||||
}
|
||||
const matched = [...folders.values()]
|
||||
.filter(folder => !folder.splitViewGroup && folder.name)
|
||||
.map(folder => ({ folder, score: score(folder.name) }))
|
||||
.filter(match => match.score)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
for (const match of matched) {
|
||||
const { folder } = match;
|
||||
const space = sidebar.spaces?.find(s => s.uuid == folder.workspaceId);
|
||||
const path = [folder.name];
|
||||
for (
|
||||
@@ -153,6 +153,11 @@ export class ZenUrlbarProviderSidebar extends UrlbarProvider {
|
||||
icon: "chrome://browser/skin/zen-icons/folder.svg",
|
||||
path: path.join(" / "),
|
||||
},
|
||||
suggestedIndex:
|
||||
1 +
|
||||
Math.round(
|
||||
(1 - Math.min(match.score, 1)) * (queryContext.maxResults - 2)
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"brandShortName": "Zen",
|
||||
"brandFullName": "Zen Browser",
|
||||
"release": {
|
||||
"displayVersion": "1.22.2b",
|
||||
"displayVersion": "1.22.3b",
|
||||
"github": {
|
||||
"repo": "zen-browser/desktop"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user