Bug 1447619 - avoid reflowing when returning items to the toolbar from the overflow menu, r=florian

MozReview-Commit-ID: BFGRssWb9F

--HG--
extra : rebase_source : e8932810584e10a1de0c1b11c1a152d2dc0f48dd
This commit is contained in:
Gijs Kruitbosch 2018-04-20 15:09:44 +01:00
Родитель 81b1272512
Коммит 39d0e25c38
4 изменённых файлов: 88 добавлений и 41 удалений

Просмотреть файл

@ -13,27 +13,9 @@
* for tips on how to do that. * for tips on how to do that.
*/ */
const EXPECTED_REFLOWS = [ const EXPECTED_REFLOWS = [
{ /**
stack: [ * Nothing here! Please don't add anything new!
"onOverflow@resource:///modules/CustomizableUI.jsm", */
],
maxCount: 48,
},
{
stack: [
"_moveItemsBackToTheirOrigin@resource:///modules/CustomizableUI.jsm",
"_onLazyResize@resource:///modules/CustomizableUI.jsm",
],
maxCount: 5,
},
{
stack: [
"_onLazyResize@resource:///modules/CustomizableUI.jsm",
],
maxCount: 4,
},
]; ];
const gToolbar = document.getElementById("PersonalToolbar"); const gToolbar = document.getElementById("PersonalToolbar");

Просмотреть файл

@ -4408,18 +4408,36 @@ OverflowableToolbar.prototype = {
} }
}, },
onOverflow(aEvent) { /**
// The rangeParent check is here because of bug 1111986 and ensuring that * Avoid re-entrancy in the overflow handling by keeping track of invocations:
// overflow events from the bookmarks toolbar items or similar things that */
// manage their own overflow don't trigger an overflow on the entire toolbar _lastOverflowCounter: 0,
if (!this._enabled ||
(aEvent && aEvent.target != this._toolbar.customizationTarget) || /**
(aEvent && aEvent.rangeParent)) * Handle overflow in the toolbar by moving items to the overflow menu.
* @param {Event} aEvent
* The overflow event that triggered handling overflow. May be omitted
* in some cases (e.g. when we run this method after overflow handling
* is re-enabled from customize mode, to ensure correct handling of
* initial overflow).
*/
async onOverflow(aEvent) {
if (!this._enabled)
return; return;
let child = this._target.lastChild; let child = this._target.lastChild;
while (child && this._target.scrollLeftMin != this._target.scrollLeftMax) { let thisOverflowResponse = ++this._lastOverflowCounter;
let win = this._target.ownerGlobal;
let [scrollLeftMin, scrollLeftMax] = await win.promiseDocumentFlushed(() => {
return [this._target.scrollLeftMin, this._target.scrollLeftMax];
});
if (win.closed || this._lastOverflowCounter != thisOverflowResponse) {
return;
}
while (child && scrollLeftMin != scrollLeftMax) {
let prevChild = child.previousSibling; let prevChild = child.previousSibling;
if (child.getAttribute("overflows") != "false") { if (child.getAttribute("overflows") != "false") {
@ -4438,13 +4456,26 @@ OverflowableToolbar.prototype = {
} }
} }
child = prevChild; child = prevChild;
[scrollLeftMin, scrollLeftMax] = await win.promiseDocumentFlushed(() => {
return [this._target.scrollLeftMin, this._target.scrollLeftMax];
});
// If the window has closed or if we re-enter because we were waiting
// for layout, stop.
if (win.closed || this._lastOverflowCounter != thisOverflowResponse) {
return;
}
} }
let win = this._target.ownerGlobal;
win.UpdateUrlbarSearchSplitterState(); win.UpdateUrlbarSearchSplitterState();
// Reset the counter because we finished handling overflow.
this._lastOverflowCounter = 0;
}, },
_onResize(aEvent) { _onResize(aEvent) {
// Ignore bubbled-up resize events.
if (aEvent.target != aEvent.target.ownerGlobal.top) {
return;
}
if (!this._lazyResizeHandler) { if (!this._lazyResizeHandler) {
this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this), this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
LAZY_RESIZE_INTERVAL_MS, 0); LAZY_RESIZE_INTERVAL_MS, 0);
@ -4452,16 +4483,33 @@ OverflowableToolbar.prototype = {
this._lazyResizeHandler.arm(); this._lazyResizeHandler.arm();
}, },
_moveItemsBackToTheirOrigin(shouldMoveAllItems) { /**
* Try to move toolbar items back to the toolbar from the overflow menu.
* @param {boolean} shouldMoveAllItems
* Whether we should move everything (e.g. because we're being disabled)
* @param {number} targetWidth
* Optional; the width of the toolbar in which we can put things.
* Some consumers pass this to avoid reflows.
* While there are items in the list, this width won't change, and so
* we can avoid flushing layout by providing it and/or caching it.
* Note that if `shouldMoveAllItems` is true, we never need the width
* anyway.
*/
_moveItemsBackToTheirOrigin(shouldMoveAllItems, targetWidth) {
let placements = gPlacements.get(this._toolbar.id); let placements = gPlacements.get(this._toolbar.id);
let win = this._target.ownerGlobal;
while (this._list.firstChild) { while (this._list.firstChild) {
let child = this._list.firstChild; let child = this._list.firstChild;
let minSize = this._collapsed.get(child.id); let minSize = this._collapsed.get(child.id);
if (!shouldMoveAllItems && if (!shouldMoveAllItems && minSize) {
minSize && if (!targetWidth) {
this._target.clientWidth <= minSize) { let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
break; targetWidth = Math.floor(dwu.getBoundsWithoutFlushing(this._target).width);
}
if (targetWidth <= minSize) {
break;
}
} }
this._collapsed.delete(child.id); this._collapsed.delete(child.id);
@ -4493,7 +4541,6 @@ OverflowableToolbar.prototype = {
CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target); CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
} }
let win = this._target.ownerGlobal;
win.UpdateUrlbarSearchSplitterState(); win.UpdateUrlbarSearchSplitterState();
let collapsedWidgetIds = Array.from(this._collapsed.keys()); let collapsedWidgetIds = Array.from(this._collapsed.keys());
@ -4506,14 +4553,21 @@ OverflowableToolbar.prototype = {
} }
}, },
_onLazyResize() { async _onLazyResize() {
if (!this._enabled) if (!this._enabled)
return; return;
if (this._target.scrollLeftMin != this._target.scrollLeftMax) { let win = this._target.ownerGlobal;
let [min, max, targetWidth] = await win.promiseDocumentFlushed(() => {
return [this._target.scrollLeftMin, this._target.scrollLeftMax, this._target.clientWidth];
});
if (win.closed) {
return;
}
if (min != max) {
this.onOverflow(); this.onOverflow();
} else { } else {
this._moveItemsBackToTheirOrigin(); this._moveItemsBackToTheirOrigin(false, targetWidth);
} }
}, },
@ -4608,7 +4662,7 @@ OverflowableToolbar.prototype = {
} else { } else {
// If it's now the first item in the overflow list, // If it's now the first item in the overflow list,
// maybe we can return it: // maybe we can return it:
this._moveItemsBackToTheirOrigin(); this._moveItemsBackToTheirOrigin(false);
} }
}, },

Просмотреть файл

@ -226,7 +226,14 @@ add_task(async function() {
let originalWindowWidth = window.outerWidth; let originalWindowWidth = window.outerWidth;
window.resizeTo(kForceOverflowWidthPx, window.outerHeight); window.resizeTo(kForceOverflowWidthPx, window.outerHeight);
await waitForCondition(() => navbar.hasAttribute("overflowing")); // Wait for all the widgets to overflow. We can't just wait for the
// `overflowing` attribute because we leave time for layout flushes
// inbetween, so it's possible for the timeout to run before the
// navbar has "settled"
await waitForCondition(() => {
return navbar.hasAttribute("overflowing") &&
navbar.customizationTarget.lastChild.getAttribute("overflows") == "false";
});
// Find last widget that doesn't allow overflowing // Find last widget that doesn't allow overflowing
let nonOverflowing = navbar.customizationTarget.lastChild; let nonOverflowing = navbar.customizationTarget.lastChild;

Просмотреть файл

@ -1172,11 +1172,15 @@ PlacesToolbar.prototype = {
case "overflow": case "overflow":
if (!this._isOverflowStateEventRelevant(aEvent)) if (!this._isOverflowStateEventRelevant(aEvent))
return; return;
// Avoid triggering overflow in containers if possible
aEvent.stopPropagation();
this._onOverflow(); this._onOverflow();
break; break;
case "underflow": case "underflow":
if (!this._isOverflowStateEventRelevant(aEvent)) if (!this._isOverflowStateEventRelevant(aEvent))
return; return;
// Avoid triggering underflow in containers if possible
aEvent.stopPropagation();
this._onUnderflow(); this._onUnderflow();
break; break;
case "TabOpen": case "TabOpen":