зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1401991 - Ensure that we don't hide panelviews that are already reparented to another multi-view and ensure to hide other panels consistently. r=Gijs
* Harden the new `hideAllViewsExcept()` to not do erroneous things if called when the binding is already gone. * Generalize things into `hideAllViewsExcept(thisOne)`: - Clear `_viewShowing` in there and do the descriptionHeightWorkaround thing in there too, - For Photon panels, do all the 'current' attribute setting in there. To show a panel during transition, I introduced the 'in-transition' attribute. * I had to make sure not to over-eagerly dispatch 'ViewShowing' events, because that confuses some, * Move the temporary panel handling, which contains an ephemeral panelmultiview instance, internally. This cleans up the hacky, duplicate PanelUI.js code nicely. * Keep a local copy of `_transitionDetails` to ensure it's still there after transition, * Harden `_cleanupTransitionPhase()` to only clear the phase that belongs to a specific transition, _if_ that's passed in as an argument. This resolves any potential raciness that might occur when `showSubView()` is called again mid-transition. * Skip the UITour element visibility check when it's inside a panelview, because too many things need to happen and that check is too simple to be useful in that case. MozReview-Commit-ID: 5HpJKs1Ny5j --HG-- extra : rebase_source : b810e1de2dbd75932a42d68e751fdaecd9fee69a
This commit is contained in:
Родитель
cf92431595
Коммит
882fa06e25
|
@ -90,12 +90,12 @@ panel[hidden] panelview {
|
|||
transition: transform var(--panelui-subview-transition-duration);
|
||||
}
|
||||
|
||||
panelview:not([mainview]):not([current]) {
|
||||
panelview:not([mainview]):not([current]):not([in-transition]) {
|
||||
transition: visibility 0s linear var(--panelui-subview-transition-duration);
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
photonpanelmultiview panelview:not([current]) {
|
||||
photonpanelmultiview panelview:not([current]):not([in-transition]) {
|
||||
transition: none;
|
||||
visibility: collapse;
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ const EXPECTED_APPMENU_SUBVIEW_REFLOWS = [
|
|||
{
|
||||
stack: [
|
||||
"descriptionHeightWorkaround@resource:///modules/PanelMultiView.jsm",
|
||||
"_cleanupTransitionPhase@resource:///modules/PanelMultiView.jsm",
|
||||
"hideAllViewsExcept@resource:///modules/PanelMultiView.jsm",
|
||||
],
|
||||
|
||||
times: 2, // This number should only ever go down - never up.
|
||||
|
|
|
@ -191,6 +191,15 @@ this.PanelMultiView = class {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean} |true| when the 'ephemeral' attribute is set, which means
|
||||
* that this instance should be ready to be thrown away at
|
||||
* any time.
|
||||
*/
|
||||
get _ephemeral() {
|
||||
return this.node.hasAttribute("ephemeral");
|
||||
}
|
||||
|
||||
get panelViews() {
|
||||
// If there's a dedicated subViews container, we're not in the right binding
|
||||
// to use SlidingPanelViews.
|
||||
|
@ -217,10 +226,9 @@ this.PanelMultiView = class {
|
|||
.getService(Ci.nsIScreenManager);
|
||||
}
|
||||
/**
|
||||
* Getter that returns the currently visible subview OR the subview that is
|
||||
* about to be shown whilst a 'ViewShowing' event is being dispatched.
|
||||
*
|
||||
* @return {panelview}
|
||||
* @return {panelview} the currently visible subview OR the subview that is
|
||||
* about to be shown whilst a 'ViewShowing' event is being
|
||||
* dispatched.
|
||||
*/
|
||||
get current() {
|
||||
return this._viewShowing || this._currentSubView
|
||||
|
@ -235,6 +243,13 @@ this.PanelMultiView = class {
|
|||
this.__currentSubView = panel;
|
||||
return panel;
|
||||
}
|
||||
/**
|
||||
* @return {Promise} showSubView() returns a promise, which is kept here for
|
||||
* random access.
|
||||
*/
|
||||
get currentShowPromise() {
|
||||
return this._currentShowPromise || Promise.resolve();
|
||||
}
|
||||
get _keyNavigationMap() {
|
||||
if (!this.__keyNavigationMap)
|
||||
this.__keyNavigationMap = new Map();
|
||||
|
@ -316,9 +331,11 @@ this.PanelMultiView = class {
|
|||
value: (...args) => this[method](...args)
|
||||
});
|
||||
});
|
||||
Object.defineProperty(this.node, "current", {
|
||||
enumerable: true,
|
||||
get: () => this.current
|
||||
["current", "currentShowPromise"].forEach(property => {
|
||||
Object.defineProperty(this.node, property, {
|
||||
enumerable: true,
|
||||
get: () => this[property]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -327,8 +344,10 @@ this.PanelMultiView = class {
|
|||
if (!this.node)
|
||||
return;
|
||||
|
||||
if (this._mainView) {
|
||||
let mainView = this._mainView;
|
||||
if (this._ephemeral)
|
||||
this.hideAllViewsExcept(null);
|
||||
let mainView = this._mainView;
|
||||
if (mainView) {
|
||||
if (this._panelViewCache)
|
||||
this._panelViewCache.appendChild(mainView);
|
||||
mainView.removeAttribute("mainview");
|
||||
|
@ -395,10 +414,13 @@ this.PanelMultiView = class {
|
|||
* @return {Boolean}
|
||||
*/
|
||||
_canGoBack(view = this._currentSubView) {
|
||||
return view != this._mainView;
|
||||
return view.id != this._mainViewId;
|
||||
}
|
||||
|
||||
setMainView(aNewMainView) {
|
||||
if (!aNewMainView)
|
||||
return;
|
||||
|
||||
if (this._mainView) {
|
||||
if (!this.panelViews)
|
||||
this._subViews.appendChild(this._mainView);
|
||||
|
@ -418,38 +440,59 @@ this.PanelMultiView = class {
|
|||
}
|
||||
|
||||
showMainView() {
|
||||
if (!this._mainViewId)
|
||||
return Promise.resolve();
|
||||
|
||||
if (this.panelViews)
|
||||
return this.showSubView(this._mainView);
|
||||
|
||||
if (this.showingSubView) {
|
||||
let viewNode = this._currentSubView;
|
||||
this._dispatchViewEvent(viewNode, "ViewHiding");
|
||||
if (this.panelViews) {
|
||||
this._transitionHeight(() => {
|
||||
viewNode.removeAttribute("current");
|
||||
this.showSubView(this._mainViewId);
|
||||
} else {
|
||||
this._transitionHeight(() => {
|
||||
viewNode.removeAttribute("current");
|
||||
this._currentSubView = null;
|
||||
this.node.setAttribute("viewtype", "main");
|
||||
});
|
||||
}
|
||||
} else if (this.panelViews) {
|
||||
// Make sure to hide all subviews, except for the mainView.
|
||||
let mainView = this._mainView;
|
||||
for (let panelview of this._panelViews) {
|
||||
if (panelview == mainView)
|
||||
panelview.setAttribute("current", true);
|
||||
else
|
||||
panelview.removeAttribute("current");
|
||||
}
|
||||
this.node.setAttribute("viewtype", "main");
|
||||
this._currentSubView = null;
|
||||
this.node.setAttribute("viewtype", "main");
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.panelViews) {
|
||||
this._shiftMainView();
|
||||
this._shiftMainView();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that all the panelviews, that are currently part of this instance,
|
||||
* are hidden, except one specifically.
|
||||
*
|
||||
* @param {panelview} [theOne] The panelview DOM node to ensure is visible.
|
||||
* Optional.
|
||||
*/
|
||||
hideAllViewsExcept(theOne = null) {
|
||||
for (let panelview of this._panelViews) {
|
||||
// When the panelview was already reparented, don't interfere any more.
|
||||
if (panelview == theOne || !this.node || panelview.panelMultiView != this.node)
|
||||
continue;
|
||||
if (panelview.hasAttribute("current"))
|
||||
this._dispatchViewEvent(panelview, "ViewHiding");
|
||||
panelview.removeAttribute("current");
|
||||
}
|
||||
|
||||
this._viewShowing = null;
|
||||
|
||||
if (!this.node || !theOne)
|
||||
return;
|
||||
|
||||
this._currentSubView = theOne;
|
||||
if (!theOne.hasAttribute("current")) {
|
||||
theOne.setAttribute("current", true);
|
||||
this.descriptionHeightWorkaround(theOne);
|
||||
this._dispatchViewEvent(theOne, "ViewShown");
|
||||
}
|
||||
this.node.setAttribute("viewtype", (theOne.id == this._mainViewId) ? "main" : "subview");
|
||||
}
|
||||
|
||||
showSubView(aViewId, aAnchor, aPreviousView) {
|
||||
return (async () => {
|
||||
this._currentShowPromise = (async () => {
|
||||
// Support passing in the node directly.
|
||||
let viewNode = typeof aViewId == "string" ? this.node.querySelector("#" + aViewId) : aViewId;
|
||||
if (!viewNode) {
|
||||
|
@ -465,8 +508,10 @@ this.PanelMultiView = class {
|
|||
|
||||
let reverse = !!aPreviousView;
|
||||
let previousViewNode = aPreviousView || this._currentSubView;
|
||||
let playTransition = (!!previousViewNode && previousViewNode != viewNode &&
|
||||
this._panel.state == "open");
|
||||
// If the panelview to show is the same as the previous one, the 'ViewShowing'
|
||||
// event has already been dispatched. Don't do it twice.
|
||||
let showingSameView = viewNode == previousViewNode;
|
||||
let playTransition = (!!previousViewNode && !showingSameView && this._panel.state == "open");
|
||||
|
||||
let dwu, previousRect;
|
||||
if (playTransition || this.panelViews) {
|
||||
|
@ -492,6 +537,13 @@ this.PanelMultiView = class {
|
|||
}
|
||||
|
||||
this._viewShowing = viewNode;
|
||||
// Because the 'mainview' attribute may be out-of-sync, due to view node
|
||||
// reparenting in combination with ephemeral PanelMultiView instances,
|
||||
// this is the best place to correct it (just before showing).
|
||||
if (viewNode.id == this._mainViewId)
|
||||
viewNode.setAttribute("mainview", true);
|
||||
else
|
||||
viewNode.removeAttribute("mainview");
|
||||
|
||||
// Make sure that new panels always have a title set.
|
||||
if (this.panelViews && aAnchor) {
|
||||
|
@ -502,53 +554,45 @@ this.PanelMultiView = class {
|
|||
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 or perhaps even cancel this
|
||||
// whole operation.
|
||||
let detail = {
|
||||
blockers: new Set(),
|
||||
addBlocker(promise) {
|
||||
this.blockers.add(promise);
|
||||
if (!showingSameView || !viewNode.hasAttribute("current")) {
|
||||
// Emit the ViewShowing event so that the widget definition has a chance
|
||||
// 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) {
|
||||
try {
|
||||
let results = await Promise.all(detail.blockers);
|
||||
cancel = cancel || results.some(val => val === false);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
let cancel = this._dispatchViewEvent(viewNode, "ViewShowing", aAnchor, detail);
|
||||
if (detail.blockers.size) {
|
||||
try {
|
||||
let results = await Promise.all(detail.blockers);
|
||||
cancel = cancel || results.some(val => val === false);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._viewShowing = null;
|
||||
if (cancel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentSubView = viewNode;
|
||||
if (this.panelViews) {
|
||||
if (viewNode.id == this._mainViewId) {
|
||||
this.node.setAttribute("viewtype", "main");
|
||||
} else {
|
||||
this.node.setAttribute("viewtype", "subview");
|
||||
}
|
||||
// If we've got an older transition still running, make sure to clean it up.
|
||||
await this._cleanupTransitionPhase();
|
||||
if (!playTransition) {
|
||||
viewNode.setAttribute("current", true);
|
||||
this.descriptionHeightWorkaround(viewNode);
|
||||
if (cancel) {
|
||||
this._viewShowing = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have to transition the panel.
|
||||
if (this.panelViews && playTransition) {
|
||||
await this._transitionViews(previousViewNode, viewNode, reverse, previousRect, aAnchor);
|
||||
|
||||
this._dispatchViewEvent(viewNode, "ViewShown");
|
||||
this._updateKeyboardFocus(viewNode);
|
||||
} else if (!this.panelViews) {
|
||||
if (this.panelViews) {
|
||||
// If we've got an older transition still running, make sure to clean it up.
|
||||
await this._cleanupTransitionPhase();
|
||||
if (playTransition) {
|
||||
await this._transitionViews(previousViewNode, viewNode, reverse, previousRect, aAnchor);
|
||||
this._updateKeyboardFocus(viewNode);
|
||||
} else {
|
||||
this.hideAllViewsExcept(viewNode);
|
||||
}
|
||||
} else {
|
||||
this._currentSubView = viewNode;
|
||||
this._transitionHeight(() => {
|
||||
viewNode.setAttribute("current", true);
|
||||
if (viewNode.id == this._mainViewId) {
|
||||
|
@ -564,6 +608,7 @@ this.PanelMultiView = class {
|
|||
this._shiftMainView(aAnchor);
|
||||
}
|
||||
})().catch(e => Cu.reportError(e));
|
||||
return this._currentShowPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -596,7 +641,7 @@ this.PanelMultiView = class {
|
|||
if (this._autoResizeWorkaroundTimer)
|
||||
window.clearTimeout(this._autoResizeWorkaroundTimer);
|
||||
|
||||
this._transitionDetails = {
|
||||
let details = this._transitionDetails = {
|
||||
phase: TRANSITION_PHASES.START,
|
||||
previousViewNode, viewNode, reverse, anchor
|
||||
};
|
||||
|
@ -604,6 +649,10 @@ this.PanelMultiView = class {
|
|||
if (anchor)
|
||||
anchor.setAttribute("open", "true");
|
||||
|
||||
// Since we're going to show two subview at the same time, don't abuse the
|
||||
// 'current' attribute, since it's needed for other state-keeping, but use
|
||||
// a separate 'in-transition' attribute instead.
|
||||
previousViewNode.setAttribute("in-transition", 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";
|
||||
|
@ -616,7 +665,7 @@ this.PanelMultiView = class {
|
|||
let viewRect;
|
||||
if (viewNode.__lastKnownBoundingRect) {
|
||||
viewRect = viewNode.__lastKnownBoundingRect;
|
||||
viewNode.setAttribute("current", true);
|
||||
viewNode.setAttribute("in-transition", true);
|
||||
} else if (viewNode.customRectGetter) {
|
||||
// Can't use Object.assign directly with a DOM Rect object because its properties
|
||||
// aren't enumerable.
|
||||
|
@ -626,11 +675,11 @@ this.PanelMultiView = class {
|
|||
if (header) {
|
||||
viewRect.height += this._dwu.getBoundsWithoutFlushing(header).height;
|
||||
}
|
||||
viewNode.setAttribute("current", true);
|
||||
viewNode.setAttribute("in-transition", true);
|
||||
} else {
|
||||
let oldSibling = viewNode.nextSibling || null;
|
||||
this._offscreenViewStack.appendChild(viewNode);
|
||||
viewNode.setAttribute("current", true);
|
||||
viewNode.setAttribute("in-transition", true);
|
||||
|
||||
viewRect = await BrowserUtils.promiseLayoutFlushed(this.document, "layout", () => {
|
||||
return this._dwu.getBoundsWithoutFlushing(viewNode);
|
||||
|
@ -644,7 +693,7 @@ this.PanelMultiView = class {
|
|||
}
|
||||
|
||||
this._transitioning = true;
|
||||
this._transitionDetails.phase = TRANSITION_PHASES.PREPARE;
|
||||
details.phase = TRANSITION_PHASES.PREPARE;
|
||||
|
||||
// The 'magic' part: build up the amount of pixels to move right or left.
|
||||
let moveToLeft = (this._dir == "rtl" && !reverse) || (this._dir == "ltr" && reverse);
|
||||
|
@ -679,26 +728,26 @@ this.PanelMultiView = class {
|
|||
await BrowserUtils.promiseLayoutFlushed(document, "layout", () => {});
|
||||
|
||||
// Kick off the transition!
|
||||
this._transitionDetails.phase = TRANSITION_PHASES.TRANSITION;
|
||||
details.phase = TRANSITION_PHASES.TRANSITION;
|
||||
this._viewStack.style.transform = "translateX(" + (moveToLeft ? "" : "-") + deltaX + "px)";
|
||||
|
||||
await new Promise(resolve => {
|
||||
this._transitionDetails.resolve = resolve;
|
||||
this._viewContainer.addEventListener("transitionend", this._transitionDetails.listener = ev => {
|
||||
details.resolve = resolve;
|
||||
this._viewContainer.addEventListener("transitionend", details.listener = 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._transitionDetails.listener);
|
||||
delete this._transitionDetails.listener;
|
||||
this._viewContainer.removeEventListener("transitionend", details.listener);
|
||||
delete details.listener;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
this._transitionDetails.phase = TRANSITION_PHASES.END;
|
||||
details.phase = TRANSITION_PHASES.END;
|
||||
|
||||
await this._cleanupTransitionPhase();
|
||||
await this._cleanupTransitionPhase(details);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -706,8 +755,9 @@ this.PanelMultiView = class {
|
|||
* above. Which attributes and properties depends on the phase the transition
|
||||
* was left from - normally that'd be `TRANSITION_PHASES.END`.
|
||||
*/
|
||||
async _cleanupTransitionPhase() {
|
||||
if (!this._transitionDetails)
|
||||
async _cleanupTransitionPhase(details = this._transitionDetails) {
|
||||
// Make sure to only clean up a phase from the most recent transition.
|
||||
if (!this._transitionDetails || details != this._transitionDetails)
|
||||
return;
|
||||
|
||||
let {phase, previousViewNode, viewNode, reverse, resolve, listener, anchor} = this._transitionDetails;
|
||||
|
@ -715,11 +765,11 @@ this.PanelMultiView = class {
|
|||
|
||||
// Do the things we _always_ need to do whenever the transition ends or is
|
||||
// interrupted.
|
||||
this._dispatchViewEvent(previousViewNode, "ViewHiding");
|
||||
previousViewNode.removeAttribute("current");
|
||||
this.hideAllViewsExcept(viewNode);
|
||||
previousViewNode.removeAttribute("in-transition");
|
||||
viewNode.removeAttribute("in-transition");
|
||||
if (reverse)
|
||||
this._resetKeyNavigation(previousViewNode);
|
||||
this.descriptionHeightWorkaround(viewNode);
|
||||
|
||||
if (anchor)
|
||||
anchor.removeAttribute("open");
|
||||
|
@ -915,7 +965,7 @@ this.PanelMultiView = class {
|
|||
case "mousemove":
|
||||
this._resetKeyNavigation();
|
||||
break;
|
||||
case "popupshowing":
|
||||
case "popupshowing": {
|
||||
this.node.setAttribute("panelopen", "true");
|
||||
// Bug 941196 - The panel can get taller when opening a subview. Disabling
|
||||
// autoPositioning means that the panel won't jump around if an opened
|
||||
|
@ -971,13 +1021,14 @@ this.PanelMultiView = class {
|
|||
// without any scrolling (using "display: flex;"), and only if the view
|
||||
// exceeds the available space we set the height explicitly and enable
|
||||
// scrolling.
|
||||
if (this._mainView.hasAttribute("blockinboxworkaround")) {
|
||||
let mainView = this._mainView;
|
||||
if (mainView && mainView.hasAttribute("blockinboxworkaround")) {
|
||||
let blockInBoxWorkaround = () => {
|
||||
let mainViewHeight =
|
||||
this._dwu.getBoundsWithoutFlushing(this._mainView).height;
|
||||
this._dwu.getBoundsWithoutFlushing(mainView).height;
|
||||
if (mainViewHeight > maxHeight) {
|
||||
this._mainView.style.height = maxHeight + "px";
|
||||
this._mainView.setAttribute("exceeding", "true");
|
||||
mainView.style.height = maxHeight + "px";
|
||||
mainView.setAttribute("exceeding", "true");
|
||||
}
|
||||
};
|
||||
// On Windows, we cannot measure the full height of the main view
|
||||
|
@ -993,12 +1044,14 @@ this.PanelMultiView = class {
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "popupshown":
|
||||
// Now that the main view is visible, we can check the height of the
|
||||
// description elements it contains.
|
||||
this.descriptionHeightWorkaround();
|
||||
if (!this.panelViews)
|
||||
this.descriptionHeightWorkaround();
|
||||
break;
|
||||
case "popuphidden":
|
||||
case "popuphidden": {
|
||||
// WebExtensions consumers can hide the popup from viewshowing, or
|
||||
// mid-transition, which disrupts our state:
|
||||
this._viewShowing = null;
|
||||
|
@ -1029,12 +1082,14 @@ this.PanelMultiView = class {
|
|||
|
||||
// Always try to layout the panel normally when reopening it. This is
|
||||
// also the layout that will be used in customize mode.
|
||||
if (this._mainView.hasAttribute("blockinboxworkaround")) {
|
||||
this._mainView.style.removeProperty("height");
|
||||
this._mainView.removeAttribute("exceeding");
|
||||
let mainView = this._mainView;
|
||||
if (mainView && mainView.hasAttribute("blockinboxworkaround")) {
|
||||
mainView.style.removeProperty("height");
|
||||
mainView.removeAttribute("exceeding");
|
||||
}
|
||||
this._dispatchViewEvent(this.node, "PanelMultiViewHidden");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1257,7 +1312,7 @@ this.PanelMultiView = class {
|
|||
* view if omitted.
|
||||
*/
|
||||
descriptionHeightWorkaround(viewNode = this._mainView) {
|
||||
if (!viewNode.hasAttribute("descriptionheightworkaround")) {
|
||||
if (!viewNode || !viewNode.hasAttribute("descriptionheightworkaround")) {
|
||||
// This view does not require the workaround.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -419,31 +419,37 @@ const PanelUI = {
|
|||
tempPanel.setAttribute("animate", "false");
|
||||
}
|
||||
tempPanel.setAttribute("context", "");
|
||||
tempPanel.setAttribute("photon", true);
|
||||
document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
|
||||
// If the view has a footer, set a convenience class on the panel.
|
||||
tempPanel.classList.toggle("cui-widget-panelWithFooter",
|
||||
viewNode.querySelector(".panel-subview-footer"));
|
||||
|
||||
// If the panelview is already selected in another PanelMultiView instance
|
||||
// as a subview, make sure to properly hide it there.
|
||||
let oldMultiView = viewNode.panelMultiView;
|
||||
if (oldMultiView && oldMultiView.current == viewNode) {
|
||||
await oldMultiView.showMainView();
|
||||
}
|
||||
|
||||
let viewShown = false;
|
||||
let listener = () => viewShown = true;
|
||||
viewNode.addEventListener("ViewShown", listener, {once: true});
|
||||
|
||||
let multiView = document.createElement("photonpanelmultiview");
|
||||
multiView.setAttribute("id", "customizationui-widget-multiview");
|
||||
multiView.setAttribute("nosubviews", "true");
|
||||
multiView.setAttribute("viewCacheId", "appMenu-viewCache");
|
||||
tempPanel.setAttribute("photon", true);
|
||||
multiView.setAttribute("mainViewId", viewNode.id);
|
||||
multiView.appendChild(viewNode);
|
||||
multiView.setAttribute("ephemeral", true);
|
||||
document.getElementById("appMenu-viewCache").appendChild(viewNode);
|
||||
tempPanel.appendChild(multiView);
|
||||
viewNode.classList.add("cui-widget-panelview");
|
||||
|
||||
let viewShown = false;
|
||||
let panelRemover = () => {
|
||||
viewNode.classList.remove("cui-widget-panelview");
|
||||
if (viewShown) {
|
||||
CustomizableUI.removePanelCloseListeners(tempPanel);
|
||||
tempPanel.removeEventListener("popuphidden", panelRemover);
|
||||
|
||||
let currentView = multiView.current || viewNode;
|
||||
let evt = new CustomEvent("ViewHiding", {detail: currentView});
|
||||
currentView.dispatchEvent(evt);
|
||||
}
|
||||
aAnchor.open = false;
|
||||
|
||||
|
@ -453,35 +459,15 @@ const PanelUI = {
|
|||
tempPanel.remove();
|
||||
};
|
||||
|
||||
// Emit the ViewShowing event so that the widget definition has a chance
|
||||
// to lazily populate the subview with things.
|
||||
let detail = {
|
||||
blockers: new Set(),
|
||||
addBlocker(aPromise) {
|
||||
this.blockers.add(aPromise);
|
||||
},
|
||||
};
|
||||
// Wait until all the tasks needed to show a view are done.
|
||||
await multiView.currentShowPromise;
|
||||
|
||||
let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
|
||||
viewNode.dispatchEvent(evt);
|
||||
|
||||
let cancel = evt.defaultPrevented;
|
||||
if (detail.blockers.size) {
|
||||
try {
|
||||
let results = await Promise.all(detail.blockers);
|
||||
cancel = cancel || results.some(val => val === false);
|
||||
} catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
cancel = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
if (!viewShown) {
|
||||
viewNode.removeEventListener("ViewShown", listener);
|
||||
panelRemover();
|
||||
return;
|
||||
}
|
||||
|
||||
viewShown = true;
|
||||
CustomizableUI.addPanelCloseListeners(tempPanel);
|
||||
tempPanel.addEventListener("popuphidden", panelRemover);
|
||||
|
||||
|
@ -539,7 +525,8 @@ const PanelUI = {
|
|||
withFavicons: true
|
||||
});
|
||||
// If there's nothing to display, or the panel is already hidden, get out.
|
||||
if (!highlights.length || viewNode.panelMultiView.getAttribute("panelopen") != "true") {
|
||||
let multiView = viewNode.panelMultiView;
|
||||
if (!highlights.length || (multiView && multiView.getAttribute("panelopen") != "true")) {
|
||||
this._loadingRecentHighlights = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -35,15 +35,16 @@ function checkSeparatorInsertion(menuId, buttonId, subviewId) {
|
|||
|
||||
await document.getElementById("nav-bar").overflowable.show();
|
||||
|
||||
let subview = document.getElementById(subviewId);
|
||||
let button = document.getElementById(buttonId);
|
||||
button.click();
|
||||
await BrowserTestUtils.waitForEvent(subview, "ViewShown");
|
||||
|
||||
await BrowserTestUtils.waitForEvent(PanelUI.overflowPanel, "ViewShown");
|
||||
let subview = document.getElementById(subviewId);
|
||||
ok(subview.firstChild, "Subview should have a kid");
|
||||
is(subview.firstChild.localName, "toolbarbutton", "There should be no separators to start with");
|
||||
let subviewBody = subview.firstChild;
|
||||
ok(subviewBody.firstChild, "Subview should have a kid");
|
||||
is(subviewBody.firstChild.localName, "toolbarbutton", "There should be no separators to start with");
|
||||
|
||||
for (let kid of subview.children) {
|
||||
for (let kid of subviewBody.children) {
|
||||
if (kid.localName == "menuseparator") {
|
||||
ok(kid.previousSibling && kid.previousSibling.localName != "menuseparator",
|
||||
"Separators should never have another separator next to them, and should never be the first node.");
|
||||
|
@ -58,7 +59,7 @@ function checkSeparatorInsertion(menuId, buttonId, subviewId) {
|
|||
};
|
||||
}
|
||||
|
||||
add_task(checkSeparatorInsertion("menuWebDeveloperPopup", "developer-button", "PanelUI-developerItems"));
|
||||
add_task(checkSeparatorInsertion("menuWebDeveloperPopup", "developer-button", "PanelUI-developer"));
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
for (let el of tempElements) {
|
||||
|
|
|
@ -8,7 +8,6 @@ const kWidgetId = "test-981418-widget-onbeforecreated";
|
|||
|
||||
// Should be able to add broken view widget
|
||||
add_task(async function testAddOnBeforeCreatedWidget() {
|
||||
let viewShownDeferred = Promise.defer();
|
||||
let onBeforeCreatedCalled = false;
|
||||
let widgetSpec = {
|
||||
id: kWidgetId,
|
||||
|
@ -17,73 +16,47 @@ add_task(async function testAddOnBeforeCreatedWidget() {
|
|||
onBeforeCreated(doc) {
|
||||
let view = doc.createElement("panelview");
|
||||
view.id = kWidgetId + "idontexistyet";
|
||||
document.getElementById("PanelUI-multiView").appendChild(view);
|
||||
document.getElementById("appMenu-viewCache").appendChild(view);
|
||||
onBeforeCreatedCalled = true;
|
||||
},
|
||||
onViewShowing() {
|
||||
viewShownDeferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
let noError = true;
|
||||
try {
|
||||
CustomizableUI.createWidget(widgetSpec);
|
||||
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
noError = false;
|
||||
}
|
||||
ok(noError, "Should not throw an exception trying to add the widget.");
|
||||
CustomizableUI.createWidget(widgetSpec);
|
||||
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_NAVBAR);
|
||||
|
||||
ok(onBeforeCreatedCalled, "onBeforeCreated should have been called");
|
||||
|
||||
let widgetNode = document.getElementById(kWidgetId);
|
||||
let viewNode = document.getElementById(kWidgetId + "idontexistyet");
|
||||
ok(widgetNode, "Widget should exist");
|
||||
if (widgetNode) {
|
||||
try {
|
||||
widgetNode.click();
|
||||
ok(viewNode, "Panelview should exist");
|
||||
widgetNode.click();
|
||||
|
||||
let tempPanel = document.getElementById("customizationui-widget-panel");
|
||||
let panelShownPromise = promisePanelElementShown(window, tempPanel);
|
||||
let tempPanel = document.getElementById("customizationui-widget-panel");
|
||||
let panelShownPromise = promisePanelElementShown(window, tempPanel);
|
||||
|
||||
let shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
|
||||
await viewShownDeferred.promise;
|
||||
await panelShownPromise;
|
||||
clearTimeout(shownTimeout);
|
||||
ok(true, "Found view shown");
|
||||
await Promise.all([
|
||||
BrowserTestUtils.waitForEvent(viewNode, "ViewShown"),
|
||||
panelShownPromise
|
||||
]);
|
||||
|
||||
let panelHiddenPromise = promisePanelElementHidden(window, tempPanel);
|
||||
tempPanel.hidePopup();
|
||||
await panelHiddenPromise;
|
||||
let panelHiddenPromise = promisePanelElementHidden(window, tempPanel);
|
||||
tempPanel.hidePopup();
|
||||
await panelHiddenPromise;
|
||||
|
||||
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
await waitForOverflowButtonShown();
|
||||
await document.getElementById("nav-bar").overflowable.show();
|
||||
CustomizableUI.addWidgetToArea(kWidgetId, CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
|
||||
await waitForOverflowButtonShown();
|
||||
await document.getElementById("nav-bar").overflowable.show();
|
||||
|
||||
viewShownDeferred = Promise.defer();
|
||||
widgetNode.click();
|
||||
widgetNode.click();
|
||||
|
||||
shownTimeout = setTimeout(() => viewShownDeferred.reject("Panel not shown within 20s"), 20000);
|
||||
await viewShownDeferred.promise;
|
||||
clearTimeout(shownTimeout);
|
||||
ok(true, "Found view shown");
|
||||
await BrowserTestUtils.waitForEvent(viewNode, "ViewShown");
|
||||
|
||||
let panelHidden = promiseOverflowHidden(window);
|
||||
PanelUI.overflowPanel.hidePopup();
|
||||
await panelHidden;
|
||||
} catch (ex) {
|
||||
ok(false, "Unexpected exception (like a timeout for one of the yields) " +
|
||||
"when testing view widget.");
|
||||
}
|
||||
}
|
||||
let panelHidden = promiseOverflowHidden(window);
|
||||
PanelUI.overflowPanel.hidePopup();
|
||||
await panelHidden;
|
||||
|
||||
noError = true;
|
||||
try {
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
noError = false;
|
||||
}
|
||||
ok(noError, "Should not throw an exception trying to remove the broken view widget.");
|
||||
CustomizableUI.destroyWidget(kWidgetId);
|
||||
});
|
||||
|
||||
add_task(async function asyncCleanup() {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
let syncService = {};
|
||||
Components.utils.import("resource://services-sync/service.js", syncService);
|
||||
const service = syncService.Service;
|
||||
Components.utils.import("resource://services-sync/UIState.jsm");
|
||||
const {UIState} = Components.utils.import("resource://services-sync/UIState.jsm", {});
|
||||
|
||||
let getState;
|
||||
let originalSync;
|
||||
|
|
|
@ -316,12 +316,12 @@ function subviewShown(aSubview) {
|
|||
let timeoutId = win.setTimeout(() => {
|
||||
reject("Subview (" + aSubview.id + ") did not show within 20 seconds.");
|
||||
}, 20000);
|
||||
function onViewShowing(e) {
|
||||
aSubview.removeEventListener("ViewShowing", onViewShowing);
|
||||
function onViewShown(e) {
|
||||
aSubview.removeEventListener("ViewShown", onViewShown);
|
||||
win.clearTimeout(timeoutId);
|
||||
resolve();
|
||||
}
|
||||
aSubview.addEventListener("ViewShowing", onViewShowing);
|
||||
aSubview.addEventListener("ViewShown", onViewShown);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ this.browserAction = class extends ExtensionAPI {
|
|||
view.setAttribute("flex", "1");
|
||||
view.setAttribute("extension", true);
|
||||
|
||||
document.getElementById("PanelUI-multiView").appendChild(view);
|
||||
document.getElementById("appMenu-viewCache").appendChild(view);
|
||||
|
||||
if (this.extension.hasPermission("menus") ||
|
||||
this.extension.hasPermission("contextMenus")) {
|
||||
|
|
|
@ -1078,15 +1078,16 @@ this.UITour = {
|
|||
shouldOpenAppMenu = true;
|
||||
} else if (this.targetIsInPageActionPanel(aTarget)) {
|
||||
shouldOpenPageActionPanel = true;
|
||||
// Ensure the panel visibility so as to ensure the visibility of
|
||||
// the target element inside the panel otherwise
|
||||
// we would be rejected in the below `isElementVisible` checking.
|
||||
// Ensure the panel visibility so as to ensure the visibility of the target
|
||||
// element inside the panel otherwise we would be rejected in the below
|
||||
// `isElementVisible` checking.
|
||||
aChromeWindow.BrowserPageActions.panelNode.hidden = false;
|
||||
}
|
||||
|
||||
// Prevent showing a panel at an undefined position.
|
||||
if (!this.isElementVisible(aTarget.node)) {
|
||||
return Promise.reject(`_ensureTarget: Reject the ${aTarget.name} target since it isn't visible.`);
|
||||
// Prevent showing a panel at an undefined position, but when it's tucked
|
||||
// away inside a panel, we skip this check.
|
||||
if (!aTarget.node.closest("panelview") && !this.isElementVisible(aTarget.node)) {
|
||||
return Promise.reject(`_ensureTarget: Reject the ${aTarget.name || aTarget.targetName} target since it isn't visible.`);
|
||||
}
|
||||
|
||||
let menuToOpen = null;
|
||||
|
|
|
@ -194,10 +194,6 @@
|
|||
transform: translateX(-@menuPanelWidth@);
|
||||
}
|
||||
|
||||
panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
|
||||
display: none;
|
||||
}
|
||||
|
||||
panelview {
|
||||
-moz-box-orient: vertical;
|
||||
-moz-box-flex: 1;
|
||||
|
|
Загрузка…
Ссылка в новой задаче