From 403d5ae92e86b5f3fb9d795fa710e8706e7ce519 Mon Sep 17 00:00:00 2001 From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com> Date: Sat, 27 Sep 2025 00:53:25 +0200 Subject: [PATCH] feat: Add learning for omnibox commands, p=#10564 --- locales/en-US/browser/browser/zen-general.ftl | 2 + package.json | 1 + .../sessionstore/TabState-sys-mjs.patch | 8 +- .../urlbar/UrlbarController-sys-mjs.patch | 20 +- .../urlbar/UrlbarInput-sys-mjs.patch | 36 +++- .../urlbar/UrlbarMuxerStandard-sys-mjs.patch | 12 +- .../urlbar/UrlbarPrefs-sys-mjs.patch | 4 +- .../urlbar/UrlbarUtils-sys-mjs.patch | 28 ++- .../urlbar/UrlbarView-sys-mjs.patch | 38 +--- src/zen/common/ZenCommonUtils.mjs | 1 + src/zen/common/ZenStartup.mjs | 6 +- src/zen/common/ZenUIManager.mjs | 51 +++++ src/zen/common/styles/zen-animations.css | 194 +----------------- src/zen/common/styles/zen-browser-ui.css | 4 +- src/zen/common/styles/zen-omnibox.css | 44 +++- src/zen/common/zenThemeModifier.js | 22 ++ src/zen/compact-mode/ZenCompactMode.mjs | 1 + src/zen/compact-mode/zen-compact-mode.css | 69 +++---- src/zen/split-view/ZenViewSplitter.mjs | 28 ++- src/zen/urlbar/ZenUBActionsProvider.sys.mjs | 74 +++++-- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 14 +- src/zen/urlbar/ZenUBResultsLearner.sys.mjs | 108 ++++++++++ src/zen/urlbar/moz.build | 1 + surfer.json | 2 +- 24 files changed, 419 insertions(+), 349 deletions(-) create mode 100644 src/zen/urlbar/ZenUBResultsLearner.sys.mjs diff --git a/locales/en-US/browser/browser/zen-general.ftl b/locales/en-US/browser/browser/zen-general.ftl index 8f81afbb0..708bc05d9 100644 --- a/locales/en-US/browser/browser/zen-general.ftl +++ b/locales/en-US/browser/browser/zen-general.ftl @@ -62,3 +62,5 @@ zen-icons-picker-emoji = .label = Emojis zen-icons-picker-svg = .label = Icons + +urlbar-search-mode-zen_actions = Actions diff --git a/package.json b/package.json index d0fc05677..ea0542817 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "lint-staged": { "**/*": "" }, + "type": "module", "scripts": { "build": "surfer build", "build:ui": "surfer build --ui", diff --git a/src/browser/components/sessionstore/TabState-sys-mjs.patch b/src/browser/components/sessionstore/TabState-sys-mjs.patch index b216d7090..2100e2334 100644 --- a/src/browser/components/sessionstore/TabState-sys-mjs.patch +++ b/src/browser/components/sessionstore/TabState-sys-mjs.patch @@ -1,8 +1,8 @@ diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs -index 82721356d191055bec0d4b0ca49e481221988801..9c8a2b1791e780e0fcd3a9bfc7efdadf35d52165 100644 +index 82721356d191055bec0d4b0ca49e481221988801..1ea5c394c704da295149443d7794961a12f2060b 100644 --- a/browser/components/sessionstore/TabState.sys.mjs +++ b/browser/components/sessionstore/TabState.sys.mjs -@@ -85,6 +85,18 @@ class _TabState { +@@ -85,7 +85,22 @@ class _TabState { tabData.groupId = tab.group.id; } @@ -19,5 +19,9 @@ index 82721356d191055bec0d4b0ca49e481221988801..9c8a2b1791e780e0fcd3a9bfc7efdadf + tabData.zenIsGlance = tab.hasAttribute("zen-glance-tab"); + tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true); ++ if (tabData.searchMode?.source === tab.ownerGlobal.UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS) { ++ delete tabData.searchMode; ++ } tabData.userContextId = tab.userContextId || 0; + diff --git a/src/browser/components/urlbar/UrlbarController-sys-mjs.patch b/src/browser/components/urlbar/UrlbarController-sys-mjs.patch index 884bfb156..cd90f6116 100644 --- a/src/browser/components/urlbar/UrlbarController-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarController-sys-mjs.patch @@ -1,13 +1,13 @@ diff --git a/browser/components/urlbar/UrlbarController.sys.mjs b/browser/components/urlbar/UrlbarController.sys.mjs -index 36e3ab4a5a153230bb488b66dda7e3e7c763ca23..28b59c7c3a95febafc3f2a6e0ac3493b9785ff1a 100644 +index 36e3ab4a5a153230bb488b66dda7e3e7c763ca23..81f2944b939ac0963c129f86aab0b55817349401 100644 --- a/browser/components/urlbar/UrlbarController.sys.mjs +++ b/browser/components/urlbar/UrlbarController.sys.mjs -@@ -411,7 +411,7 @@ export class UrlbarController { - // When there's no search string and no view selection, we want to focus - // the next toolbar item instead, for accessibility reasons. - let allowTabbingThroughResults = -- this.input.focusedViaMousedown || -+ true || - this.input.searchMode?.isPreview || - this.input.searchMode?.source == - lazy.UrlbarUtils.RESULT_SOURCE.ACTIONS || +@@ -434,6 +434,8 @@ export class UrlbarController { + }); + } + event.preventDefault(); ++ } else { ++ this.browserWindow.gZenUIManager.enableCommandsMode(event); + } + break; + } diff --git a/src/browser/components/urlbar/UrlbarInput-sys-mjs.patch b/src/browser/components/urlbar/UrlbarInput-sys-mjs.patch index 79eba8b6d..91f5a08ac 100644 --- a/src/browser/components/urlbar/UrlbarInput-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarInput-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/urlbar/UrlbarInput.sys.mjs b/browser/components/urlbar/UrlbarInput.sys.mjs -index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c563851ae89385 100644 +index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..1dc520f63b240cccda7be074346d2079774eed27 100644 --- a/browser/components/urlbar/UrlbarInput.sys.mjs +++ b/browser/components/urlbar/UrlbarInput.sys.mjs @@ -74,6 +74,13 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () => @@ -184,7 +184,20 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 let controller = this.document.commandDispatcher.getControllerForCommand("cmd_paste"); -@@ -4130,6 +4195,7 @@ export class UrlbarInput { +@@ -3836,7 +3901,11 @@ export class UrlbarInput { + if (!engineName && !source && !this.hasAttribute("searchmode")) { + return; + } +- ++ this.window.dispatchEvent( ++ new CustomEvent("Zen:UrlbarSearchModeChanged", { ++ detail: { searchMode }, ++ }) ++ ); + this._searchModeIndicatorTitle.textContent = ""; + this._searchModeIndicatorTitle.removeAttribute("data-l10n-id"); + +@@ -4130,6 +4199,7 @@ export class UrlbarInput { this.document.l10n.setAttributes( this.inputField, @@ -192,7 +205,7 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 l10nId, l10nId == "urlbar-placeholder-with-name" ? { name } : undefined ); -@@ -4241,6 +4307,11 @@ export class UrlbarInput { +@@ -4241,6 +4311,11 @@ export class UrlbarInput { } _on_click(event) { @@ -204,7 +217,7 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 if ( event.target == this.inputField || event.target == this._inputContainer -@@ -4311,7 +4382,7 @@ export class UrlbarInput { +@@ -4311,7 +4386,7 @@ export class UrlbarInput { } } @@ -213,7 +226,7 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 this.view.autoOpen({ event }); } else { if (this._untrimOnFocusAfterKeydown) { -@@ -4351,9 +4422,16 @@ export class UrlbarInput { +@@ -4351,9 +4426,16 @@ export class UrlbarInput { } _on_mousedown(event) { @@ -231,7 +244,7 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 if ( event.target != this.inputField && -@@ -4364,6 +4442,10 @@ export class UrlbarInput { +@@ -4364,6 +4446,10 @@ export class UrlbarInput { this.focusedViaMousedown = !this.focused; this._preventClickSelectsAll = this.focused; @@ -242,7 +255,7 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 // Keep the focus status, since the attribute may be changed // upon calling this.focus(). -@@ -4399,7 +4481,7 @@ export class UrlbarInput { +@@ -4399,7 +4485,7 @@ export class UrlbarInput { } // Don't close the view when clicking on a tab; we may want to keep the // view open on tab switch, and the TabSelect event arrived earlier. @@ -251,3 +264,12 @@ index 1c447bd31de854d1522dbcfb5d7ad557c84f1388..2341d04c5afee303ce4150c3c2c56385 break; } +@@ -4716,7 +4802,7 @@ export class UrlbarInput { + // When we are in actions search mode we can show more results so + // increase the limit. + let maxResults = +- this.searchMode?.source != lazy.UrlbarUtils.RESULT_SOURCE.ACTIONS ++ this.searchMode?.source != lazy.UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS + ? lazy.UrlbarPrefs.get("maxRichResults") + : UNLIMITED_MAX_RESULTS; + let options = { diff --git a/src/browser/components/urlbar/UrlbarMuxerStandard-sys-mjs.patch b/src/browser/components/urlbar/UrlbarMuxerStandard-sys-mjs.patch index 607d78a00..f1c290366 100644 --- a/src/browser/components/urlbar/UrlbarMuxerStandard-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarMuxerStandard-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs b/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs -index cdc476a3eb2ee2cb6193d215513b65ed375f6153..a2b106916d6cca25096d37b80bea45f016ad82a5 100644 +index cdc476a3eb2ee2cb6193d215513b65ed375f6153..bc66d9651e521bda75a3bb9e7f1e4b3bb325be90 100644 --- a/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs +++ b/browser/components/urlbar/UrlbarMuxerStandard.sys.mjs @@ -855,6 +855,7 @@ class MuxerUnifiedComplete extends UrlbarMuxer { @@ -10,3 +10,13 @@ index cdc476a3eb2ee2cb6193d215513b65ed375f6153..a2b106916d6cca25096d37b80bea45f0 // Discard the result if a tab-to-search result was added already. if (!state.canAddTabToSearch) { return false; +@@ -1501,7 +1502,9 @@ class MuxerUnifiedComplete extends UrlbarMuxer { + usedLimits.maxResultCount++; + } + ++ if (!(result.heuristic && result.source == UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS)) { + state.usedResultSpan += span; ++ } + this._updateStatePostAdd(result, state); + + return true; diff --git a/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch b/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch index 6cc0c51c1..2b93e360f 100644 --- a/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarPrefs-sys-mjs.patch @@ -1,12 +1,12 @@ diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs -index 3c179db3b310c43f8c6c06b1ecbcf5ed59feefe6..693bef15401cd4428c8a0222de57b83b78564194 100644 +index 3c179db3b310c43f8c6c06b1ecbcf5ed59feefe6..d9d2ce116ebcee8d403e165066c3a569bb952cd2 100644 --- a/browser/components/urlbar/UrlbarPrefs.sys.mjs +++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs @@ -719,6 +719,7 @@ function makeResultGroups({ showSearchSuggestionsFirst }) { */ let rootGroup = { children: [ -+ { group: lazy.UrlbarUtils.RESULT_GROUP.ZEN_ACTION }, ++ { children: [{ group: lazy.UrlbarUtils.RESULT_GROUP.ZEN_ACTION }] }, // heuristic { maxResultCount: 1, diff --git a/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch b/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch index cc0b30de1..b477dc892 100644 --- a/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarUtils-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs -index 0bc15c02f56dd8f46a21fed02b4e21a741f27f41..b69a4d34f700637bd352620c520b989cf00fa39f 100644 +index 0bc15c02f56dd8f46a21fed02b4e21a741f27f41..40da868f68f21d8411107fb8a95e2d0b74337b51 100644 --- a/browser/components/urlbar/UrlbarUtils.sys.mjs +++ b/browser/components/urlbar/UrlbarUtils.sys.mjs @@ -75,6 +75,7 @@ export var UrlbarUtils = { @@ -10,12 +10,20 @@ index 0bc15c02f56dd8f46a21fed02b4e21a741f27f41..b69a4d34f700637bd352620c520b989c }), // Defines provider types. -@@ -576,6 +577,8 @@ export var UrlbarUtils = { - return this.RESULT_GROUP.INPUT_HISTORY; - case "UrlbarProviderQuickSuggest": - return this.RESULT_GROUP.GENERAL_PARENT; -+ case "ZenUrlbarProviderGlobalActions": -+ return this.RESULT_GROUP.ZEN_ACTION; - default: - break; - } +@@ -134,6 +135,7 @@ export var UrlbarUtils = { + OTHER_NETWORK: 6, + ADDON: 7, + ACTIONS: 8, ++ ZEN_ACTIONS: 9, + }), + + // Per-result exposure telemetry. +@@ -553,6 +555,8 @@ export var UrlbarUtils = { + return this.RESULT_GROUP.HEURISTIC_SEARCH_TIP; + case "HistoryUrlHeuristic": + return this.RESULT_GROUP.HEURISTIC_HISTORY_URL; ++ case "ZenUrlbarProviderGlobalActions": ++ return this.RESULT_GROUP.ZEN_ACTION; + default: + if (result.providerName.startsWith("TestProvider")) { + return this.RESULT_GROUP.HEURISTIC_TEST; diff --git a/src/browser/components/urlbar/UrlbarView-sys-mjs.patch b/src/browser/components/urlbar/UrlbarView-sys-mjs.patch index 15c3687e2..0e0812bd7 100644 --- a/src/browser/components/urlbar/UrlbarView-sys-mjs.patch +++ b/src/browser/components/urlbar/UrlbarView-sys-mjs.patch @@ -1,5 +1,5 @@ diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs -index fdbab8806fd320f4aacec46a42c8ef953580d00c..e23fae0d7e0b71d74899c11c229359864cd7e427 100644 +index fdbab8806fd320f4aacec46a42c8ef953580d00c..031a615ad09274c578184b129434bbd93b49353d 100644 --- a/browser/components/urlbar/UrlbarView.sys.mjs +++ b/browser/components/urlbar/UrlbarView.sys.mjs @@ -613,7 +613,7 @@ export class UrlbarView { @@ -11,39 +11,7 @@ index fdbab8806fd320f4aacec46a42c8ef953580d00c..e23fae0d7e0b71d74899c11c22935986 // Try to reuse the cached top-sites context. If it's not cached, then // there will be a gap of time between when the input is focused and // when the view opens that can be perceived as flicker. -@@ -824,6 +824,31 @@ export class UrlbarView { - // still associated with the first result. - this.input.setResultForCurrentValue(firstResult); - } -+ if (queryContext.results[0].payload.zenAction) { -+ this.#selectElement(this.getFirstSelectableElement(), { -+ updateInput: false, -+ setAccessibleFocus: -+ this.controller._userSelectionBehavior == "arrow", -+ }); -+ this.window.setTimeout(() => { -+ this.window.setTimeout(() => { -+ this.#selectElement(this.getFirstSelectableElement(), { -+ updateInput: false, -+ setAccessibleFocus: -+ this.controller._userSelectionBehavior == "arrow", -+ }); -+ }); -+ }); -+ } -+ this.window.setTimeout(() => { -+ if (queryContext.results[0].payload.zenAction) { -+ this.#selectElement(this.getFirstSelectableElement(), { -+ updateInput: false, -+ setAccessibleFocus: -+ this.controller._userSelectionBehavior == "arrow", -+ }); -+ } -+ }, 220); - } - - // Announce tab-to-search results to screen readers as the user types. -@@ -2706,6 +2731,8 @@ export class UrlbarView { +@@ -2706,6 +2706,8 @@ export class UrlbarView { if (row?.hasAttribute("row-selectable")) { row?.toggleAttribute("selected", true); } @@ -52,7 +20,7 @@ index fdbab8806fd320f4aacec46a42c8ef953580d00c..e23fae0d7e0b71d74899c11c22935986 if (element != row) { row?.toggleAttribute("descendant-selected", true); } -@@ -3189,7 +3216,7 @@ export class UrlbarView { +@@ -3189,7 +3191,7 @@ export class UrlbarView { } #enableOrDisableRowWrap() { diff --git a/src/zen/common/ZenCommonUtils.mjs b/src/zen/common/ZenCommonUtils.mjs index 5b0a09c67..e35178936 100644 --- a/src/zen/common/ZenCommonUtils.mjs +++ b/src/zen/common/ZenCommonUtils.mjs @@ -103,6 +103,7 @@ var gZenCommonActions = { gZenUIManager.showToast('zen-copy-current-url-confirmation', { button, timeout: 3000 }); } }, + copyCurrentURLAsMarkdownToClipboard() { const currentUrl = gBrowser.currentURI.spec; const tabTitle = gBrowser.selectedTab.label; diff --git a/src/zen/common/ZenStartup.mjs b/src/zen/common/ZenStartup.mjs index 6bfdbd62d..3fa3d061c 100644 --- a/src/zen/common/ZenStartup.mjs +++ b/src/zen/common/ZenStartup.mjs @@ -29,8 +29,12 @@ background.appendChild(grain); document.getElementById('browser').prepend(background); const toolbarBackground = background.cloneNode(true); - toolbarBackground.id = 'zen-toolbar-background'; + toolbarBackground.removeAttribute('id'); + toolbarBackground.classList.add('zen-toolbar-background'); document.getElementById('titlebar').prepend(toolbarBackground); + document + .getElementById('zen-appcontent-navbar-container') + .prepend(toolbarBackground.cloneNode(true)); } #zenInitBrowserLayout() { diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index c22e7c3a2..055670d0c 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -59,6 +59,10 @@ var gZenUIManager = { }); window.addEventListener('TabClose', this.onTabClose.bind(this)); + window.addEventListener( + 'Zen:UrlbarSearchModeChanged', + this.onUrlbarSearchModeChanged.bind(this) + ); gZenMediaController.init(); gZenVerticalTabsManager.init(); @@ -241,6 +245,53 @@ var gZenUIManager = { // Section: URL bar + onUrlbarSearchModeChanged(event) { + const { searchMode } = event.detail; + const input = gURLBar.textbox; + if (gURLBar.hasAttribute('breakout-extend') && !this._animatingSearchMode) { + this._animatingSearchMode = true; + this.motion + .animate(input, { scale: [1, 0.98, 1] }, { duration: 0.25, delay: 0.1 }) + .then(() => { + delete this._animatingSearchMode; + }); + if (searchMode) { + gURLBar.setAttribute('animate-searchmode', 'true'); + this._animatingSearchModeTimeout = setTimeout(() => { + requestAnimationFrame(() => { + gURLBar.removeAttribute('animate-searchmode'); + delete this._animatingSearchModeTimeout; + }); + }, 700); + } + } + }, + + enableCommandsMode(event) { + event.preventDefault(); + if (!gURLBar.hasAttribute('breakout-extend') || this._animatingSearchMode) { + return; + } + const currentSearchMode = gURLBar.getSearchMode(gBrowser.selectedBrowser); + let searchMode = null; + if (!currentSearchMode) { + searchMode = { + source: UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS, + isPreview: true, + }; + } + gURLBar.removeAttribute('animate-searchmode'); + if (this._animatingSearchModeTimeout) { + clearTimeout(this._animatingSearchModeTimeout); + delete this._animatingSearchModeTimeout; + } + gURLBar.searchMode = searchMode; + gURLBar.startQuery({ + allowAutofill: false, + event, + }); + }, + get newtabButtons() { return document.querySelectorAll('#tabs-newtab-button'); }, diff --git a/src/zen/common/styles/zen-animations.css b/src/zen/common/styles/zen-animations.css index 240825b22..6956fbc7f 100644 --- a/src/zen/common/styles/zen-animations.css +++ b/src/zen/common/styles/zen-animations.css @@ -57,113 +57,6 @@ } } -@keyframes better-sidebar-pinned-hide { - 0% { - opacity: 1; - transform: scale(1) rotateX(0deg); - } - - 100% { - transform: scale(0.99) rotateX(-5deg); - opacity: 0; - } -} - -@keyframes better-sidebar-pinned-show { - 0% { - opacity: 0; - - transform: scale(0.99) rotateX(-5deg); - } - - 100% { - transform: scale(1) rotateX(0deg); - opacity: 1; - } -} - -@keyframes better-sidebar-hide { - 0% { - opacity: 1; - - transform: scale(1) rotateX(0deg); - } - - 100% { - transform: scale(0.99) rotateX(-5deg); - opacity: 0; - } -} - -@keyframes better-sidebar-show { - 0% { - opacity: 0; - } - - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes better-sidebar-intro-show { - 0% { - opacity: 0; - transform: translateY(5px); - } - - 100% { - transform: translateY(0px); - opacity: 1; - } -} - -@keyframes zen-vtabs-animation { - 0% { - opacity: 0; - transform: translateX(-10px); - } - - 20% { - opacity: 1; - } - - 100% { - transform: translateX(0); - } -} - -@keyframes zen-sidebar-panel-animation-right { - 0% { - opacity: 0; - transform: translateX(10px); - } - - 20% { - opacity: 1; - } - - 100% { - transform: translateX(0); - } -} - -@keyframes zen-sidebar-panel-animation-reverse { - 0% { - opacity: 1; - transform: translateX(0) scale3d(1, 1, 1); - } - - 99% { - opacity: 0; - transform: translateX(-50px) scale3d(0.95, 0.95, 0.95); - } - - 100% { - display: none !important; - } -} - /* Mark: Zen Glance */ @keyframes zen-glance-overlay-animation { from { @@ -187,82 +80,6 @@ } } -@keyframes zen-rice-form-out { - 0% { - transform: translateX(0); - max-height: 350px; - opacity: 1; - position: relative; - } - - 50% { - transform: translateX(-100%); - opacity: 0; - position: relative; - } - - 99% { - transform: translateX(-100%); - opacity: 0; - max-height: 15px; - position: relative; - } - - 100% { - transform: translateX(-100%); - opacity: 0; - pointer-events: none; - position: absolute; - } -} - -@keyframes zen-rice-form-in { - 0% { - position: absolute; - transform: translateX(100%); - opacity: 0; - } - - 99% { - position: absolute; - transform: translateX(0); - opacity: 1; - } - - 100% { - position: relative; - } -} - -@keyframes zen-rice-form-in-2 { - from { - opacity: 0; - transform: translateX(100%); - max-height: 50px; - } - - to { - opacity: 1; - transform: translateX(0); - max-height: 450px; - } -} - -@keyframes zen-rice-submit-animation { - /* Scale and shake the dialog */ - 0% { - transform: scale(1); - } - - 50% { - transform: scale(1.1); - } - - 100% { - transform: scale(1); - } -} - @keyframes zen-back-and-forth-text { 0%, 10% { @@ -282,3 +99,14 @@ left: 0; } } + +/* Mark: URL Bar */ +@keyframes zen-urlbar-searchmode { + 0% { + box-shadow: 0 0 20px color-mix(in srgb, var(--zen-primary-color), var(--toolbox-textcolor) 40%); + } + + 100% { + box-shadow: 0 0 300px color-mix(in srgb, var(--zen-primary-color), transparent 100%); + } +} diff --git a/src/zen/common/styles/zen-browser-ui.css b/src/zen/common/styles/zen-browser-ui.css index 468021407..c542eccd4 100644 --- a/src/zen/common/styles/zen-browser-ui.css +++ b/src/zen/common/styles/zen-browser-ui.css @@ -64,7 +64,7 @@ transition: 0s; } - &:is(#zen-toolbar-background) { + &:is(.zen-toolbar-background) { &::after { background: var(--zen-main-browser-background-toolbar); } @@ -76,7 +76,7 @@ transition: 0s; } - &:is(#zen-toolbar-background) { + &:is(.zen-toolbar-background) { &::before { background: var(--zen-main-browser-background-toolbar-old); } diff --git a/src/zen/common/styles/zen-omnibox.css b/src/zen/common/styles/zen-omnibox.css index 90d675f82..9219437a9 100644 --- a/src/zen/common/styles/zen-omnibox.css +++ b/src/zen/common/styles/zen-omnibox.css @@ -155,15 +155,15 @@ order: 2 !important; } -#urlbar[breakout] { - position: fixed; -} - #urlbar[breakout-extend='true'] { + position: fixed; z-index: 2; & .urlbar-input-container { font-weight: 400; + @media (-moz-platform: windows) { + font-weight: 500; + } } & #identity-box { @@ -188,13 +188,28 @@ ) !important; box-shadow: 0px 0px 90px -10px light-dark(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.75)) !important; backdrop-filter: none !important; - border-radius: 12px !important; outline: 0.5px solid light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.2)) !important; outline-offset: var(--zen-urlbar-outline-offset) !important; @media -moz-pref('zen.theme.acrylic-elements') { backdrop-filter: blur(42px) saturate(110%) brightness(0.25) contrast(100%) !important; } } + + &, + .urlbar-background { + border-radius: 12px !important; + } + + &[breakout-extend][animate-searchmode='true']::before { + position: absolute; + content: ''; + inset: 0; + width: 100%; + height: 100%; + border-radius: inherit; + pointer-events: none; + animation: zen-urlbar-searchmode ease-out 0.7s forwards; + } } #urlbar-go-button { @@ -444,7 +459,7 @@ font-size: 1.5em !important; } top: var(--zen-urlbar-top) !important; - transform: translateX(-50%); + translate: -50% 0%; left: 50% !important; #urlbar-container:has(&) { @@ -617,7 +632,7 @@ var(--zen-primary-color) 50%, light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.2)) 50% ); - --zen-selected-color: color-mix(in srgb, var(--zen-selected-bg), light-dark(white, black) 40%); + --zen-selected-color: color-mix(in srgb, var(--zen-selected-bg), black 30%); background-color: var(--zen-selected-bg) !important; & *, @@ -627,6 +642,7 @@ & .urlbarView-favicon { fill: currentColor !important; + stroke: currentColor !important; } & .urlbarView-shortcutContent { @@ -656,8 +672,14 @@ & .action-contextualidentity { display: none; } - - .urlbarView[noresults] > .urlbarView-body-outer > .urlbarView-body-inner > & { - display: none; - } +} + +/* Hide the results body when there are no results, to avoid showing an empty box */ +.urlbarView[noresults] > .urlbarView-body-outer > .urlbarView-body-inner, +#urlbar-search-mode-indicator-close { + display: none; +} + +#urlbar-search-mode-indicator-title { + font-weight: 600; } diff --git a/src/zen/common/zenThemeModifier.js b/src/zen/common/zenThemeModifier.js index 4f8017dc8..4c940536d 100644 --- a/src/zen/common/zenThemeModifier.js +++ b/src/zen/common/zenThemeModifier.js @@ -46,12 +46,27 @@ var ZenThemeModifier = { Services.prefs.addObserver(pref, handleEvent); } + // Add fullscreen listener to update the theme when going in and out of fullscreen + const eventsForSeparation = [ + 'ZenViewSplitter:SplitViewDeactivated', + 'ZenViewSplitter:SplitViewActivated', + 'fullscreen', + 'ZenCompactMode:Toggled', + ]; + const separationHandler = this.updateElementSeparation.bind(this); + for (let eventName of eventsForSeparation) { + window.addEventListener(eventName, separationHandler); + } + window.addEventListener( 'unload', () => { for (let pref of kZenThemePrefsList) { Services.prefs.removeObserver(pref, handleEvent); } + for (let eventName of eventsForSeparation) { + window.removeEventListener(eventName, separationHandler); + } }, { once: true } ); @@ -78,6 +93,13 @@ var ZenThemeModifier = { updateElementSeparation() { let separation = this.elementSeparation; + if ( + window.fullScreen && + window.gZenCompactModeManager?.preference && + !document.getElementById('tabbrowser-tabbox')?.hasAttribute('zen-split-view') + ) { + separation = 0; + } document.documentElement.style.setProperty('--zen-element-separation', separation + 'px'); if (separation == 0) { document.documentElement.setAttribute('zen-no-padding', true); diff --git a/src/zen/compact-mode/ZenCompactMode.mjs b/src/zen/compact-mode/ZenCompactMode.mjs index 74b20aa64..e0b76019a 100644 --- a/src/zen/compact-mode/ZenCompactMode.mjs +++ b/src/zen/compact-mode/ZenCompactMode.mjs @@ -246,6 +246,7 @@ var gZenCompactModeManager = { } else { ZenHasPolyfill.disconnectObserver(this.sidebarObserverId); } + window.dispatchEvent(new CustomEvent('ZenCompactMode:Toggled', { detail: this.preference })); }, // NOTE: Dont actually use event, it's just so we make sure diff --git a/src/zen/compact-mode/zen-compact-mode.css b/src/zen/compact-mode/zen-compact-mode.css index 27f3619e9..4f8f6aaa4 100644 --- a/src/zen/compact-mode/zen-compact-mode.css +++ b/src/zen/compact-mode/zen-compact-mode.css @@ -5,9 +5,32 @@ */ /* All overrides for compact mode go here */ -#zen-toolbar-background { +.zen-toolbar-background { display: none; pointer-events: none; + + overflow: clip; + z-index: -1; + background: black; /* Any color thats not white */ + box-shadow: var(--zen-big-shadow); + @media -moz-pref('zen.theme.acrylic-elements') { + background: transparent; + backdrop-filter: blur(42px) saturate(110%) brightness(0.25) contrast(100%) !important; + } + + &::before, + &::after { + outline: 1px solid rgba(255, 255, 255, .15); + outline-offset: -1px; + background-attachment: fixed !important; + background-size: 100vw 100vh !important; + } + + &, + &::before, + &::after { + border-radius: calc(var(--zen-native-inner-radius) + var(--zen-element-separation) / 4 - var(--zen-compact-mode-no-padding-radius-fix, 0px)); + } } :root[zen-compact-mode='true']:not([customizing]):not([inDOMFullscreen='true']) { @@ -140,30 +163,8 @@ width: calc(var(--zen-sidebar-width) + var(--zen-toolbox-padding)); } - & #zen-toolbar-background { + & .zen-toolbar-background { display: flex; - overflow: clip; - z-index: -1; - background: black; /* Any color thats not white */ - box-shadow: var(--zen-big-shadow); - @media -moz-pref('zen.theme.acrylic-elements') { - background: transparent; - backdrop-filter: blur(42px) saturate(110%) brightness(0.25) contrast(100%) !important; - } - - &::before, - &::after { - outline: 1px solid rgba(255, 255, 255, .15); - outline-offset: -1px; - background-attachment: fixed !important; - background-size: 100vw !important; - } - - &, - &::before, - &::after { - border-radius: calc(var(--zen-native-inner-radius) + var(--zen-element-separation) / 4 - var(--zen-compact-mode-no-padding-radius-fix, 0px)); - } } } @@ -348,11 +349,6 @@ } transition: all 0.15s ease; width: 100%; - background: var(--zen-dialog-background); - - & > * { - position: relative !important; - } } } @@ -364,19 +360,8 @@ ) { & #zen-appcontent-navbar-container { visibility: visible !important; - :root[zen-show-grainy-background='true'] &::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-image: url(chrome://browser/content/zen-images/grain-bg.png); - pointer-events: none; - z-index: 0; - opacity: var(--zen-grainy-background-opacity, 0); - mix-blend-mode: overlay; - transition: opacity 0.3s ease-in-out; + & .zen-toolbar-background { + display: flex; } } border-top-width: 0px; diff --git a/src/zen/split-view/ZenViewSplitter.mjs b/src/zen/split-view/ZenViewSplitter.mjs index f61d2a78d..f80455bb6 100644 --- a/src/zen/split-view/ZenViewSplitter.mjs +++ b/src/zen/split-view/ZenViewSplitter.mjs @@ -297,7 +297,11 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { } // Add a min width to all the browser elements to prevent them from resizing const panelsWidth = gBrowser.tabbox.getBoundingClientRect().width; - const halfWidth = panelsWidth / 2; + let numOfTabsToDivide = 2; + if (currentView) { + numOfTabsToDivide = currentView.tabs.length + 1; + } + const halfWidth = panelsWidth / numOfTabsToDivide; let threshold = gNavToolbox.getBoundingClientRect().width * (gZenVerticalTabsManager._prefsRightSide ? 0 : 1); @@ -397,7 +401,12 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { return; } const panelsWidth = panelsRect.width; - const halfWidth = panelsWidth / 2; + let numOfTabsToDivide = 2; + const currentView = this._data[this._lastOpenedTab.splitViewValue]; + if (currentView) { + numOfTabsToDivide = currentView.tabs.length + 1; + } + const halfWidth = panelsWidth / numOfTabsToDivide; const padding = ZenThemeModifier.elementSeparation; if (!this.fakeBrowser) { return; @@ -1180,6 +1189,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { this.currentView = -1; this.toggleWrapperDisplay(false); this.maybeDisableOpeningTabOnSplitView(); + window.dispatchEvent(new CustomEvent('ZenViewSplitter:SplitViewDeactivated')); } /** @@ -1754,24 +1764,12 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { } else { // Create new split view with layout based on drop position let gridType = 'vsep'; - //switch (hoverSide) { - // case 'left': - // case 'right': - // gridType = 'vsep'; - // break; - // case 'top': - // case 'bottom': - // gridType = 'hsep'; - // break; - // default: - // gridType = 'grid'; - //} // Put tabs always as if it was dropped from the left this.splitTabs( dropSide == 'left' ? [draggedTab, droppedOnTab] : [droppedOnTab, draggedTab], gridType, - 1 + dropSide == 'left' ? 0 : 1 ); } } diff --git a/src/zen/urlbar/ZenUBActionsProvider.sys.mjs b/src/zen/urlbar/ZenUBActionsProvider.sys.mjs index e8f3b2ff1..a14825a7e 100644 --- a/src/zen/urlbar/ZenUBActionsProvider.sys.mjs +++ b/src/zen/urlbar/ZenUBActionsProvider.sys.mjs @@ -5,6 +5,7 @@ import { XPCOMUtils } from 'resource://gre/modules/XPCOMUtils.sys.mjs'; import { UrlbarProvider, UrlbarUtils } from 'resource:///modules/UrlbarUtils.sys.mjs'; import { globalActions } from 'resource:///modules/ZenUBGlobalActions.sys.mjs'; +import { zenUrlbarResultsLearner } from './ZenUBResultsLearner.sys.mjs'; const lazy = {}; @@ -12,9 +13,9 @@ const DYNAMIC_TYPE_NAME = 'zen-actions'; // The suggestion index of the actions row within the urlbar results. const MAX_RECENT_ACTIONS = 5; -const MINIMUM_QUERY_SCORE = 92; -const EN_LOCALE_MATCH = /^en(-.*)$/; +const MINIMUM_QUERY_SCORE = 92; +const MINIMUM_PREFIXED_QUERY_SCORE = 50; ChromeUtils.defineESModuleGetters(lazy, { UrlbarResult: 'resource:///modules/UrlbarResult.sys.mjs', @@ -22,6 +23,7 @@ ChromeUtils.defineESModuleGetters(lazy, { QueryScorer: 'resource:///modules/UrlbarProviderInterventions.sys.mjs', BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs', AddonManager: 'resource://gre/modules/AddonManager.sys.mjs', + zenUrlbarResultsLearner: 'resource:///modules/ZenUBResultsLearner.sys.mjs', }); XPCOMUtils.defineLazyPreferenceGetter( @@ -35,6 +37,8 @@ XPCOMUtils.defineLazyPreferenceGetter( * A provider that lets the user view all available global actions for a query. */ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { + #seenCommands = new Set(); + constructor() { super(); lazy.UrlbarResult.addDynamicResultType(DYNAMIC_TYPE_NAME); @@ -60,12 +64,12 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { */ async isActive(queryContext) { return ( - lazy.enabledPref && - queryContext.searchString && - queryContext.searchString.length < UrlbarUtils.MAX_TEXT_LENGTH && - queryContext.searchString.length > 2 && - !lazy.UrlbarTokenizer.REGEXP_LIKE_PROTOCOL.test(queryContext.searchString) && - EN_LOCALE_MATCH.test(Services.locale.appLocaleAsBCP47) + queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS || + (lazy.enabledPref && + queryContext.searchString && + queryContext.searchString.length < UrlbarUtils.MAX_TEXT_LENGTH && + queryContext.searchString.length > 2 && + !lazy.UrlbarTokenizer.REGEXP_LIKE_PROTOCOL.test(queryContext.searchString)) ); } @@ -92,6 +96,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { prettyIcon: workspace.icon, accentColor, }, + commandId: `zen:workspace-${workspace.uuid}`, icon: 'chrome://browser/skin/zen-icons/forward.svg', }); } @@ -116,6 +121,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { return { icon: 'chrome://browser/skin/zen-icons/extension.svg', label: 'Extension', + commandId: `zen:extension-${addon.id}`, extraPayload: { extensionId: addon.id, prettyName: addon.name, @@ -142,14 +148,18 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { * @param {string} query The user's search query. * */ - async #findMatchingActions(query) { + async #findMatchingActions(query, isPrefixed) { const window = lazy.BrowserWindowTracker.getTopWindow(); const actions = await this.#getAvailableActions(window); let results = []; for (let action of actions) { + if (isPrefixed && query.length < 1) { + results.push({ action, score: 100 }); + continue; + } const label = action.extraPayload?.prettyName || action.label; const score = this.#calculateFuzzyScore(label, query); - if (score > MINIMUM_QUERY_SCORE) { + if (score > (isPrefixed ? MINIMUM_PREFIXED_QUERY_SCORE : MINIMUM_QUERY_SCORE)) { results.push({ score, action, @@ -157,6 +167,10 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { } } results.sort((a, b) => b.score - a.score); + // We must show all we can when prefixed, to avoid showing no results. + if (isPrefixed) { + return results.map((r) => r.action); + } return results.slice(0, MAX_RECENT_ACTIONS).map((r) => r.action); } @@ -217,16 +231,18 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { async startQuery(queryContext, addCallback) { const query = queryContext.trimmedLowerCaseSearchString; - if (!query) { + const isPrefixed = queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS; + if (!query && !isPrefixed) { return; } - const actionsResults = await this.#findMatchingActions(query); + const actionsResults = await this.#findMatchingActions(query, isPrefixed); if (!actionsResults.length) { return; } const ownerGlobal = lazy.BrowserWindowTracker.getTopWindow(); + const finalResults = []; for (const action of actionsResults) { const [payload, payloadHighlights] = lazy.UrlbarResult.payloadAndSimpleHighlights([], { suggestion: action.label, @@ -235,7 +251,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { zenCommand: action.command, dynamicType: DYNAMIC_TYPE_NAME, zenAction: true, - icon: action.icon || 'chrome://browser/skin/trending.svg', + icon: action.icon, shortcutContent: ownerGlobal.gZenKeyboardShortcutsManager.getShortcutDisplayFromCommand( action.command ), @@ -245,15 +261,26 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { let result = new lazy.UrlbarResult( UrlbarUtils.RESULT_TYPE.DYNAMIC, - UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, + UrlbarUtils.RESULT_SOURCE.ZEN_ACTIONS, payload, payloadHighlights ); - if (typeof action.suggestedIndex === 'number') { - result.suggestedIndex = action.suggestedIndex; + if (zenUrlbarResultsLearner.shouldPrioritize(action.commandId)) { + result.heuristic = true; + } else { + result.suggestedIndex = zenUrlbarResultsLearner.getDeprioritizeIndex(action.commandId); } - addCallback(this, result); + result.commandId = action.commandId; + if (!(isPrefixed && query.length < 2)) { + // We dont want to record prefixed results, as the user explicitly asked for them. + // Selecting other results would de-prioritize these actions unfairly. + this.#seenCommands.add(action.commandId); + } + finalResults.push(result); } + zenUrlbarResultsLearner.sortCommandsByPriority(finalResults).forEach((result) => { + addCallback(this, result); + }); } /** @@ -362,6 +389,19 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider { }; } + onSearchSessionEnd(_queryContext, _controller, details) { + // We should only record the execution if a result was actually used. + // Otherwise we would start de-prioritizing commands that were never used. + if (details?.result) { + let usedCommand = null; + if (details?.provider === this.name) { + usedCommand = details.result?.commandId; + } + zenUrlbarResultsLearner.recordExecution(usedCommand, [...this.#seenCommands]); + } + this.#seenCommands = new Set(); + } + onEngagement(queryContext, controller, details) { const result = details.result; const payload = result.payload; diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 2fdb25324..ba5a88abe 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -11,7 +11,6 @@ const globalActionsTemplate = [ label: 'Toggle Compact Mode', command: 'cmd_zenCompactModeToggle', icon: 'chrome://browser/skin/zen-icons/sidebar.svg', - suggestedIndex: 0, }, { label: 'Open Theme Picker', @@ -22,7 +21,6 @@ const globalActionsTemplate = [ label: 'New Split View', command: 'cmd_zenNewEmptySplit', icon: 'chrome://browser/skin/zen-icons/split.svg', - suggestedIndex: 0, }, { label: 'New Folder', @@ -33,7 +31,6 @@ const globalActionsTemplate = [ label: 'Copy Current URL', command: 'cmd_zenCopyCurrentURL', icon: 'chrome://browser/skin/zen-icons/edit-copy.svg', - suggestedIndex: 0, }, { label: 'Settings', @@ -89,7 +86,6 @@ const globalActionsTemplate = [ label: 'Close Tab', command: 'cmd_close', icon: 'chrome://browser/skin/zen-icons/close.svg', - suggestedIndex: 1, isAvailable: (window) => { return isNotEmptyTab(window); }, @@ -121,13 +117,11 @@ const globalActionsTemplate = [ isAvailable: (window) => { return isNotEmptyTab(window); }, - suggestedIndex: 1, }, { label: 'Toggle Tabs on right', command: 'cmd_zenToggleTabsOnRight', icon: 'chrome://browser/skin/zen-icons/sidebars-right.svg', - suggestedIndex: 1, }, { label: 'Add to Essentials', @@ -139,14 +133,12 @@ const globalActionsTemplate = [ ); }, icon: 'chrome://browser/skin/zen-icons/essential-add.svg', - suggestedIndex: 1, }, { label: 'Remove from Essentials', command: (window) => window.gZenPinnedTabManager.removeEssentials(window.gBrowser.selectedTab), isAvailable: (window) => window.gBrowser.selectedTab.hasAttribute('zen-essential'), icon: 'chrome://browser/skin/zen-icons/essential-remove.svg', - suggestedIndex: 1, }, { label: 'Find in Page', @@ -155,13 +147,11 @@ const globalActionsTemplate = [ isAvailable: (window) => { return isNotEmptyTab(window); }, - suggestedIndex: 1, }, { label: 'Manage Extensions', command: 'Tools:Addons', icon: 'chrome://browser/skin/zen-icons/extension.svg', - suggestedIndex: 1, }, ]; @@ -169,6 +159,10 @@ export const globalActions = globalActionsTemplate.map((action) => ({ isAvailable: (window) => { return window.document.getElementById(action.command)?.getAttribute('disabled') !== 'true'; }, + commandId: + typeof action.command === 'string' + ? action.command + : `zen:global-action-${action.label.toLowerCase().replace(/\s+/g, '-')}`, extraPayload: {}, ...action, })); diff --git a/src/zen/urlbar/ZenUBResultsLearner.sys.mjs b/src/zen/urlbar/ZenUBResultsLearner.sys.mjs new file mode 100644 index 000000000..99165f85f --- /dev/null +++ b/src/zen/urlbar/ZenUBResultsLearner.sys.mjs @@ -0,0 +1,108 @@ +/* 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 { XPCOMUtils } from 'resource://gre/modules/XPCOMUtils.sys.mjs'; + +const lazy = {}; + +const DEFAULT_DB_DATA = '{}'; +const DEPRIORITIZE_MAX = -4; +const PRIORITIZE_MAX = 5; + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + 'database', + 'zen.urlbar.suggestions-learner', + 'DEFAULT_DB_DATA' +); + +/** + * A class that manages the learning of URL bar results for commands, + * can be used for any ID that can be executed in the URL bar. + * + * The schema would be something like: + * { + * "": , + * } + * + * If the current command is not on the list is because the user + * has *seen* the command but never executed it. If the number is + * less than -2, that means the user will most likely never use it. + * + * The priority number is incremented each time the command is executed. + */ +class ZenUrlbarResultsLearner { + constructor() {} + + get database() { + try { + return JSON.parse(lazy.database); + } catch { + return {}; + } + } + + saveDatabase(db) { + Services.prefs.setStringPref( + 'zen.urlbar.suggestions-learner', + JSON.stringify(db || DEFAULT_DB_DATA) + ); + } + + recordExecution(commandId, seenCommands = []) { + const db = this.database; + if (commandId) { + const numberOfUsages = Math.min((db[commandId] || 0) + 1, PRIORITIZE_MAX); + db[commandId] = numberOfUsages; + } + for (const cmd of seenCommands) { + if (cmd !== commandId) { + if (!db[cmd]) { + db[cmd] = -1; + } else { + const newIndex = Math.max(db[cmd] - 1, DEPRIORITIZE_MAX); + db[cmd] = newIndex; + if (newIndex === 0) { + // Save some space by deleting commands that are not used + // and have a neutral score. + delete db[cmd]; + } + } + } + } + this.saveDatabase(db); + } + + shouldPrioritize(commandId) { + if (!commandId) { + return false; + } + const db = this.database; + return !!db[commandId] && db[commandId] > 0; + } + + getDeprioritizeIndex(commandId) { + if (!commandId) { + return 1; + } + const db = this.database; + if (db[commandId] < 0) { + return Math.abs(db[commandId]); + } + // This will most likely never run, since + // positive commands are prioritized. + return 1; + } + + /** + * Sorts the given commands by their priority in the database. + * @param {*} commands + */ + sortCommandsByPriority(commands) { + const db = this.database; + return commands.sort((a, b) => (db[b.commandId] || 0) - (db[a.commandId] || 0)); + } +} + +export const zenUrlbarResultsLearner = new ZenUrlbarResultsLearner(); diff --git a/src/zen/urlbar/moz.build b/src/zen/urlbar/moz.build index 306a7c08b..1e331db90 100644 --- a/src/zen/urlbar/moz.build +++ b/src/zen/urlbar/moz.build @@ -6,4 +6,5 @@ EXTRA_JS_MODULES += [ "ZenUBActionsProvider.sys.mjs", "ZenUBGlobalActions.sys.mjs", "ZenUBProvider.sys.mjs", + "ZenUBResultsLearner.sys.mjs", ] diff --git a/surfer.json b/surfer.json index 64ca34198..f408413bf 100644 --- a/surfer.json +++ b/surfer.json @@ -19,7 +19,7 @@ "brandShortName": "Zen", "brandFullName": "Zen Browser", "release": { - "displayVersion": "1.16.1b", + "displayVersion": "1.16.2b", "github": { "repo": "zen-browser/desktop" },