diff options
Diffstat (limited to 'devtools/server/tests/xpcshell/test_layout-reflows-observer.js')
-rw-r--r-- | devtools/server/tests/xpcshell/test_layout-reflows-observer.js | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/devtools/server/tests/xpcshell/test_layout-reflows-observer.js b/devtools/server/tests/xpcshell/test_layout-reflows-observer.js new file mode 100644 index 0000000000..74f31b97fe --- /dev/null +++ b/devtools/server/tests/xpcshell/test_layout-reflows-observer.js @@ -0,0 +1,311 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the LayoutChangesObserver + +/* eslint-disable mozilla/use-chromeutils-generateqi */ + +var { + getLayoutChangesObserver, + releaseLayoutChangesObserver, + LayoutChangesObserver, +} = require("resource://devtools/server/actors/reflow.js"); +const EventEmitter = require("resource://devtools/shared/event-emitter.js"); + +// Override set/clearTimeout on LayoutChangesObserver to avoid depending on +// time in this unit test. This means that LayoutChangesObserver.eventLoopTimer +// will be the timeout callback instead of the timeout itself, so test cases +// will need to execute it to fake a timeout +LayoutChangesObserver.prototype._setTimeout = cb => cb; +LayoutChangesObserver.prototype._clearTimeout = function () {}; + +// Mock the targetActor since we only really want to test the LayoutChangesObserver +// and don't want to depend on a window object, nor want to test protocol.js +class MockTargetActor extends EventEmitter { + constructor() { + super(); + this.docShell = new MockDocShell(); + this.window = new MockWindow(this.docShell); + this.windows = [this.window]; + this.attached = true; + } + + get chromeEventHandler() { + return this.docShell.chromeEventHandler; + } + + isDestroyed() { + return false; + } +} + +function MockWindow(docShell) { + this.docShell = docShell; +} +MockWindow.prototype = { + QueryInterface() { + const self = this; + return { + getInterface() { + return { + QueryInterface() { + return self.docShell; + }, + }; + }, + }; + }, + setTimeout(cb) { + // Simply return the cb itself so that we can execute it in the test instead + // of depending on a real timeout + return cb; + }, + clearTimeout() {}, +}; + +function MockDocShell() { + this.observer = null; +} +MockDocShell.prototype = { + addWeakReflowObserver(observer) { + this.observer = observer; + }, + removeWeakReflowObserver() {}, + get chromeEventHandler() { + return { + addEventListener: (type, cb) => { + if (type === "resize") { + this.resizeCb = cb; + } + }, + removeEventListener: (type, cb) => { + if (type === "resize" && cb === this.resizeCb) { + this.resizeCb = null; + } + }, + }; + }, + mockResize() { + if (this.resizeCb) { + this.resizeCb(); + } + }, +}; + +function run_test() { + instancesOfObserversAreSharedBetweenWindows(); + eventsAreBatched(); + noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts(); + observerIsAlreadyStarted(); + destroyStopsObserving(); + stoppingAndStartingSeveralTimesWorksCorrectly(); + reflowsArentStackedWhenStopped(); + stackedReflowsAreResetOnStop(); +} + +function instancesOfObserversAreSharedBetweenWindows() { + info( + "Checking that when requesting twice an instances of the observer " + + "for the same WindowGlobalTargetActor, the instance is shared" + ); + + info("Checking 2 instances of the observer for the targetActor 1"); + const targetActor1 = new MockTargetActor(); + const obs11 = getLayoutChangesObserver(targetActor1); + const obs12 = getLayoutChangesObserver(targetActor1); + Assert.equal(obs11, obs12); + + info("Checking 2 instances of the observer for the targetActor 2"); + const targetActor2 = new MockTargetActor(); + const obs21 = getLayoutChangesObserver(targetActor2); + const obs22 = getLayoutChangesObserver(targetActor2); + Assert.equal(obs21, obs22); + + info( + "Checking that observers instances for 2 different targetActors are " + + "different" + ); + Assert.notEqual(obs11, obs21); + + releaseLayoutChangesObserver(targetActor1); + releaseLayoutChangesObserver(targetActor1); + releaseLayoutChangesObserver(targetActor2); + releaseLayoutChangesObserver(targetActor2); +} + +function eventsAreBatched() { + info( + "Checking that reflow events are batched and only sent when the " + + "timeout expires" + ); + + // Note that in this test, we mock the target actor and its window property, so we also + // mock the setTimeout/clearTimeout mechanism and just call the callback manually + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + + const reflowsEvents = []; + const onReflows = reflows => reflowsEvents.push(reflows); + observer.on("reflows", onReflows); + + const resizeEvents = []; + const onResize = () => resizeEvents.push("resize"); + observer.on("resize", onResize); + + info("Fake one reflow event"); + targetActor.window.docShell.observer.reflow(); + info("Checking that no batched reflow event has been emitted"); + Assert.equal(reflowsEvents.length, 0); + + info("Fake another reflow event"); + targetActor.window.docShell.observer.reflow(); + info("Checking that still no batched reflow event has been emitted"); + Assert.equal(reflowsEvents.length, 0); + + info("Fake a few of resize events too"); + targetActor.window.docShell.mockResize(); + targetActor.window.docShell.mockResize(); + targetActor.window.docShell.mockResize(); + info("Checking that still no batched resize event has been emitted"); + Assert.equal(resizeEvents.length, 0); + + info("Faking timeout expiration and checking that events are sent"); + observer.eventLoopTimer(); + Assert.equal(reflowsEvents.length, 1); + Assert.equal(reflowsEvents[0].length, 2); + Assert.equal(resizeEvents.length, 1); + + observer.off("reflows", onReflows); + observer.off("resize", onResize); + releaseLayoutChangesObserver(targetActor); +} + +function noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts() { + info( + "Checking that if no reflows were detected and the event batching " + + "loop expires, then no reflows event is sent" + ); + + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + + const reflowsEvents = []; + const onReflows = reflows => reflowsEvents.push(reflows); + observer.on("reflows", onReflows); + + info("Faking timeout expiration and checking for reflows"); + observer.eventLoopTimer(); + Assert.equal(reflowsEvents.length, 0); + + observer.off("reflows", onReflows); + releaseLayoutChangesObserver(targetActor); +} + +function observerIsAlreadyStarted() { + info("Checking that the observer is already started when getting it"); + + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + Assert.ok(observer.isObserving); + + observer.stop(); + Assert.ok(!observer.isObserving); + + observer.start(); + Assert.ok(observer.isObserving); + + releaseLayoutChangesObserver(targetActor); +} + +function destroyStopsObserving() { + info("Checking that the destroying the observer stops it"); + + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + Assert.ok(observer.isObserving); + + observer.destroy(); + Assert.ok(!observer.isObserving); + + releaseLayoutChangesObserver(targetActor); +} + +function stoppingAndStartingSeveralTimesWorksCorrectly() { + info( + "Checking that the stopping and starting several times the observer" + + " works correctly" + ); + + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + + Assert.ok(observer.isObserving); + observer.start(); + observer.start(); + observer.start(); + Assert.ok(observer.isObserving); + + observer.stop(); + Assert.ok(!observer.isObserving); + + observer.stop(); + observer.stop(); + Assert.ok(!observer.isObserving); + + releaseLayoutChangesObserver(targetActor); +} + +function reflowsArentStackedWhenStopped() { + info("Checking that when stopped, reflows aren't stacked in the observer"); + + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + + info("Stoping the observer"); + observer.stop(); + + info("Faking reflows"); + targetActor.window.docShell.observer.reflow(); + targetActor.window.docShell.observer.reflow(); + targetActor.window.docShell.observer.reflow(); + + info("Checking that reflows aren't recorded"); + Assert.equal(observer.reflows.length, 0); + + info("Starting the observer and faking more reflows"); + observer.start(); + targetActor.window.docShell.observer.reflow(); + targetActor.window.docShell.observer.reflow(); + targetActor.window.docShell.observer.reflow(); + + info("Checking that reflows are recorded"); + Assert.equal(observer.reflows.length, 3); + + releaseLayoutChangesObserver(targetActor); +} + +function stackedReflowsAreResetOnStop() { + info("Checking that stacked reflows are reset on stop"); + + const targetActor = new MockTargetActor(); + const observer = getLayoutChangesObserver(targetActor); + + targetActor.window.docShell.observer.reflow(); + Assert.equal(observer.reflows.length, 1); + + observer.stop(); + Assert.equal(observer.reflows.length, 0); + + targetActor.window.docShell.observer.reflow(); + Assert.equal(observer.reflows.length, 0); + + observer.start(); + Assert.equal(observer.reflows.length, 0); + + targetActor.window.docShell.observer.reflow(); + Assert.equal(observer.reflows.length, 1); + + releaseLayoutChangesObserver(targetActor); +} |