feat: Urlbar now supports 'switch space' and 'extensions', b=no-bug, c=common, kbs

This commit is contained in:
Mr. M
2025-09-14 21:31:04 +02:00
parent 148e63e226
commit d3cca04c15
5 changed files with 199 additions and 37 deletions

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs
index fdbab8806fd320f4aacec46a42c8ef953580d00c..5ed31d5dbfa2e2041e6616f6b036d67928e64114 100644
index fdbab8806fd320f4aacec46a42c8ef953580d00c..f327ca63ff4c99f7aa9acbddc69d2d8b80442d35 100644
--- a/browser/components/urlbar/UrlbarView.sys.mjs
+++ b/browser/components/urlbar/UrlbarView.sys.mjs
@@ -613,7 +613,7 @@ export class UrlbarView {
@@ -11,27 +11,25 @@ index fdbab8806fd320f4aacec46a42c8ef953580d00c..5ed31d5dbfa2e2041e6616f6b036d679
// 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.
@@ -823,6 +823,19 @@ export class UrlbarView {
@@ -823,7 +823,16 @@ export class UrlbarView {
// them, resembling tab-to-search. In that case, the input value is
// still associated with the first result.
this.input.setResultForCurrentValue(firstResult);
+ } else if (firstResult.payload.zenAction) {
+ this.#selectElement(this.getFirstSelectableElement(), {
+ updateInput: false,
+ setAccessibleFocus:
+ this.controller._userSelectionBehavior == "arrow",
+ });
+ this.window.setTimeout(() => {
- }
+ }
+ this.window.setTimeout(() => {
+ if (queryContext.results[0].payload.zenAction) {
+ this.#selectElement(this.getFirstSelectableElement(), {
+ updateInput: false,
+ setAccessibleFocus:
+ this.controller._userSelectionBehavior == "arrow",
+ });
+ }, 140);
}
+ }
+ }, 10);
}
@@ -1341,7 +1354,7 @@ export class UrlbarView {
// Announce tab-to-search results to screen readers as the user types.
@@ -1341,7 +1350,7 @@ export class UrlbarView {
includeHiddenExposures: true,
});
let canBeVisible =
@@ -40,7 +38,7 @@ index fdbab8806fd320f4aacec46a42c8ef953580d00c..5ed31d5dbfa2e2041e6616f6b036d679
if (result.isHiddenExposure) {
if (canBeVisible) {
this.controller.engagementEvent.addExposure(
@@ -3189,7 +3202,7 @@ export class UrlbarView {
@@ -3189,7 +3198,7 @@ export class UrlbarView {
}
#enableOrDisableRowWrap() {

View File

@@ -563,21 +563,52 @@ button.popup-notification-dropmarker {
}
.urlbarView-shortcutContent {
border-radius: 4px;
padding: 6px 8px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
margin-left: auto;
margin-top: auto;
margin-bottom: auto;
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 95%);
padding: 6px 8px;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
font-size: 10px;
&:empty {
display: none !important;
}
}
.urlbarView-prettyName,
.urlbarView-shortcutContent {
border-radius: 4px;
font-weight: 600;
margin-top: auto;
margin-bottom: auto;
}
.urlbarView-prettyName {
padding: 4px 6px;
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 90%);
margin-left: 6px;
font-size: 12px;
align-items: center;
gap: 6px;
display: flex;
color: color-mix(in srgb, var(--zen-primary-color), currentColor 95%);
& img {
-moz-context-properties: fill;
fill: currentColor;
width: 16px;
height: 16px;
&[workspaceIcon] {
scale: 1.4;
}
}
&[hidden] {
display: none !important;
}
}
.urlbarView-row[has-action]:is([type='switchtab'], [type='remotetab'], [type='clipboard']) {
& .urlbarView-action:last-child {
margin-left: auto !important;
@@ -635,14 +666,15 @@ button.popup-notification-dropmarker {
--urlbarView-item-inline-padding: 8px;
--urlbarView-item-block-padding: 10px;
&:hover.urlbarView-favicon,
&:hover,
& .urlbarView-shortcutContent {
background-color: color-mix(
in srgb,
var(--zen-branding-bg-reverse) 5%,
transparent 95%
) !important;
&:hover {
&,
& .urlbarView-favicon {
background-color: color-mix(
in srgb,
var(--zen-branding-bg-reverse) 5%,
transparent 95%
) !important;
}
}
&[selected] {
@@ -669,6 +701,10 @@ button.popup-notification-dropmarker {
& .urlbarView-shortcutContent {
background-color: rgba(255, 255, 255, 0.9) !important;
}
& .urlbarView-prettyName {
background-color: color-mix(in srgb, var(--zen-branding-bg-reverse), transparent 80%);
}
}
}

View File

@@ -1377,6 +1377,9 @@ var gZenKeyboardShortcutsManager = {
* @returns {string|null} The shortcut as a string or null if not found
*/
getShortcutDisplayFromCommand(command) {
if (!command) {
return null;
}
const shortcut = this.getShortcutFromCommand(command);
if (shortcut) {
return shortcut.toUserString();

View File

@@ -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 { ExtensionCommon } from 'resource://gre/modules/ExtensionCommon.sys.mjs';
const lazy = {};
@@ -21,6 +22,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
UrlbarTokenizer: 'resource:///modules/UrlbarTokenizer.sys.mjs',
QueryScorer: 'resource:///modules/UrlbarProviderInterventions.sys.mjs',
BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs',
AddonManager: 'resource://gre/modules/AddonManager.sys.mjs',
});
XPCOMUtils.defineLazyPreferenceGetter(
@@ -30,6 +32,8 @@ XPCOMUtils.defineLazyPreferenceGetter(
true
);
let { makeWidgetId } = ExtensionCommon;
/**
* A provider that lets the user view all available global actions for a query.
*/
@@ -63,12 +67,67 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
);
}
#getWorkspaceActions(window) {
if (window.gZenWorkspaces.privateWindowOrDisabled) {
return [];
}
const workspaces = window.gZenWorkspaces._workspaceCache?.workspaces;
if (!workspaces?.length) {
return [];
}
let actions = [];
const activeSpaceUUID = window.gZenWorkspaces.activeWorkspace;
for (const workspace of workspaces) {
if (workspace.uuid !== activeSpaceUUID) {
const accentColor = window.gZenWorkspaces
.workspaceElement(workspace.uuid)
?.style.getPropertyValue('--zen-primary-color');
actions.push({
label: 'Focus on',
extraPayload: {
workspaceId: workspace.uuid,
prettyName: workspace.name,
prettyIcon: workspace.icon,
accentColor,
},
icon: 'chrome://browser/skin/zen-icons/forward.svg',
});
}
}
return actions;
}
async #getExtensionActions(window) {
const addons = await lazy.AddonManager.getAddonsByTypes(['extension']);
return addons
.filter(
(addon) =>
addon.isActive &&
!addon.isSystem &&
window.document.getElementById(makeWidgetId(addon.id) + '-BAP')
)
.map((addon) => {
return {
icon: 'chrome://browser/skin/zen-icons/extension.svg',
label: 'Extension',
extraPayload: {
extensionId: addon.id,
prettyName: addon.name,
prettyIcon: addon.iconURL,
},
};
});
}
/**
* @param {Window} window The window to check available actions for.
* @returns All the available global actions.
*/
#getAvailableActions(window) {
return globalActions.filter((a) => a.isAvailable(window));
async #getAvailableActions(window) {
return globalActions
.filter((a) => a.isAvailable(window))
.concat(this.#getWorkspaceActions(window))
.concat(await this.#getExtensionActions(window));
}
/**
@@ -77,12 +136,12 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
* @param {string} query The user's search query.
*
*/
#findMatchingActions(query) {
async #findMatchingActions(query) {
const window = lazy.BrowserWindowTracker.getTopWindow();
const actions = this.#getAvailableActions(window);
const actions = await this.#getAvailableActions(window);
let results = [];
for (let action of actions) {
const label = action.label;
const label = action.extraPayload?.prettyName || action.label;
const score = this.#calculateFuzzyScore(label, query);
if (score > MINIMUM_QUERY_SCORE) {
results.push({
@@ -156,7 +215,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
return;
}
const actionsResults = this.#findMatchingActions(query);
const actionsResults = await this.#findMatchingActions(query);
if (!actionsResults.length) {
return;
}
@@ -174,6 +233,7 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
shortcutContent: ownerGlobal.gZenKeyboardShortcutsManager.getShortcutDisplayFromCommand(
action.command
),
...action.extraPayload,
});
let result = new lazy.UrlbarResult(
@@ -207,6 +267,9 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
* @returns {object} An object describing the view update.
*/
getViewUpdate(result) {
const prettyIconIsSvg =
result.payload.prettyIcon &&
(result.payload.prettyIcon.endsWith('.svg') || result.payload.prettyIcon.endsWith('.png'));
return {
icon: {
attributes: {
@@ -220,6 +283,27 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
shortcutContent: {
textContent: result.payload.shortcutContent || '',
},
prettyName: {
attributes: {
hidden: !result.payload.prettyName,
style: `--zen-primary-color: ${result.payload.accentColor || 'currentColor'}`,
},
},
prettyNameStrong: {
textContent: result.payload.prettyName
? prettyIconIsSvg || !result.payload.prettyIcon
? result.payload.prettyName
: `${result.payload.prettyIcon} ${result.payload.prettyName}`
: '',
attributes: { dir: 'ltr' },
},
prettyNameIcon: {
attributes: {
src: result.payload.prettyIcon || '',
hidden: !prettyIconIsSvg || !result.payload.prettyIcon,
workspaceIcon: !!result.payload.workspaceId,
},
},
};
}
@@ -245,6 +329,23 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
},
],
},
{
tag: 'span',
classList: ['urlbarView-prettyName'],
hidden: true,
name: 'prettyName',
children: [
{
tag: 'img',
name: 'prettyNameIcon',
attributes: { hidden: true },
},
{
name: 'prettyNameStrong',
tag: 'strong',
},
],
},
{
name: 'shortcutContent',
tag: 'span',
@@ -258,14 +359,27 @@ export class ZenUrlbarProviderGlobalActions extends UrlbarProvider {
const result = details.result;
const payload = result.payload;
const command = payload.zenCommand;
if (!command) {
return;
}
const ownerGlobal = details.element.ownerGlobal;
if (typeof command === 'function') {
command(ownerGlobal);
return;
}
// Switch workspace if theres a workspaceId in the payload.
if (payload.workspaceId) {
ownerGlobal.gZenWorkspaces.changeWorkspaceWithID(payload.workspaceId);
return;
}
if (payload.extensionId) {
const widgetId = makeWidgetId(payload.extensionId) + '-BAP';
const node = ownerGlobal.document.getElementById(widgetId);
if (node) {
node.doCommand();
}
return;
}
if (!command) {
return;
}
const commandToRun = ownerGlobal.document.getElementById(command);
if (commandToRun) {
ownerGlobal.gBrowser.selectedBrowser.focus();

View File

@@ -37,11 +37,22 @@ const globalActionsTemplate = [
command: (window) => window.openPreferences(),
icon: 'chrome://browser/skin/zen-icons/settings.svg',
},
{
label: 'Open New Window',
command: 'cmd_newNavigator',
icon: 'chrome://browser/skin/zen-icons/window.svg',
},
{
label: 'Open Private Window',
command: 'Tools:PrivateBrowsing',
icon: 'chrome://browser/skin/zen-icons/private-window.svg',
},
];
export const globalActions = globalActionsTemplate.map((action) => ({
...action,
isAvailable: (window) => {
return window.document.getElementById(action.command)?.getAttribute('disabled') !== 'true';
},
extraPayload: {},
...action,
}));