/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/** * About the types of objects in this file: * * - ReflowActor: the actor class used for protocol purposes. * Mostly empty, just gets an instance of LayoutChangesObserver and forwards * its "reflows" events to clients. * * - LayoutChangesObserver: extends Observable and uses the ReflowObserver, to * track reflows on the page. * Used by the LayoutActor, but is also exported on the module, so can be used * by any other actor that needs it. * * - Observable: A utility parent class, meant at being extended by classes that * need a to observe something on the targetActor's windows. * * - Dedicated observers: There's only one of them for now: ReflowObserver which * listens to reflow events via the docshell, * These dedicated classes are used by the LayoutChangesObserver.
*/
/** * The reflow actor tracks reflows and emits events about them.
*/
exports.ReflowActor = class ReflowActor extends Actor {
constructor(conn, targetActor) { super(conn, reflowSpec);
/** * Start tracking reflows and sending events to clients about them. * This is a oneway method, do not expect a response and it won't return a * promise.
*/
start() { if (!this._isStarted) { this.observer.on("reflows", this._onReflow); this._isStarted = true;
}
}
/** * Stop tracking reflows and sending events to clients about them. * This is a oneway method, do not expect a response and it won't return a * promise.
*/
stop() { if (this._isStarted) { this.observer.off("reflows", this._onReflow); this._isStarted = false;
}
}
_onReflow(reflows) { if (this._isStarted) { this.emit("reflows", reflows);
}
}
};
/** * Base class for all sorts of observers that need to listen to events on the * targetActor's windows. * @param {WindowGlobalTargetActor} targetActor * @param {Function} callback Executed everytime the observer observes something
*/ class Observable {
constructor(targetActor, callback) { this.targetActor = targetActor; this.callback = callback;
if (!this.targetActor.isDestroyed() && this.targetActor.docShell) { // It's only worth stopping if the targetActor is still active this._stopListeners(this.targetActor.windows);
}
}
_onWindowReady({ window }) { if (this.isObserving) { this._startListeners([window]);
}
}
_onWindowDestroyed({ window }) { if (this.isObserving) { this._stopListeners([window]);
}
}
_startListeners() { // To be implemented by sub-classes.
}
_stopListeners() { // To be implemented by sub-classes.
}
/** * To be called by sub-classes when something has been observed
*/
notifyCallback(...args) { this.isObserving && this.callback && this.callback.apply(null, args);
}
}
/** * The LayouChangesObserver will observe reflows as soon as it is started. * Some devtools actors may cause reflows and it may be wanted to "hide" these * reflows from the LayouChangesObserver consumers. * If this is the case, such actors should require this module and use this * global function to turn the ignore mode on and off temporarily. * * Note that if a node is provided, it will be used to force a sync reflow to * make sure all reflows which occurred before switching the mode on or off are * either observed or ignored depending on the current mode. * * @param {Boolean} ignore * @param {DOMNode} syncReflowNode The node to use to force a sync reflow
*/ var gIgnoreLayoutChanges = false;
exports.setIgnoreLayoutChanges = function (ignore, syncReflowNode) { if (syncReflowNode) {
let forceSyncReflow = syncReflowNode.offsetWidth; // eslint-disable-line
}
gIgnoreLayoutChanges = ignore;
};
class LayoutChangesObserver extends EventEmitter { /** * The LayoutChangesObserver class is instantiated only once per given tab * and is used to track reflows and dom and style changes in that tab. * The LayoutActor uses this class to send reflow events to its clients. * * This class isn't exported on the module because it shouldn't be instantiated * to avoid creating several instances per tabs. * Use `getLayoutChangesObserver(targetActor)` * and `releaseLayoutChangesObserver(targetActor)` * which are exported to get and release instances. * * The observer loops every EVENT_BATCHING_DELAY ms and checks if layout changes * have happened since the last loop iteration. If there are, it sends the * corresponding events: * * - "reflows", with an array of all the reflows that occured, * - "resizes", with an array of all the resizes that occured, * * @param {WindowGlobalTargetActor} targetActor
*/
constructor(targetActor) { super();
// Creating the various observers we're going to need // For now, just the reflow observer, but later we can add markupMutation, // styleSheetChanges and styleRuleChanges this.reflowObserver = new ReflowObserver(this.targetActor, this._onReflow); this.resizeObserver = new WindowResizeObserver( this.targetActor, this._onResize
);
}
/** * How long does this observer waits before emitting batched events. * The lower the value, the more event packets will be sent to clients, * potentially impacting performance. * The higher the value, the more time we'll wait, this is better for * performance but has an effect on how soon changes are shown in the toolbox.
*/
EVENT_BATCHING_DELAY = 300;
/** * Destroying this instance of LayoutChangesObserver will stop the batched * events from being sent.
*/
destroy() { this.isObserving = false;
/** * Start the event loop, which regularly checks if there are any observer * events to be sent as batched events * Calls itself in a loop.
*/
_startEventLoop() { // Avoid emitting events if the targetActor has been detached (may happen // during shutdown) if (!this.targetActor || this.targetActor.isDestroyed()) { return;
}
// Send any reflows we have if (this.reflows && this.reflows.length) { this.emit("reflows", this.reflows); this.reflows = [];
}
// Send any resizes we have if (this.hasResized) { this.emit("resize"); this.hasResized = false;
}
// Exposing set/clearTimeout here to let tests override them if needed
_setTimeout(cb, ms) { return setTimeout(cb, ms);
}
_clearTimeout(t) { return clearTimeout(t);
}
/** * Executed whenever a reflow is observed. Only stacks the reflow in the * reflows array. * The EVENT_BATCHING_DELAY loop will take care of it later. * @param {Number} start When the reflow started * @param {Number} end When the reflow ended * @param {Boolean} isInterruptible
*/
_onReflow(start, end, isInterruptible) { if (gIgnoreLayoutChanges) { return;
}
// XXX: when/if bug 997092 gets fixed, we will be able to know which // elements have been reflowed, which would be a nice thing to add here. this.reflows.push({
start,
end,
isInterruptible,
});
}
/** * Executed whenever a resize is observed. Only store a flag saying that a * resize occured. * The EVENT_BATCHING_DELAY loop will take care of it later.
*/
_onResize() { if (gIgnoreLayoutChanges) { return;
}
/** * Get a LayoutChangesObserver instance for a given window. This function makes * sure there is only one instance per window. * @param {WindowGlobalTargetActor} targetActor * @return {LayoutChangesObserver}
*/ var observedWindows = new Map(); function getLayoutChangesObserver(targetActor) { const observerData = observedWindows.get(targetActor); if (observerData) {
observerData.refCounting++; return observerData.observer;
}
const obs = new LayoutChangesObserver(targetActor);
observedWindows.set(targetActor, {
observer: obs, // counting references allows to stop the observer when no targetActor owns an // instance.
refCounting: 1,
});
obs.start(); return obs;
}
exports.getLayoutChangesObserver = getLayoutChangesObserver;
/** * Release a LayoutChangesObserver instance that was retrieved by * getLayoutChangesObserver. This is required to ensure the targetActor reference * is removed and the observer is eventually stopped and destroyed. * @param {WindowGlobalTargetActor} targetActor
*/ function releaseLayoutChangesObserver(targetActor) { const observerData = observedWindows.get(targetActor); if (!observerData) { return;
}
/** * Reports any reflow that occurs in the targetActor's docshells. * @extends Observable * @param {WindowGlobalTargetActor} targetActor * @param {Function} callback Executed everytime a reflow occurs
*/ class ReflowObserver extends Observable {
constructor(targetActor, callback) { super(targetActor, callback);
}
_startListeners(windows) { for (const window of windows) {
window.docShell.addWeakReflowObserver(this);
}
}
_stopListeners(windows) { for (const window of windows) { try {
window.docShell.removeWeakReflowObserver(this);
} catch (e) { // Corner cases where a global has already been freed may happen, in // which case, no need to remove the observer.
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.