Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: devtools/server/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"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);
}