Bug 1358712 - Get rid of synchronous layout flushes when calculating where to put the StatusPanel. r=Felipe

MozReview-Commit-ID: KHagFdaRAzF

--HG--
extra : rebase_source : 4c79b7f23cc8980179ab0437eab13e7b03fd60c1
This commit is contained in:
Mike Conley 2018-03-07 17:39:20 -05:00
Родитель 0c2106dafb
Коммит 0a0c4cc456
2 изменённых файлов: 116 добавлений и 30 удалений

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

@ -7815,19 +7815,64 @@ var MousePosTracker = {
_listeners: new Set(),
_x: 0,
_y: 0,
_mostRecentEvent: null,
get _windowUtils() {
delete this._windowUtils;
return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
},
/**
* Registers a listener, and then waits for the next refresh
* driver tick before running the listener to see if the
* mouse is within the listener's target rect.
*
* @param listener (object)
* A listener is expected to expose the following properties:
*
* getMouseTargetRect (function)
* Returns the rect that the MousePosTracker needs to alert
* the listener about if the mouse happens to be within it.
*
* onTrackingStarted (function, optional)
* Called after the next refresh driver tick after listening,
* when the mouse's initial position relative to the MouseTargetRect
* can be computed. If the listener is removed before the refresh
* driver tick, this might never be called.
*
* onMouseEnter (function, optional)
* The function to be called if the mouse enters the rect
* returned by getMouseTargetRect. MousePosTracker always
* runs this inside of a requestAnimationFrame, since it
* assumes that the notification is used to update the DOM.
*
* onMouseLeave (function, optional)
* The function to be called if the mouse exits the rect
* returned by getMouseTargetRect. MousePosTracker always
* runs this inside of a requestAnimationFrame, since it
* assumes that the notification is used to update the DOM.
*/
addListener(listener) {
if (this._listeners.has(listener))
if (this._listeners.has(listener)) {
return;
}
listener._hover = false;
this._listeners.add(listener);
this._callListener(listener);
// We're adding some asynchronicity here, during which the listener
// might be removed. At each step, we need to ensure that the listener
// is still registered before proceeding.
window.promiseDocumentFlushed(() => {
if (this._listeners.has(listener)) {
this._callListeners([listener]);
window.requestAnimationFrame(() => {
if (this._listeners.has(listener) && listener.onTrackingStarted) {
listener.onTrackingStarted();
}
});
}
});
},
removeListener(listener) {
@ -7835,38 +7880,73 @@ var MousePosTracker = {
},
handleEvent(event) {
var fullZoom = this._windowUtils.fullZoom;
this._x = event.screenX / fullZoom - window.mozInnerScreenX;
this._y = event.screenY / fullZoom - window.mozInnerScreenY;
let firstEvent = !this._mostRecentEvent;
this._mostRecentEvent = event;
this._listeners.forEach(function(listener) {
try {
this._callListener(listener);
} catch (e) {
Cu.reportError(e);
}
}, this);
if (firstEvent) {
window.promiseDocumentFlushed(() => {
this.onDocumentFlushed();
this._mostRecentEvent = null;
});
}
},
_callListener(listener) {
let rect = listener.getMouseTargetRect();
let hover = this._x >= rect.left &&
this._x <= rect.right &&
this._y >= rect.top &&
this._y <= rect.bottom;
onDocumentFlushed() {
let event = this._mostRecentEvent;
if (hover == listener._hover)
return;
if (event) {
let fullZoom = this._windowUtils.fullZoom;
this._x = event.screenX / fullZoom - window.mozInnerScreenX;
this._y = event.screenY / fullZoom - window.mozInnerScreenY;
listener._hover = hover;
if (hover) {
if (listener.onMouseEnter)
listener.onMouseEnter();
} else if (listener.onMouseLeave) {
listener.onMouseLeave();
this._callListeners(this._listeners);
}
}
},
_callListeners(listeners) {
let functionsToCall = [];
for (let listener of listeners) {
let rect;
try {
rect = listener.getMouseTargetRect();
} catch (e) {
Cu.reportError(e);
continue;
}
let hover = this._x >= rect.left &&
this._x <= rect.right &&
this._y >= rect.top &&
this._y <= rect.bottom;
if (hover == listener._hover) {
continue;
}
listener._hover = hover;
if (hover) {
if (listener.onMouseEnter) {
functionsToCall.push(listener.onMouseEnter.bind(listener));
}
} else if (listener.onMouseLeave) {
functionsToCall.push(listener.onMouseLeave.bind(listener));
}
}
// _callListeners is being called from within a promiseDocumentFlushed,
// where we are expressly forbidden from dirtying styles or layout. Since
// the onMouseEnter or onMouseLeave functions are liable to do such
// dirtying, we run them inside a requestAnimationFrame callback instead.
window.requestAnimationFrame(() => {
for (let fn of functionsToCall) {
try {
fn();
} catch (e) {
Cu.reportError(e);
}
}
});
},
};
var ToolbarIconColor = {

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

@ -4642,10 +4642,12 @@ var StatusPanel = {
}
if (val) {
this._labelElement.value = val;
this.panel.removeAttribute("inactive");
this._mouseTargetRect = null;
this._labelElement.value = val;
MousePosTracker.addListener(this);
// The inactive state for the panel will be removed in onTrackingStarted,
// once the initial position of the mouse relative to the StatusPanel
// is figured out (to avoid both flicker and sync flushing).
} else {
this.panel.setAttribute("inactive", "true");
MousePosTracker.removeListener(this);
@ -4654,6 +4656,10 @@ var StatusPanel = {
return val;
},
onTrackingStarted() {
this.panel.removeAttribute("inactive");
},
getMouseTargetRect() {
if (!this._mouseTargetRect) {
this._calcMouseTargetRect();