gh-15460: Fixed folder expansion expanding real folders on library (gh-15498)

This commit is contained in:
mr. m
2026-09-21 16:51:05 +02:00
committed by GitHub
parent b3467c0ff2
commit 7545e5a870
9 changed files with 158 additions and 87 deletions

View File

@@ -14,13 +14,11 @@
.zen-library-media-preview-backdrop {
position: absolute;
inset: 0;
-moz-window-dragging: drag;
background: light-dark(rgba(255, 255, 255, 0.55), rgba(0, 0, 0, 0.45));
}
.zen-library-media-preview-panel {
position: relative;
-moz-window-dragging: no-drag;
display: flex;
flex-direction: column;
width: min(78vw, 1100px);

View File

@@ -124,7 +124,7 @@
background-image: var(--download-image);
width: 20px;
height: 20px;
border-radius: var(--border-radius-small);
border-radius: var(--border-radius-xsmall);
background-size: cover;
scale: 2;

View File

@@ -100,7 +100,10 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
const folderActionsMenu = document.getElementById("zenFolderActions");
folderActionsMenu.addEventListener("popupshowing", event => {
const target = event.explicitOriginalTarget;
const target =
event.target === folderActionsMenu
? (folderActionsMenu.triggerNode ?? event.explicitOriginalTarget)
: event.explicitOriginalTarget;
let folder;
if (gBrowser.isTabGroupLabel(target)) {
folder = target.group;
@@ -498,16 +501,6 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
await this.animateCollapse(group);
}
/**
* Settles a collapsed folder's own height. The library toggles folders
* through copies, so the real one needs telling once the dust settles.
*
* @param {Element} group - A folder
*/
relayoutCollapsedFolder(group) {
this.#queueCollapsedRelayout(group);
}
#queueCollapsedRelayout(group) {
if (this.#collapsedRelayoutQueue.size === 0) {
requestAnimationFrame(() => this.#flushCollapsedRelayout());

View File

@@ -33,6 +33,7 @@ ChromeUtils.defineESModuleGetters(
);
const LAST_TAB_PREF = "zen.library.last-tab";
const CLEANUP_DELAY_MS = 30000;
ChromeUtils.defineLazyGetter(lazy, "appContentWrapper", function () {
return document.getElementById("zen-appcontent-wrapper");
@@ -183,18 +184,8 @@ export class ZenLibrary extends MozLitElement {
}
if (isOpen && !wasOpen) {
this.setAttribute("open", "true");
document
.getElementById("zen-sidebar-splitter")
.setAttribute("zen-library-open", "true");
this.#init();
} else if (!isOpen && wasOpen) {
this.removeAttribute("open");
this.#mounted = new Set([this.activeTab]);
this.requestUpdate();
document
.getElementById("zen-sidebar-splitter")
.removeAttribute("zen-library-open");
this.#cleanup();
}
@@ -293,20 +284,29 @@ export class ZenLibrary extends MozLitElement {
return this.#stylesLoaded;
}
#cleanupTimer = null;
#idleCleanup = null;
#scheduleIdleCleanup() {
this.#idleCleanup = window.requestIdleCallback(() => {
this.#idleCleanup = null;
this.#stylesLoaded = null;
this.#cancelIdleCleanup();
this.#cleanupTimer = window.setTimeout(() => {
this.#cleanupTimer = null;
this.#idleCleanup = window.requestIdleCallback(() => {
this.#idleCleanup = null;
this.#stylesLoaded = null;
this.#contentMounted = false;
this.#mounted = new Set([this.activeTab]);
this.requestUpdate();
});
this.#contentMounted = false;
this.#mounted = new Set([this.activeTab]);
this.requestUpdate();
});
}, CLEANUP_DELAY_MS);
}
#cancelIdleCleanup() {
if (this.#cleanupTimer) {
window.clearTimeout(this.#cleanupTimer);
this.#cleanupTimer = null;
}
if (this.#idleCleanup) {
window.cancelIdleCallback(this.#idleCleanup);
this.#idleCleanup = null;
@@ -376,11 +376,6 @@ export class ZenLibrary extends MozLitElement {
lib.openProgress = target;
lib.#springControls = null;
lib.removeAttribute("transitioning");
if (target === 0) {
lib.#mounted = new Set([lib.activeTab]);
lib.requestUpdate();
lib.#scheduleIdleCleanup();
}
},
}
);
@@ -401,12 +396,12 @@ export class ZenLibrary extends MozLitElement {
lib.#springControls = null;
}
lib.style.setProperty("pointer-events", "none");
lib.style.pointerEvents = "none";
}
static stopSwipe(direction) {
const lib = this.getInstance();
lib.style.setProperty("pointer-events", "unset");
lib.style.pointerEvents = "";
lib.#canSwipe = false;
if (lib.#libraryOnRight) {
@@ -418,7 +413,6 @@ export class ZenLibrary extends MozLitElement {
this.animateProgress(target);
}
// Return library open state
return lib.#isOpen;
}
@@ -499,6 +493,15 @@ export class ZenLibrary extends MozLitElement {
}
#init() {
this.#cancelIdleCleanup();
if (!this.#contentMounted) {
this.#contentMounted = true;
this.requestUpdate();
}
this.setAttribute("open", "true");
document
.getElementById("zen-sidebar-splitter")
.setAttribute("zen-library-open", "true");
document.addEventListener("keydown", this, true);
window.addEventListener("TabOpen", this);
@@ -511,11 +514,18 @@ export class ZenLibrary extends MozLitElement {
}
#cleanup() {
this.#cancelIdleCleanup();
this.removeAttribute("open");
this.#mounted = new Set([this.activeTab]);
this.requestUpdate();
document
.getElementById("zen-sidebar-splitter")
.removeAttribute("zen-library-open");
if (this.#springControls) {
this.#springControls.stop();
this.#springControls = null;
}
this.removeAttribute("transitioning");
this.#detachWrapperOfSwipe();
if (this.#gestureControl) {
@@ -531,6 +541,7 @@ export class ZenLibrary extends MozLitElement {
document.removeEventListener("keydown", this, true);
window.removeEventListener("TabOpen", this);
this.isHidden = true;
this.#scheduleIdleCleanup();
}
get #isCompactMode() {

View File

@@ -43,6 +43,8 @@ export class ZenLibraryMediaPreview {
#root = null;
#parts = {};
#menu = null;
/** @type {object|null} The media the open menu acts on */
#menuItem = null;
#items = [];
#index = -1;
#origin = null;
@@ -135,6 +137,9 @@ export class ZenLibraryMediaPreview {
openButton.addEventListener("click", () => this.#launch());
const stage = make("div", "zen-library-media-preview-stage", panel);
stage.addEventListener("contextmenu", event =>
this.openContextMenu(event, this.#item)
);
this.#parts = {
panel,
@@ -472,8 +477,7 @@ export class ZenLibraryMediaPreview {
event.dataTransfer.setData("text/uri-list", item.url);
}
#launch() {
const item = this.#item;
#launch(item = this.#item) {
if (!item) {
return;
}
@@ -484,15 +488,13 @@ export class ZenLibraryMediaPreview {
}
}
#reveal() {
const item = this.#item;
#reveal(item = this.#item) {
if (item) {
new lazy.FileUtils.File(item.path).reveal();
}
}
#copy() {
const item = this.#item;
#copy(item = this.#item) {
if (!item) {
return;
}
@@ -513,6 +515,40 @@ export class ZenLibraryMediaPreview {
}
#openMenu(event) {
this.#menuItem = this.#item;
this.#ensureMenu().openPopup(
event.currentTarget,
"after_end",
0,
0,
false,
false
);
}
/**
* Opens the same menu as the more button, at the pointer, for any media
* in the grid or the one on show.
*
* @param {MouseEvent} event - The right click
* @param {object} item - The media it was on
*/
openContextMenu(event, item) {
event.preventDefault();
event.stopPropagation();
if (!item) {
return;
}
this.#menuItem = item;
this.#ensureMenu().openPopupAtScreen(
event.screenX,
event.screenY,
true,
event
);
}
#ensureMenu() {
if (!this.#menu) {
this.#menu = this.#window.MozXULElement.parseXULToFragment(`
<menupopup class="zen-library-media-menu">
@@ -523,20 +559,21 @@ export class ZenLibraryMediaPreview {
</menupopup>
`).firstElementChild;
this.#menu.addEventListener("command", menuEvent => {
const item = this.#menuItem;
switch (menuEvent.target.dataset.action) {
case "open":
this.#launch();
this.#launch(item);
break;
case "show":
this.#reveal();
this.#reveal(item);
break;
case "copy":
this.#copy();
this.#copy(item);
break;
}
});
this.#document.getElementById("mainPopupSet").appendChild(this.#menu);
}
this.#menu.openPopup(event.currentTarget, "after_end", 0, 0, false, false);
return this.#menu;
}
}

View File

@@ -18,7 +18,6 @@ const FILE_MIME = "application/x-moz-file";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const FOLDERS_PREF = "zen.library.media.folders";
const ENABLED_PREF = "zen.library.media.enabled";
const SKELETON_CARDS = 10;
const FOLDERS = {
downloads: { key: "DfltDwnld", home: "Downloads" },
@@ -287,6 +286,11 @@ export class ZenLibraryMediaSection extends ZenLibrarySearchSection {
dataTransfer.addElement(event.currentTarget);
}
#onItemContextMenu(event, item) {
this.#preview ??= new ZenLibraryMediaPreview(window);
this.#preview.openContextMenu(event, item);
}
#onItemClick(event, item) {
this.#preview ??= new ZenLibraryMediaPreview(window);
const shown = this.#shown;
@@ -313,6 +317,9 @@ export class ZenLibraryMediaSection extends ZenLibrarySearchSection {
card.title = item.name;
card.addEventListener("dragstart", event => this.#onDragStart(event, item));
card.addEventListener("click", event => this.#onItemClick(event, item));
card.addEventListener("contextmenu", event =>
this.#onItemContextMenu(event, item)
);
const frame = document.createElementNS(HTML_NS, "div");
frame.className = "zen-library-media-frame";
@@ -443,12 +450,7 @@ export class ZenLibraryMediaSection extends ZenLibrarySearchSection {
*/
#renderOptIn() {
return html`
<div class="zen-library-media-grid zen-library-media-skeleton" behind>
${Array.from(
{ length: SKELETON_CARDS },
() => html`<div class="zen-library-media-item"></div>`
)}
</div>
<div class="zen-library-media-skeleton" behind></div>
<div class="zen-library-media-opt-in">
<div class="zen-library-media-opt-in-icon">
<div class="empty-state-icon-image"></div>

View File

@@ -108,6 +108,13 @@ export class ZenLibrarySpacesSection extends MozLitElement {
#pendingGroups = [];
/** @type {WeakMap<Element, Element>} copied tab or group to the real one */
#realElements = new WeakMap();
/**
* Folders are opened and closed here without touching the real ones, so
* rebuilt copies keep what was chosen here.
*
* @type {WeakMap<Element, boolean>} real group to its copy's collapsed state
*/
#collapsedCopies = new WeakMap();
#dnd = new ZenLibraryDragAndDrop(this);
#dragIndex = -1;
#dropIndex = -1;
@@ -455,6 +462,9 @@ export class ZenLibrarySpacesSection extends MozLitElement {
if (GROUP_TAGS.includes(node.localName)) {
const copy = node.cloneNode(false);
this.#renameIds(copy);
if (this.#collapsedCopies.has(node)) {
copy.toggleAttribute("collapsed", this.#collapsedCopies.get(node));
}
container.appendChild(copy);
this.#realElements.set(copy, node);
const icon = copy.querySelector(".tab-group-folder-icon");
@@ -502,15 +512,12 @@ export class ZenLibrarySpacesSection extends MozLitElement {
const group = this.#realElements.get(copy);
if (group) {
event.stopPropagation();
const collapsed = !group.collapsed;
group.collapsed = collapsed;
copy.collapsed = collapsed;
if (collapsed) {
this.#collapsedCopies.set(group, copy.collapsed);
if (copy.collapsed) {
gZenFolders.animateCollapse(copy);
} else {
gZenFolders.animateExpand(copy);
}
gZenFolders.relayoutCollapsedFolder(group);
}
return;
}
@@ -527,6 +534,35 @@ export class ZenLibrarySpacesSection extends MozLitElement {
this.library?.constructor.toggle();
};
/**
* A copy carries the real element's context menu, which would then act on
* the copy. The menu opens for the real tab or folder instead.
*
* @param {MouseEvent} event
*/
#onStripContextMenu = event => {
let real = null;
const label = event.target.closest(".tab-group-label-container");
if (label) {
const group = this.#realElements.get(label.closest(GROUP_TAGS.join()));
real = group?.labelElement?.parentElement;
} else {
real = this.#realElements.get(event.target.closest("tab"));
}
const menu = real && document.getElementById(real.getAttribute("context"));
event.preventDefault();
event.stopPropagation();
if (!menu) {
return;
}
const trigger = new MouseEvent("ZenLibraryContextMenu", {
screenX: event.screenX,
screenY: event.screenY,
});
real.dispatchEvent(trigger);
menu.openPopupAtScreen(event.screenX, event.screenY, true, trigger);
};
/**
* @param {Element} copy - A copied tab, group or folder
* @returns {Element|undefined} The real element it stands for
@@ -591,6 +627,7 @@ export class ZenLibrarySpacesSection extends MozLitElement {
orient="vertical"
expanded="true"
@click=${this.#onStripClick}
@contextmenu=${this.#onStripContextMenu}
@mouseover=${this.#containHover}
@mouseout=${this.#containHover}
@dragstart=${this.#onStripDragStart}

View File

@@ -338,33 +338,22 @@ zen-library[zen-library-downloading] .zen-library-tab[data-section="downloads"]:
box-shadow: none;
}
#zen-library-panel
:is(.zen-library-section, .zen-library-search-results)
> * {
animation: zen-library-section-fade 0.22s ease;
}
@keyframes zen-library-section-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.zen-library-media-skeleton {
position: absolute;
inset: 0;
pointer-events: none;
& .zen-library-media-item {
&::before {
content: "";
position: absolute;
inset: 0;
background: var(--zen-library-pill-bg);
mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 122 122'><rect x='6' y='6' width='110' height='110' rx='16'/></svg>");
mask-size: 150px auto;
mask-repeat: round repeat;
animation: zen-library-media-wait 1.8s ease-in-out infinite;
}
& .zen-library-media-item:nth-child(even) {
animation-delay: 0.3s;
}
&[behind] {
opacity: 0.7;
}
@@ -821,6 +810,7 @@ zen-library:not([open]) :is(.zen-library-search-header, .zen-library-filter-head
width: 46px;
height: 46px;
margin-inline: 2px 1px;
color-scheme: light;
&::before {
content: "";

View File

@@ -474,14 +474,17 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
}
// Remove everything except the entry we want to keep
let url;
try {
url = Services.io.newURI(initialState.entry.url);
} catch {}
state.entries = [
{
...initialState.entry,
triggeringPrincipal_base64: E10SUtils.serializePrincipal(
Services.scriptSecurityManager.createContentPrincipal(
Services.io.newURI(initialState.entry.url),
{}
)
url
? Services.scriptSecurityManager.createContentPrincipal(url, {})
: Services.scriptSecurityManager.createNullPrincipal()
),
},
];