summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/xpcshell/test_layout-reflows-observer.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/xpcshell/test_layout-reflows-observer.js')
-rw-r--r--devtools/server/tests/xpcshell/test_layout-reflows-observer.js307
1 files changed, 307 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..7f6d1e9004
--- /dev/null
+++ b/devtools/server/tests/xpcshell/test_layout-reflows-observer.js
@@ -0,0 +1,307 @@
+/* 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("devtools/server/actors/reflow");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+// 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;
+ }
+}
+
+function MockWindow(docShell) {
+ this.docShell = docShell;
+}
+MockWindow.prototype = {
+ QueryInterface: function() {
+ const self = this;
+ return {
+ getInterface: function() {
+ return {
+ QueryInterface: function() {
+ return self.docShell;
+ },
+ };
+ },
+ };
+ },
+ setTimeout: function(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() {},
+};
+
+function MockDocShell() {
+ this.observer = null;
+}
+MockDocShell.prototype = {
+ addWeakReflowObserver: function(observer) {
+ this.observer = observer;
+ },
+ removeWeakReflowObserver: function() {},
+ 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: function() {
+ 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 BrowsingContextTargetActor, 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);
+}