feat: Added copy url button and small tweaks, b=no-bug, c=common, compact-mode, folders, glance

This commit is contained in:
mr. m
2025-10-14 00:25:13 +02:00
parent c2d6ce83c0
commit 3c014999d8
17 changed files with 158 additions and 110 deletions

View File

@@ -90,6 +90,9 @@ zen-site-data-get-addons =
zen-site-data-site-settings =
.label = All Site Settings
zen-urlbar-copy-url-button =
.tooltiptext = Copy URL
zen-site-data-setting-site-protection = Site Protection
zen-site-data-panel-feature-callout-title = A new home for add-ons, permissions, and more

View File

@@ -1,5 +1,5 @@
diff --git a/browser/components/urlbar/UrlbarInput.sys.mjs b/browser/components/urlbar/UrlbarInput.sys.mjs
index afc7a6c6ddbf4cf5a5b27c0bd60577b833c63093..77955d888e70409c83b217e676e1575417018831 100644
index afc7a6c6ddbf4cf5a5b27c0bd60577b833c63093..a2494f2c0f3e4fc50cbe2fe3bf6e2bd2b69f0cf7 100644
--- a/browser/components/urlbar/UrlbarInput.sys.mjs
+++ b/browser/components/urlbar/UrlbarInput.sys.mjs
@@ -76,6 +76,13 @@ ChromeUtils.defineLazyGetter(lazy, "logger", () =>
@@ -157,14 +157,14 @@ index afc7a6c6ddbf4cf5a5b27c0bd60577b833c63093..77955d888e70409c83b217e676e15754
: val;
// Only trim value if the directionality doesn't change to RTL and we're not
// showing a strikeout https protocol.
@@ -3407,6 +3475,7 @@ export class UrlbarInput {
);
}
@@ -3501,6 +3569,7 @@ export class UrlbarInput {
resultDetails = null,
browser = this.window.gBrowser.selectedBrowser
) {
+ openUILinkWhere = this.window.gZenUIManager.getOpenUILinkWhere(url, browser, openUILinkWhere);
// No point in setting these because we'll handleRevert() a few rows below.
if (openUILinkWhere == "current") {
// Make sure URL is formatted properly (don't show punycode).
if (this.isAddressbar) {
this.#prepareAddressbarLoad(
url,
@@ -3608,6 +3677,10 @@ export class UrlbarInput {
}
reuseEmpty = true;

View File

@@ -944,3 +944,7 @@
display: none;
}
}
#zen-copy-url-button image {
list-style-image: url('link.svg');
}

View File

@@ -527,6 +527,9 @@ var gZenUIManager = {
this._toastContainer.removeAttribute('hidden');
this._toastContainer.appendChild(toast);
const timeoutFunction = () => {
if (Services.prefs.getBoolPref('ui.popup.disable_autohide')) {
return;
}
this.motion
.animate(toast, { opacity: [1, 0], scale: [1, 0.5] }, { duration: 0.2, bounce: 0 })
.then(() => {

View File

@@ -3,20 +3,6 @@
* 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/.
*/
@keyframes zen-jello-animation {
0% {
transform: scale3d(0.8, 0.8, 0.8);
}
60% {
transform: scale3d(1.02, 1.02, 1.02);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
@keyframes zen-jello-animation-macos {
0% {
@@ -30,21 +16,6 @@
}
}
@keyframes zen-jello-animation-large {
0% {
transform: scale3d(0.8, 0.8, 0.8);
}
60% {
transform: scale3d(1.02, 1.02, 1.02);
}
to {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
@keyframes zen-theme-picker-dot-animation {
from {
transform: scale(0.8) translate(-50%, -50%);

View File

@@ -130,7 +130,8 @@
}
}
.identity-box-button {
.identity-box-button,
#zen-copy-url-button {
opacity: 0;
transition:
opacity 0.2s,
@@ -246,7 +247,9 @@
:root[zen-single-toolbar='true'] {
--urlbar-icon-border-radius: 8px !important;
.urlbar-page-action:not([open]):not([showing]):not(#identity-permission-box),
.urlbar-page-action:not([open]):not([showing]):not(
:is(#zen-copy-url-button, #identity-permission-box)
),
#tracking-protection-icon-container {
display: none;
}

View File

@@ -23,12 +23,6 @@ panel[type='arrow'] {
animation: zen-jello-animation-macos 0.2s ease-in-out forwards !important;
}
}
@media (-moz-platform: linux) or ((-moz-platform: windows) and (not (-moz-windows-mica-popups))) and (-moz-panel-animations) {
/* Mica popups have a weird background while the animation is running */
&::part(content) {
animation: zen-jello-animation 0.35s ease;
}
}
}
menupopup,

View File

@@ -386,13 +386,17 @@ menuseparator {
min-width: unset !important;
margin: 0px !important;
border-radius: calc(var(--zen-native-inner-radius) + 2px) !important;
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)) !important;
border: 1px solid light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15)) !important;
background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)) !important;
border: 1px solid light-dark(rgba(0, 0, 0, 0.01), rgba(255, 255, 255, 0.01)) !important;
color: light-dark(rgba(0, 0, 0, 0.8), rgba(255, 255, 255, 0.8)) !important;
:root[zen-right-side='true'] & {
order: -1;
}
& .button-text {
display: none;
}
}
}
}

View File

@@ -180,7 +180,11 @@ body > #confetti {
background-color 0.1s,
transform 0.2s;
&:active:hover {
&[open='true'] {
transition: background-color 0.1s;
}
&:not([open='true']):active:hover {
transform: scale(0.95);
}
}

View File

@@ -238,7 +238,7 @@
--zen-native-content-radius: env(-moz-gtk-csd-titlebar-radius);
}
@media (-moz-mac-tahoe-theme) {
--zen-native-content-radius: 14px;
--zen-native-content-radius: 15px;
}
--zen-native-inner-radius: var(
--zen-webview-border-radius,

View File

@@ -39,6 +39,7 @@
#navigator-toolbox {
--zen-toolbox-max-width: 74px !important;
--zen-compact-float: var(--zen-element-separation);
:root[zen-no-padding='true'] & {
--zen-compact-float: 10px;
--zen-compact-mode-no-padding-radius-fix: 2px;

View File

@@ -31,7 +31,7 @@
width: 100%;
height: 100%;
border-radius: 50%;
background-color: var(--zen-colors-secondary);
background-color: var(--toolbar-color);
}
.zen-download-arc-animation-icon {

View File

@@ -10,7 +10,7 @@
display: flex;
align-items: center;
justify-content: center;
background-color: var(--zen-colors-primary);
background-color: var(--toolbar-color);
border-radius: var(--zen-native-content-radius);
box-shadow: var(--zen-big-shadow);
pointer-events: none;

View File

@@ -335,13 +335,13 @@ zen-folder {
}
&:last-child {
border-bottom-left-radius: max(calc(var(--panel-border-radius) - 6px), 4px);
border-bottom-right-radius: max(calc(var(--panel-border-radius) - 6px), 4px);
border-bottom-left-radius: max(calc(var(--panel-border-radius) - 2px), 4px);
border-bottom-right-radius: max(calc(var(--panel-border-radius) - 2px), 4px);
}
&:first-child {
border-top-left-radius: max(calc(var(--panel-border-radius) - 6px), 4px);
border-top-right-radius: max(calc(var(--panel-border-radius) - 6px), 4px);
border-top-left-radius: max(calc(var(--panel-border-radius) - 2px), 4px);
border-top-right-radius: max(calc(var(--panel-border-radius) - 2px), 4px);
}
}

View File

@@ -142,7 +142,7 @@
* @param {Tab} existingTab - Optional existing tab to reuse
* @returns {Browser} The created browser element
*/
createBrowserElement(url, currentTab, existingTab = null) {
#createBrowserElement(url, currentTab, existingTab = null) {
const newTabOptions = this.#createTabOptions(currentTab);
const newUUID = gZenUIManager.generateUuidv4();
@@ -250,6 +250,28 @@
);
}
/**
* Get element preview data as a data URL
* @param {Object} data - Glance data
* @returns {Promise<string|null>} Promise resolving to data URL or null
* if not available
*/
async #getElementPreviewData(data) {
// Make the rect relative to the tabpanels. We dont do it directly on the
// content process since it does not take into account scroll. This way, we can
// be sure that the coordinates are correct.
const tabPanelsRect = gBrowser.tabpanels.getBoundingClientRect();
const rect = new DOMRect(
data.clientX + tabPanelsRect.left,
data.clientY + tabPanelsRect.top,
data.width,
data.height
);
return await this.#imageBitmapToBase64(
await window.browsingContext.currentWindowGlobal.drawSnapshot(rect, 1, 'transparent', true)
);
}
/**
* Open a glance overlay with the specified data
* @param {Object} data - Glance data including URL, position, and dimensions
@@ -269,7 +291,7 @@
this.#setAnimationState(true);
const currentTab = ownerTab ?? gBrowser.selectedTab;
const browserElement = this.createBrowserElement(data.url, currentTab, existingTab);
const browserElement = this.#createBrowserElement(data.url, currentTab, existingTab);
this.fillOverlay(browserElement);
this.overlay.classList.add('zen-glance-overlay');
@@ -293,9 +315,13 @@
* @returns {Promise<Tab>} Promise that resolves to the glance tab
*/
#animateGlanceOpening(data, browserElement) {
return new Promise((resolve) => {
return new Promise(async (resolve) => {
this.#prepareGlanceAnimation(data, browserElement);
if (data.width && data.height) {
data.elementData = await this.#getElementPreviewData(data);
}
this.#glances.get(this.#currentGlanceID).elementData = data.elementData;
window.requestAnimationFrame(() => {
this.#prepareGlanceAnimation(data, browserElement);
this.#executeGlanceAnimation(data, browserElement, resolve);
});
});
@@ -450,18 +476,24 @@
const transformOrigin = this.#getTransformOrigin(data);
this.browserWrapper.style.transformOrigin = transformOrigin;
gZenUIManager.motion
.animate(
this.contentWrapper,
{ opacity: [0, 1] },
{
duration: 0.1,
easing: 'easeInOut',
}
)
.then(() => {
this.contentWrapper.style.opacity = '';
});
// Only animate if there is element data, so we can apply a
// nice fade-in effect to the content. But if it doesn't exist,
// we just fall back to always showing the browser directly.
if (data.elementData) {
gZenUIManager.motion
.animate(
this.contentWrapper,
{ opacity: [0, 1] },
{
duration: 0.1,
easing: 'easeInOut',
}
)
.then(() => {
this.contentWrapper.style.opacity = '';
});
}
gZenUIManager.motion
.animate(this.browserWrapper, arcSequence, {
@@ -826,6 +858,21 @@
}
}
#imageBitmapToBase64(imageBitmap) {
// 1. Create a canvas with the same size as the ImageBitmap
const canvas = document.createElement('canvas');
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;
// 2. Draw the ImageBitmap onto the canvas
const ctx = canvas.getContext('2d');
ctx.drawImage(imageBitmap, 0, 0);
// 3. Convert the canvas content to a Base64 string (PNG by default)
const base64String = canvas.toDataURL('image/png');
return base64String;
}
/**
* Animate parent background restoration
* @param {Element} browserSidebarContainer - The sidebar container
@@ -1481,10 +1528,15 @@
*/
#createGlanceDataFromBookmark(event) {
const rect = window.windowUtils.getBoundsWithoutFlushing(event.target);
const tabPanelRect = window.windowUtils.getBoundsWithoutFlushing(gBrowser.tabpanels);
// the bookmark is most likely outisde the tabpanel, so we need to give a negative number
// so it can be corrected later
let top = rect.top - tabPanelRect.top;
let left = rect.left - tabPanelRect.left;
return {
url: event.target._placesNode.uri,
clientX: rect.left,
clientY: rect.top,
clientX: left,
clientY: top,
width: rect.width,
height: rect.height,
};

View File

@@ -28,38 +28,7 @@ export class ZenGlanceParent extends JSWindowActorParent {
}
}
#imageBitmapToBase64(imageBitmap) {
// 1. Create a canvas with the same size as the ImageBitmap
const canvas = this.browsingContext.topChromeWindow.document.createElement('canvas');
canvas.width = imageBitmap.width;
canvas.height = imageBitmap.height;
// 2. Draw the ImageBitmap onto the canvas
const ctx = canvas.getContext('2d');
ctx.drawImage(imageBitmap, 0, 0);
// 3. Convert the canvas content to a Base64 string (PNG by default)
const base64String = canvas.toDataURL('image/png');
return base64String;
}
async openGlance(window, data) {
const win = this.browsingContext.topChromeWindow;
const tabPanels = win.gBrowser.tabpanels;
// Make the rect relative to the tabpanels. We dont do it directly on the
// content process since it does not take into account scroll. This way, we can
// be sure that the coordinates are correct.
const tabPanelsRect = tabPanels.getBoundingClientRect();
const rect = new DOMRect(
data.clientX + tabPanelsRect.left,
data.clientY + tabPanelsRect.top,
data.width,
data.height
);
const elementData = await this.#imageBitmapToBase64(
await win.browsingContext.currentWindowGlobal.drawSnapshot(rect, 1, 'transparent', true)
);
data.elementData = elementData;
window.gZenGlanceManager.openGlance(data);
openGlance(window, data) {
return window.gZenGlanceManager.openGlance(data);
}
}

View File

@@ -42,6 +42,7 @@ export class nsZenSiteDataPanel {
// Remove the old permissions dialog
this.document.getElementById('unified-extensions-panel-template').remove();
this.#initCopyUrlButton();
this.#initEventListeners();
this.#maybeShowFeatureCallout();
}
@@ -65,6 +66,35 @@ export class nsZenSiteDataPanel {
this.#initContextMenuEventListener();
}
#initCopyUrlButton() {
// This function is a bit out of place, but it's related enough to the panel
// that it's easier to do it here than in a separate module.
const container = this.document.getElementById('page-action-buttons');
const fragment = this.window.MozXULElement.parseXULToFragment(`
<hbox id="zen-copy-url-button"
class="urlbar-page-action"
role="button"
data-l10n-id="zen-urlbar-copy-url-button"
hidden="true">
<image class="urlbar-icon"/>
</hbox>
`);
container.appendChild(fragment);
const aElement = this.document.getElementById('zen-copy-url-button');
aElement.addEventListener('click', (event) => {
this.document.getElementById('cmd_zenCopyCurrentURL').doCommand();
});
this.window.gBrowser.addProgressListener({
onLocationChange: (aWebProgress, aRequest, aLocation) => {
if (aWebProgress.isTopLevel) {
aElement.hidden = !this.#canCopyUrl(aLocation);
}
},
});
}
#initContextMenuEventListener() {
const kCommands = {
context_zenClearSiteData: (event) => {
@@ -117,10 +147,7 @@ export class nsZenSiteDataPanel {
}
{
const button = this.document.getElementById('zen-site-data-header-share');
if (
this.window.gBrowser.currentURI.schemeIs('http') ||
this.window.gBrowser.currentURI.schemeIs('https')
) {
if (this.#canCopyUrl(this.window.gBrowser.currentURI)) {
button.removeAttribute('disabled');
} else {
button.setAttribute('disabled', 'true');
@@ -128,6 +155,19 @@ export class nsZenSiteDataPanel {
}
}
/*
* Determines whether the copy URL button should be hidden for the given URI.
* @param {nsIURI} uri - The URI to check.
* @returns {boolean} True if the button should be hidden, false otherwise.
*/
#canCopyUrl(uri) {
if (!uri) {
return false;
}
return uri.scheme.startsWith('http');
}
#setSiteSecurityInfo() {
const { gIdentityHandler } = this.window;
const button = this.document.getElementById('zen-site-data-security-info');