Bug 1368208 - Don't flush layout to determine whether scrollbox scroll buttons should be enabled/disabled. r=mconley

MozReview-Commit-ID: 9BA3u2gOawu

--HG--
extra : rebase_source : 5a5f50f643f2911c6a290413ddfeff9fb6d053d9
This commit is contained in:
Dão Gottwald 2017-05-30 12:07:49 +02:00
Родитель f7c6cee7a6
Коммит f7cc68b14a
1 изменённых файлов: 107 добавлений и 63 удалений

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

@ -185,14 +185,18 @@
return innerRect;
]]></getter>
</property>
<property name="scrollboxPaddingStart" readonly="true">
<getter><![CDATA[
var ltr = (window.getComputedStyle(this).direction == "ltr");
var paddingStartName = ltr ? "padding-left" : "padding-right";
var scrollboxStyle = window.getComputedStyle(this._scrollbox);
return parseFloat(scrollboxStyle.getPropertyValue(paddingStartName));
]]></getter>
</property>
<field name="scrollboxPaddingStart"><![CDATA[
parseFloat(window.getComputedStyle(this._scrollbox)[
this.orient == "vertical" ? "paddingTop" :
(this._isRTLScrollbox ? "paddingRight" : "paddingLeft")
]);
]]></field>
<field name="scrollboxPaddingEnd"><![CDATA[{
parseFloat(window.getComputedStyle(this._scrollbox)[
this.orient == "vertical" ? "paddingBottom" :
(this._isRTLScrollbox ? "paddingLeft" : "paddingRight")
]);
}]]></field>
<property name="scrollPosition">
<getter><![CDATA[
return this.orient == "vertical" ?
@ -208,12 +212,10 @@
]]></setter>
</property>
<property name="_startEndProps" readonly="true">
<getter><![CDATA[
return this.orient == "vertical" ?
["top", "bottom"] : ["left", "right"];
]]></getter>
</property>
<field name="_startEndProps"><![CDATA[
this.orient == "vertical" ?
["top", "bottom"] : ["left", "right"];
]]></field>
<field name="_isRTLScrollbox"><![CDATA[
this.orient != "vertical" &&
@ -222,10 +224,39 @@
<field name="_scrollTarget">null</field>
<method name="_boundsWithoutFlushing">
<parameter name="element"/>
<body><![CDATA[
if (!("_DOMWindowUtils" in this)) {
try {
this._DOMWindowUtils =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
} catch (e) {
// Can't access nsIDOMWindowUtils if we're unprivileged.
this._DOMWindowUtils = null;
}
}
return this._DOMWindowUtils ?
this._DOMWindowUtils.getBoundsWithoutFlushing(element) :
element.getBoundingClientRect();
]]></body>
</method>
<method name="_canScrollToElement">
<parameter name="element"/>
<body><![CDATA[
return window.getComputedStyle(element).display != "none";
if (element.hidden) {
return false;
}
// See if the element is hidden via CSS without the hidden attribute.
// If we get only zeros for the client rect, this means the element
// is hidden. As a performance optimization, we don't flush layout
// here which means that on the fly changes aren't fully supported.
let rect = this._boundsWithoutFlushing(element);
return !!(rect.top || rect.left || rect.width || rect.height);
]]></body>
</method>
@ -611,40 +642,73 @@
]]></body>
</method>
<field name="_scrollButtonUpdatePending">false</field>
<method name="_updateScrollButtonsDisabledState">
<parameter name="aScrolling"/>
<body><![CDATA[
let scrolledToStart;
let scrolledToEnd;
if (this._scrollButtonUpdatePending) {
return;
}
this._scrollButtonUpdatePending = true;
// Avoid flushing layout when not overflowing or when scrolling.
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
} else if (aScrolling) {
scrolledToStart = false;
scrolledToEnd = false;
} else if (this.scrollPosition == 0) {
// In the RTL case, this means the _last_ element in the
// scrollbox is visible
scrolledToEnd = this._isRTLScrollbox;
scrolledToStart = !this._isRTLScrollbox;
} else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
// In the RTL case, this means the _first_ element in the
// scrollbox is visible
scrolledToStart = this._isRTLScrollbox;
scrolledToEnd = !this._isRTLScrollbox;
// Invalidate layout lazily before painting the next frame.
window.requestAnimationFrame(() => {
this._scrollButtonUpdatePending = false;
this.setAttribute("scrolledtoend", "true");
this.setAttribute("scrolledtostart", "true");
});
return;
}
if (scrolledToEnd)
this.setAttribute("scrolledtoend", "true");
else
this.removeAttribute("scrolledtoend");
// Wait until after the next paint to get current layout data from
// getBoundsWithoutFlushing.
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
this._scrollButtonUpdatePending = false;
if (scrolledToStart)
this.setAttribute("scrolledtostart", "true");
else
this.removeAttribute("scrolledtostart");
let scrolledToStart = false;
let scrolledToEnd = false;
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
} else {
let scrollboxPaddingStart = Math.round(this.scrollboxPaddingStart);
let scrollboxPaddingEnd = Math.round(this.scrollboxPaddingEnd);
let [start, end] = this._startEndProps;
if (this._isRTLScrollbox) {
[start, end] = [end, start];
scrollboxPaddingStart = -scrollboxPaddingStart;
scrollboxPaddingEnd = -scrollboxPaddingEnd;
}
let scrollboxRect = this._boundsWithoutFlushing(this._scrollbox);
let elements = this._getScrollableElements();
let [firstElement, lastElement] = [elements[0], elements[elements.length - 1]];
if (firstElement &&
this._boundsWithoutFlushing(firstElement)[start] - scrollboxPaddingStart == scrollboxRect[start]) {
scrolledToStart = true;
} else if (lastElement &&
this._boundsWithoutFlushing(lastElement)[end] + scrollboxPaddingEnd == scrollboxRect[end]) {
scrolledToEnd = true;
}
}
if (scrolledToEnd) {
this.setAttribute("scrolledtoend", "true");
} else {
this.removeAttribute("scrolledtoend");
}
if (scrolledToStart) {
this.setAttribute("scrolledtostart", "true");
} else {
this.removeAttribute("scrolledtostart");
}
});
});
]]></body>
</method>
</implementation>
@ -797,27 +861,7 @@
]]></handler>
<handler event="scroll"><![CDATA[
if (!this._delayedUpdateScrollButtonsTimer) {
// This is the beginning of a scrolling animation. We need to update
// scroll buttons now in case we were scrolled to the start or to the
// end before we started scrolling.
this._updateScrollButtonsDisabledState(true);
} else {
// We're in the middle of the scrolling animation. We'll restart the
// delayed update request so that we only update the scroll buttons
// a second time once we're done scrolling.
window.clearTimeout(this._delayedUpdateScrollButtonsTimer);
}
// Try to detect the end of the scrolling animation to update the
// scroll buttons again. To avoid false positives, this timeout needs
// to be big enough to account for intermediate frames that don't move
// the scroll position in case we're scrolling slowly.
this._delayedUpdateScrollButtonsTimer = setTimeout(() => {
// Scrolling animation has finished.
this._delayedUpdateScrollButtonsTimer = 0;
this._updateScrollButtonsDisabledState();
}, 200);
this._updateScrollButtonsDisabledState();
]]></handler>
</handlers>
</binding>