--- a/browser/components/customizableui/PanelMultiView.jsm
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -418,30 +418,31 @@ this.PanelMultiView = class {
this.showSubView(this._mainViewId);
} else {
this._transitionHeight(() => {
viewNode.removeAttribute("current");
this._currentSubView = null;
this.node.setAttribute("viewtype", "main");
});
}
+ } else if (this.panelViews) {
+ this._mainView.setAttribute("current", "true");
}
if (!this.panelViews) {
this._shiftMainView();
}
}
showSubView(aViewId, aAnchor, aPreviousView) {
- const {document, window} = this;
return (async () => {
// Support passing in the node directly.
let viewNode = typeof aViewId == "string" ? this.node.querySelector("#" + aViewId) : aViewId;
if (!viewNode) {
- viewNode = document.getElementById(aViewId);
+ viewNode = this.document.getElementById(aViewId);
if (viewNode) {
this._placeSubView(viewNode);
} else {
throw new Error(`Subview ${aViewId} doesn't exist!`);
}
} else if (viewNode.parentNode == this._panelViewCache) {
this._placeSubView(viewNode);
}
@@ -480,17 +481,18 @@ this.PanelMultiView = class {
if (!viewNode.hasAttribute("title"))
viewNode.setAttribute("title", aAnchor.getAttribute("label"));
viewNode.classList.add("PanelUI-subView");
}
if (this.panelViews && this._mainViewWidth)
viewNode.style.maxWidth = viewNode.style.minWidth = this._mainViewWidth + "px";
// Emit the ViewShowing event so that the widget definition has a chance
- // to lazily populate the subview with things.
+ // to lazily populate the subview with things or perhaps even cancel this
+ // whole operation.
let detail = {
blockers: new Set(),
addBlocker(promise) {
this.blockers.add(promise);
}
};
let cancel = this._dispatchViewEvent(viewNode, "ViewShowing", aAnchor, detail);
if (detail.blockers.size) {
@@ -504,17 +506,16 @@ this.PanelMultiView = class {
}
this._viewShowing = null;
if (cancel) {
return;
}
this._currentSubView = viewNode;
- viewNode.setAttribute("current", true);
if (this.panelViews) {
if (viewNode.id == this._mainViewId) {
this.node.setAttribute("viewtype", "main");
} else {
this.node.setAttribute("viewtype", "subview");
}
if (!playTransition)
this.descriptionHeightWorkaround(viewNode);
@@ -527,140 +528,26 @@ this.PanelMultiView = class {
// 2) The subview deck slides in so that it takes up almost all of the
// panel.
// 3) If the subview is taller then the main panel contents, then the panel
// must grow to meet that new height. Otherwise, it must shrink.
//
// All three of these actions make use of CSS transformations, so they
// should all occur simultaneously.
if (this.panelViews && playTransition) {
- // Sliding the next subview in means that the previous panelview stays
- // where it is and the active panelview slides in from the left in LTR
- // mode, right in RTL mode.
- let onTransitionEnd = () => {
- this._dispatchViewEvent(previousViewNode, "ViewHiding");
- previousViewNode.removeAttribute("current");
- this.descriptionHeightWorkaround(viewNode);
- };
-
- // There's absolutely no need to show off our epic animation skillz when
- // the panel's not even open.
- if (this._panel.state != "open") {
- onTransitionEnd();
- return;
- }
-
if (aAnchor)
aAnchor.setAttribute("open", true);
- // Set the viewContainer dimensions to make sure only the current view
- // is visible.
- this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
- this._viewContainer.style.width = previousRect.width + "px";
- // Lock the dimensions of the window that hosts the popup panel.
- let rect = this._panel.popupBoxObject.getOuterScreenRect();
- this._panel.setAttribute("width", rect.width);
- this._panel.setAttribute("height", rect.height);
-
- this._viewBoundsOffscreen(viewNode, previousRect, viewRect => {
- this._transitioning = true;
- if (this._autoResizeWorkaroundTimer)
- window.clearTimeout(this._autoResizeWorkaroundTimer);
- this._viewContainer.setAttribute("transition-reverse", reverse);
- let nodeToAnimate = reverse ? previousViewNode : viewNode;
-
- if (!reverse) {
- // We set the margin here to make sure the view is positioned next
- // to the view that is currently visible. The animation is taken
- // care of by transitioning the `transform: translateX()` property
- // instead.
- // Once the transition finished, we clean both properties up.
- nodeToAnimate.style.marginInlineStart = previousRect.width + "px";
- }
-
- // Set the transition style and listen for its end to clean up and
- // make sure the box sizing becomes dynamic again.
- // Somehow, putting these properties in PanelUI.css doesn't work for
- // newly shown nodes in a XUL parent node.
- nodeToAnimate.style.transition = "transform ease-" + (reverse ? "in" : "out") +
- " var(--panelui-subview-transition-duration)";
- nodeToAnimate.style.willChange = "transform";
- nodeToAnimate.style.borderInlineStart = "1px solid var(--panel-separator-color)";
-
- // Wait until after the first paint to ensure setting 'current=true'
- // has taken full effect; once both views are visible, we want to
- // correctly measure rects using `dwu.getBoundsWithoutFlushing`.
- window.addEventListener("MozAfterPaint", () => {
- if (this._panel.state != "open") {
- onTransitionEnd();
- return;
- }
- // Now set the viewContainer dimensions to that of the new view, which
- // kicks of the height animation.
- this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
- this._viewContainer.style.width = viewRect.width + "px";
- this._panel.removeAttribute("width");
- this._panel.removeAttribute("height");
+ await this._transitionViews(previousViewNode, viewNode, reverse, previousRect);
- // The 'magic' part: build up the amount of pixels to move right or left.
- let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
- let movementX = reverse ? viewRect.width : previousRect.width;
- let moveX = (moveToLeft ? "" : "-") + movementX;
- nodeToAnimate.style.transform = "translateX(" + moveX + "px)";
- // We're setting the width property to prevent flickering during the
- // sliding animation with smaller views.
- nodeToAnimate.style.width = viewRect.width + "px";
-
- this._viewContainer.addEventListener("transitionend", this._transitionEndListener = ev => {
- // It's quite common that `height` on the view container doesn't need
- // to transition, so we make sure to do all the work on the transform
- // transition-end, because that is guaranteed to happen.
- if (ev.target != nodeToAnimate || ev.propertyName != "transform")
- return;
-
- this._viewContainer.removeEventListener("transitionend", this._transitionEndListener);
- this._transitionEndListener = null;
- onTransitionEnd();
- this._transitioning = false;
- if (reverse) {
- this._resetKeyNavigation(previousViewNode);
- }
+ if (aAnchor)
+ aAnchor.removeAttribute("open");
- // Myeah, panel layout auto-resizing is a funky thing. We'll wait
- // another few milliseconds to remove the width and height 'fixtures',
- // to be sure we don't flicker annoyingly.
- // NB: HACK! Bug 1363756 is there to fix this.
- this._autoResizeWorkaroundTimer = window.setTimeout(() => {
- this._viewContainer.style.removeProperty("height");
- this._viewContainer.style.removeProperty("width");
- }, 500);
-
- // Take another breather, just like before, to wait for the 'current'
- // attribute removal to take effect. This prevents a flicker.
- // The cleanup we do doesn't affect the display anymore, so we're not
- // too fussed about the timing here.
- window.addEventListener("MozAfterPaint", () => {
- nodeToAnimate.style.removeProperty("border-inline-start");
- nodeToAnimate.style.removeProperty("transition");
- nodeToAnimate.style.removeProperty("transform");
- nodeToAnimate.style.removeProperty("width");
-
- if (!reverse)
- viewNode.style.removeProperty("margin-inline-start");
- if (aAnchor)
- aAnchor.removeAttribute("open");
-
- this._viewContainer.removeAttribute("transition-reverse");
-
- this._dispatchViewEvent(viewNode, "ViewShown");
- this._updateKeyboardFocus(viewNode);
- }, { once: true });
- });
- }, { once: true });
- });
+ this._dispatchViewEvent(viewNode, "ViewShown");
+ this._updateKeyboardFocus(viewNode);
} else if (!this.panelViews) {
this._transitionHeight(() => {
viewNode.setAttribute("current", true);
if (viewNode.id == this._mainViewId) {
this.node.setAttribute("viewtype", "main");
} else {
this.node.setAttribute("viewtype", "subview");
}
@@ -670,16 +557,171 @@ this.PanelMultiView = class {
this._dispatchViewEvent(viewNode, "ViewShown");
});
this._shiftMainView(aAnchor);
}
})().catch(e => Cu.reportError(e));
}
/**
+ * Apply a transition to 'slide' from the currently active view to the next
+ * one.
+ * Sliding the next subview in means that the previous panelview stays where it
+ * is and the active panelview slides in from the left in LTR mode, right in
+ * RTL mode.
+ *
+ * @param {panelview} previousViewNode Node that is currently shown as active,
+ * but is about to be transitioned away.
+ * @param {panelview} viewNode Node that will becode the active view,
+ * after the transition has finished.
+ * @param {Boolean} reverse Whether we're navigation back to a
+ * previous view or forward to a next view.
+ * @param {Object} previousRect Rect object, with the same structure as
+ * a DOMRect, of the `previousViewNode`.
+ * @param {Function} callback Function that will be invoked when the
+ * transition is finished or when the
+ * operation was canceled (early return).
+ */
+ async _transitionViews(previousViewNode, viewNode, reverse, previousRect) {
+ // There's absolutely no need to show off our epic animation skillz when
+ // the panel's not even open.
+ if (this._panel.state != "open") {
+ return;
+ }
+
+ const {window, document} = this;
+
+ if (this._autoResizeWorkaroundTimer)
+ window.clearTimeout(this._autoResizeWorkaroundTimer);
+
+ // Set the viewContainer dimensions to make sure only the current view is
+ // visible.
+ this._viewContainer.style.height = Math.max(previousRect.height, this._mainViewHeight) + "px";
+ this._viewContainer.style.width = previousRect.width + "px";
+ // Lock the dimensions of the window that hosts the popup panel.
+ let rect = this._panel.popupBoxObject.getOuterScreenRect();
+ this._panel.setAttribute("width", rect.width);
+ this._panel.setAttribute("height", rect.height);
+
+ let viewRect;
+ if (viewNode.__lastKnownBoundingRect) {
+ viewRect = viewNode.__lastKnownBoundingRect;
+ viewNode.setAttribute("current", true);
+ this.descriptionHeightWorkaround(viewNode);
+ } else if (viewNode.customRectGetter) {
+ // Can't use Object.assign directly with a DOM Rect object because its properties
+ // aren't enumerable.
+ let {height, width} = previousRect;
+ viewRect = Object.assign({height, width}, viewNode.customRectGetter());
+ let {header} = viewNode;
+ if (header) {
+ viewRect.height += this._dwu.getBoundsWithoutFlushing(header).height;
+ }
+ viewNode.setAttribute("current", true);
+ this.descriptionHeightWorkaround(viewNode);
+ } else {
+ let oldSibling = viewNode.nextSibling || null;
+ this._offscreenViewStack.appendChild(viewNode);
+ viewNode.setAttribute("current", true);
+ this.descriptionHeightWorkaround(viewNode);
+
+ viewRect = await BrowserUtils.promiseLayoutFlushed(this.document, "layout", () => {
+ return this._dwu.getBoundsWithoutFlushing(viewNode);
+ });
+
+ try {
+ this._viewStack.insertBefore(viewNode, oldSibling);
+ } catch (ex) {
+ this._viewStack.appendChild(viewNode);
+ }
+ }
+
+ this._transitioning = true;
+
+ // The 'magic' part: build up the amount of pixels to move right or left.
+ let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
+ let deltaX = previousRect.width;
+ let deepestNode = reverse ? previousViewNode : viewNode;
+
+ // With a transition when navigating backwards - user hits the 'back'
+ // button - we need to make sure that the views are positioned in a way
+ // that a translateX() unveils the previous view from the right direction.
+ if (reverse)
+ this._viewStack.style.marginInlineStart = "-" + deltaX + "px";
+
+ // Set the transition style and listen for its end to clean up and make sure
+ // the box sizing becomes dynamic again.
+ // Somehow, putting these properties in PanelUI.css doesn't work for newly
+ // shown nodes in a XUL parent node.
+ this._viewStack.style.transition = "transform var(--animation-easing-function)" +
+ " var(--panelui-subview-transition-duration)";
+ this._viewStack.style.willChange = "transform";
+ deepestNode.style.borderInlineStart = "1px solid var(--panel-separator-color)";
+
+ // Now set the viewContainer dimensions to that of the new view, which
+ // kicks of the height animation.
+ this._viewContainer.style.height = Math.max(viewRect.height, this._mainViewHeight) + "px";
+ this._viewContainer.style.width = viewRect.width + "px";
+ this._panel.removeAttribute("width");
+ this._panel.removeAttribute("height");
+ // We're setting the width property to prevent flickering during the
+ // sliding animation with smaller views.
+ viewNode.style.width = viewRect.width + "px";
+
+ await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {});
+
+ // Kick off the transition!
+ this._viewStack.style.transform = "translateX(" + (moveToLeft ? "" : "-") + deltaX + "px)";
+
+ await new Promise(resolve => {
+ this._transitionEndResolve = resolve;
+ this._viewContainer.addEventListener("transitionend", this._transitionEndListener = ev => {
+ // It's quite common that `height` on the view container doesn't need
+ // to transition, so we make sure to do all the work on the transform
+ // transition-end, because that is guaranteed to happen.
+ if (ev.target != this._viewStack || ev.propertyName != "transform")
+ return;
+ this._viewContainer.removeEventListener("transitionend", this._transitionEndListener);
+ this._transitionEndListener = null;
+ resolve();
+ });
+ });
+
+ this._transitioning = false;
+
+ this._viewStack.style.removeProperty("margin-inline-start");
+ this._viewStack.style.removeProperty("transition");
+ this._viewStack.style.removeProperty("transform");
+ deepestNode.style.removeProperty("border-inline-start");
+ viewNode.style.removeProperty("width");
+
+ this._dispatchViewEvent(previousViewNode, "ViewHiding");
+ previousViewNode.removeAttribute("current");
+
+ previousViewNode.style.display = "none";
+ await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {});
+ previousViewNode.style.removeProperty("display");
+
+ this._panel.removeAttribute("width");
+ this._panel.removeAttribute("height");
+ // Myeah, panel layout auto-resizing is a funky thing. We'll wait
+ // another few milliseconds to remove the width and height 'fixtures',
+ // to be sure we don't flicker annoyingly.
+ // NB: HACK! Bug 1363756 is there to fix this.
+ this._autoResizeWorkaroundTimer = this.window.setTimeout(() => {
+ this._viewContainer.style.removeProperty("height");
+ this._viewContainer.style.removeProperty("width");
+ }, 500);
+
+ if (reverse) {
+ this._resetKeyNavigation(previousViewNode);
+ }
+ }
+
+ /**
* Helper method to emit an event on a panelview, whilst also making sure that
* the correct method is called on CustomizableWidget instances.
*
* @param {panelview} viewNode Target of the event to dispatch.
* @param {String} eventName Name of the event to dispatch.
* @param {DOMNode} [anchor] Node where the panel is anchored to. Optional.
* @param {Object} [detail] Event detail object. Optional.
* @return {Boolean} `true` if the event was canceled by an event handler, `false`
@@ -699,61 +741,16 @@ this.PanelMultiView = class {
});
viewNode.dispatchEvent(evt);
if (!cancel)
cancel = evt.defaultPrevented;
return cancel;
}
/**
- * Calculate the correct bounds of a panelview node offscreen to minimize the
- * amount of paint flashing and keep the stack vs panel layouts from interfering.
- *
- * @param {panelview} viewNode Node to measure the bounds of.
- * @param {Rect} previousRect Rect representing the previous view
- * (used to fill in any blanks).
- * @param {Function} callback Called when we got the measurements in and pass
- * them on as its first argument.
- */
- _viewBoundsOffscreen(viewNode, previousRect, callback) {
- if (viewNode.__lastKnownBoundingRect) {
- callback(viewNode.__lastKnownBoundingRect);
- return;
- }
-
- if (viewNode.customRectGetter) {
- // Can't use Object.assign directly with a DOM Rect object because its properties
- // aren't enumerable.
- let {height, width} = previousRect;
- let rect = Object.assign({height, width}, viewNode.customRectGetter());
- let {header} = viewNode;
- if (header) {
- rect.height += this._dwu.getBoundsWithoutFlushing(header).height;
- }
- callback(rect);
- return;
- }
-
- let oldSibling = viewNode.nextSibling || null;
- this._offscreenViewStack.appendChild(viewNode);
-
- BrowserUtils.promiseLayoutFlushed(this.document, "layout", () => {
- return this._dwu.getBoundsWithoutFlushing(viewNode);
- }).then(viewRect => {
- try {
- this._viewStack.insertBefore(viewNode, oldSibling);
- } catch (ex) {
- this._viewStack.appendChild(viewNode);
- }
-
- callback(viewRect);
- });
- }
-
- /**
* Applies the height transition for which <panelmultiview> is designed.
*
* The height transition involves two elements, the viewContainer and its only
* immediate child the viewStack. In order for this to work correctly, the
* viewContainer must have "overflow: hidden;" and the two elements must have
* no margins or padding. This means that the height of the viewStack is never
* limited by the viewContainer, but when the height of the container is not
* constrained it matches the height of the viewStack.
@@ -960,16 +957,17 @@ this.PanelMultiView = class {
this._viewShowing = null;
this._transitioning = false;
this.node.removeAttribute("panelopen");
this.showMainView();
if (this.panelViews) {
if (this._transitionEndListener) {
this._viewContainer.removeEventListener("transitionend", this._transitionEndListener);
this._transitionEndListener = null;
+ this._transitionEndResolve();
}
for (let panelView of this._viewStack.children) {
if (panelView.nodeName != "children") {
panelView.__lastKnownBoundingRect = null;
panelView.style.removeProperty("min-width");
panelView.style.removeProperty("max-width");
}
}
--- a/toolkit/themes/osx/global/global.css
+++ b/toolkit/themes/osx/global/global.css
@@ -12,17 +12,17 @@
menulist > menupopup {
-moz-binding: url("chrome://global/content/bindings/popup.xml#popup-scrollbars");
}
/* ::::: Variables ::::: */
:root {
--arrowpanel-padding: 16px;
- --arrowpanel-background: linear-gradient(hsla(0,0%,99%,1), hsla(0,0%,99%,.975) 10%, hsla(0,0%,98%,.975));
+ --arrowpanel-background: hsl(0,0%,99%);
--arrowpanel-color: hsl(0,0%,10%);
--arrowpanel-border-color: hsla(210,4%,10%,.05);
--arrowpanel-border-radius: 3.5px;
--focus-ring-box-shadow: @focusRingShadow@;
}
/* ::::: root elements ::::: */