311 lines
8.9 KiB
JavaScript
311 lines
8.9 KiB
JavaScript
/* 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);
|
|
}
|