From e118cc1a206497a34f2dc766f4ca8c040e6a292c Mon Sep 17 00:00:00 2001 From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com> Date: Sat, 3 Jan 2026 02:42:17 +0100 Subject: [PATCH] chore: Motion to native animatiosn migration, p=#11796 * chore: Motion to native animatiosn migration, b=no-bug, c=common, glance, split-view * Discard changes to src/zen/glance/zen-glance.css * Discard changes to src/zen/glance/ZenGlanceManager.mjs --- src/zen/common/modules/ZenUIManager.mjs | 43 +++++++++++++++++++ src/zen/drag-and-drop/ZenDragAndDrop.js | 57 +++++++++---------------- src/zen/split-view/ZenViewSplitter.mjs | 1 + 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index 93b9649fb..5976e8c2e 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -65,6 +65,49 @@ window.gZenUIManager = { ZenMenubar.init(); }, + /** + * Animate an element using Element.animate API. + * This is not using gZenUIManager.motion, because motion library has some issues + * with certain properties and we want to have a simple wrapper for that. + */ + async elementAnimate(element, rawKeyframes, ...args) { + rawKeyframes = { ...rawKeyframes }; + // Convert 'y' property to 'transform' with translateY and 'x' to translateX, + // and 'scale' to 'transform' with scale. + if ((rawKeyframes.y || rawKeyframes.x || rawKeyframes.scale) && !rawKeyframes.transform) { + const yValues = rawKeyframes.y || []; + const xValues = rawKeyframes.x || []; + const scaleValues = rawKeyframes.scale || []; + delete rawKeyframes.y; + delete rawKeyframes.x; + delete rawKeyframes.scale; + rawKeyframes.transform = []; + console.assert( + yValues.length === 0 || xValues.length === 0 || yValues.length === xValues.length, + 'y and x keyframes must have the same length' + ); + const length = Math.max(yValues.length, xValues.length, scaleValues.length); + for (let i = 0; i < length; i++) { + const y = yValues[i] !== undefined ? `translateY(${yValues[i]}px)` : ''; + const x = xValues[i] !== undefined ? `translateX(${xValues[i]}px)` : ''; + const scale = scaleValues[i] !== undefined ? `scale(${scaleValues[i]})` : ''; + rawKeyframes.transform.push(`${x} ${y} ${scale}`.trim()); + } + } + let keyframes = []; + for (let i = 0; i < Object.values(rawKeyframes)[0].length; i++) { + let frame = {}; + for (const [property, values] of Object.entries(rawKeyframes)) { + frame[property] = values[i]; + } + keyframes.push(frame); + } + return await new Promise((resolve) => { + const animation = element.animate(keyframes, ...args); + animation.onfinish = () => resolve(); + }); + }, + _addNewCustomizableButtonsIfNeeded() { const kPref = 'zen.ui.migration.compact-mode-button-added'; let navbarPlacements = CustomizableUI.getWidgetIdsInArea('zen-sidebar-top-buttons'); diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js index 4fb343638..17ee6f82f 100644 --- a/src/zen/drag-and-drop/ZenDragAndDrop.js +++ b/src/zen/drag-and-drop/ZenDragAndDrop.js @@ -97,9 +97,7 @@ const { offsetX, offsetY } = this.#getDragImageOffset(event, tab, draggingTabs); const dragImage = this.#createDragImageForTabs(draggingTabs); this.originalDragImageArgs = [dragImage, offsetX, offsetY]; - setTimeout(() => { - dt.setDragImage(...this.originalDragImageArgs); - }, 0); + dt.setDragImage(...this.originalDragImageArgs); } #createDragImageForTabs(movingTabs) { @@ -520,18 +518,7 @@ let dropElementGroup = dropElement?.group; let colorCode = dropElementGroup?.color; - let lastUnmovingTabInGroup = dropElementGroup?.tabs.findLast((t) => !movingTabsSet.has(t)); - if ( - isTab(dropElement) && - dropElementGroup && - dropElement == lastUnmovingTabInGroup && - !dropBefore - ) { - // Dragging tab over the last tab of a tab group, but not enough - // for it to drop into the tab group. Drop it after the tab group instead. - dropElement = dropElementGroup; - colorCode = undefined; - } else if (isTabGroupLabel(dropElement)) { + if (isTabGroupLabel(dropElement)) { // Dropping right before the first tab in the tab group. dropElement = dropElementGroup.tabs[0]; dropBefore = true; @@ -711,7 +698,6 @@ !gZenStartup.isReady || gReduceMotion || !dropElement || - dropElement.group !== draggedTab.group || dropElement.hasAttribute('zen-essential') || draggedTab.hasAttribute('zen-essential') || draggedTab.getAttribute('zen-workspace-id') != gZenWorkspaces.activeWorkspace @@ -725,26 +711,24 @@ } const animateElement = (ele, translateY) => { ele.style.transform = `translateY(${translateY}px)`; - setTimeout(() => { - setTimeout(() => { - animations.push( - gZenUIManager.motion - .animate( - ele, - { - y: [translateY, 0], - }, - { - duration: 0.1, - bounce: 0, - } - ) - .then(() => { - ele.style.transform = ''; - }) - ); - }); - }); + let animateInternal = (resolve) => { + gZenUIManager + .elementAnimate(ele, { y: [translateY, 0] }, { duration: 100, easing: 'ease-out' }) + .then(() => { + ele.style.transform = ''; + ele.style.zIndex = ''; + }) + .finally(resolve); + }; + // Wait for the next event loop tick to ensure the initial transform style is applied. + // We need to ensure the element has already been moved in the DOM before starting the animation. + animations.push( + new Promise((resolve) => + setTimeout(() => { + setTimeout(() => animateInternal(resolve), 0); + }) + ) + ); }; const items = this._tabbrowserTabs.ariaFocusableItems; let rect = window.windowUtils.getBoundsWithoutFlushing(draggedTab); @@ -781,6 +765,7 @@ : -rect.height * tabsInBetween.length; draggedTabTranslateY += extraTranslate * (draggedTab.elementIndex > dropElement.elementIndex ? 1 : -1); + draggedTab.style.zIndex = '9'; animateElement(draggedTab, draggedTabTranslateY); } catch (e) { console.error(e); diff --git a/src/zen/split-view/ZenViewSplitter.mjs b/src/zen/split-view/ZenViewSplitter.mjs index 28e3c9cf2..c22b4bd65 100644 --- a/src/zen/split-view/ZenViewSplitter.mjs +++ b/src/zen/split-view/ZenViewSplitter.mjs @@ -335,6 +335,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature { } const side = event.clientX - threshold > halfWidth ? 'right' : 'left'; for (const browser of gBrowser.browsers) { + if (!browser) continue; const width = browser.getBoundingClientRect().width; // Only apply it to the left side because if we add it to the right side, // we wont be able to move the element to the left.