/* 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/. */
const { setTimeout } = ChromeUtils.importESModule(
"resource://gre/modules/Timer.sys.mjs"
);
const { AnimationFramePromise, Deferred, EventPromise, PollPromise } =
ChromeUtils.importESModule(
"chrome://remote/content/shared/Sync.sys.mjs");
const { Log } = ChromeUtils.importESModule(
"resource://gre/modules/Log.sys.mjs"
);
/**
* Mimic a DOM node for listening for events.
*/
class MockElement {
constructor() {
this.capture =
false;
this.eventName =
null;
this.func =
null;
this.mozSystemGroup =
false;
this.wantUntrusted =
false;
this.untrusted =
false;
}
addEventListener(name, func, options = {}) {
const { capture, mozSystemGroup, wantUntrusted } = options;
this.eventName = name;
this.func = func;
this.capture = capture ??
false;
this.mozSystemGroup = mozSystemGroup ??
false;
this.wantUntrusted = wantUntrusted ??
false;
}
click() {
if (
this.func) {
const event = {
capture:
this.capture,
mozSystemGroup:
this.mozSystemGroup,
target:
this,
type:
this.eventName,
untrusted:
this.untrusted,
wantUntrusted:
this.wantUntrusted,
};
this.func(event);
}
}
dispatchEvent() {
if (
this.wantUntrusted) {
this.untrusted =
true;
}
this.click();
}
removeEventListener() {
this.capture =
false;
this.eventName =
null;
this.func =
null;
this.mozSystemGroup =
false;
this.untrusted =
false;
this.wantUntrusted =
false;
}
}
class MockAppender
extends Log.Appender {
constructor(formatter) {
super(formatter);
this.messages = [];
}
append(message) {
this.doAppend(message);
}
doAppend(message) {
this.messages.push(message);
}
}
add_task(async
function test_AnimationFramePromise() {
let called =
false;
let win = {
addEventListener(_event, _listener) {},
requestAnimationFrame(callback) {
called =
true;
callback();
},
};
await AnimationFramePromise(win);
ok(called);
});
add_task(async
function test_AnimationFramePromiseAbortOnPageHide() {
let resolvePageHideEvent;
const mockWindow = {
addEventListener(event, listener) {
if (event ===
"pagehide") {
resolvePageHideEvent = listener;
}
},
removeEventListener() {},
requestAnimationFrame(callback) {
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => callback(), 10000);
},
};
const trackedPromise = trackPromise(AnimationFramePromise(mockWindow));
ok(trackedPromise.isPending(),
"AnimationFramePromise is pending");
// Simulate "pagehide" event.
resolvePageHideEvent({});
await trackedPromise;
});
add_task(async
function test_DeferredPending() {
const deferred = Deferred();
ok(deferred.pending);
deferred.resolve();
await deferred.promise;
ok(!deferred.pending);
});
add_task(async
function test_DeferredRejected() {
const deferred = Deferred();
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => deferred.reject(
new Error(
"foo")), 100);
try {
await deferred.promise;
ok(
false);
}
catch (e) {
ok(!deferred.pending);
ok(!deferred.fulfilled);
ok(deferred.rejected);
equal(e.message,
"foo");
}
});
add_task(async
function test_DeferredResolved() {
const deferred = Deferred();
ok(deferred.pending);
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => deferred.resolve(
"foo"), 100);
const result = await deferred.promise;
ok(!deferred.pending);
ok(deferred.fulfilled);
ok(!deferred.rejected);
equal(result,
"foo");
});
add_task(async
function test_EventPromise_subjectTypes() {
for (
const subject of [
"foo", 42,
null, undefined,
true, [], {}]) {
Assert.
throws(() =>
new EventPromise(subject,
"click"), /TypeError/);
}
});
add_task(async
function test_EventPromise_eventNameTypes() {
const element =
new MockElement();
for (
const eventName of [42,
null, undefined,
true, [], {}]) {
Assert.
throws(() =>
new EventPromise(element, eventName), /TypeError/);
}
});
add_task(async
function test_EventPromise_subjectAndEventNameEvent() {
const element =
new MockElement();
const clicked =
new EventPromise(element,
"click");
element.click();
const event = await clicked;
equal(element, event.target);
});
add_task(async
function test_EventPromise_captureTypes() {
const element =
new MockElement();
for (
const capture of [
null,
"foo", 42, [], {}]) {
Assert.
throws(
() =>
new EventPromise(element,
"click", { capture }),
/TypeError/
);
}
});
add_task(async
function test_EventPromise_captureEvent() {
const element =
new MockElement();
for (
const capture of [undefined,
false,
true]) {
const expectedCapture = capture ??
false;
const clicked =
new EventPromise(element,
"click", { capture });
element.click();
const event = await clicked;
equal(element, event.target);
equal(expectedCapture, event.capture);
}
});
add_task(async
function test_EventPromise_checkFnTypes() {
const element =
new MockElement();
for (
const checkFn of [
"foo", 42,
true, [], {}]) {
Assert.
throws(
() =>
new EventPromise(element,
"click", { checkFn }),
/TypeError/
);
}
});
add_task(async
function test_EventPromise_checkFnCallback() {
const element =
new MockElement();
let count;
const data = [
{ checkFn:
null, expected_count: 0 },
{ checkFn: undefined, expected_count: 0 },
{
checkFn: () => {
throw new Error(
"foo");
},
expected_count: 0,
},
{ checkFn: () => count++ > 0, expected_count: 2 },
];
for (
const { checkFn, expected_count } of data) {
count = 0;
const clicked =
new EventPromise(element,
"click", { checkFn });
element.click();
element.click();
const event = await clicked;
equal(element, event.target);
equal(expected_count, count);
}
});
add_task(async
function test_EventPromise_mozSystemGroupTypes() {
const element =
new MockElement();
for (
const mozSystemGroup of [
null,
"foo", 42, [], {}]) {
Assert.
throws(
() =>
new EventPromise(element,
"click", { mozSystemGroup }),
/TypeError/
);
}
});
add_task(async
function test_EventPromise_mozSystemGroupEvent() {
const element =
new MockElement();
for (
const mozSystemGroup of [undefined,
false,
true]) {
const expectedMozSystemGroup = mozSystemGroup ??
false;
const clicked =
new EventPromise(element,
"click", { mozSystemGroup });
element.click();
const event = await clicked;
equal(element, event.target);
equal(expectedMozSystemGroup, event.mozSystemGroup);
}
});
add_task(async
function test_EventPromise_wantUntrustedTypes() {
const element =
new MockElement();
for (let wantUntrusted of [
null,
"foo", 42, [], {}]) {
Assert.
throws(
() =>
new EventPromise(element,
"click", { wantUntrusted }),
/TypeError/
);
}
});
add_task(async
function test_EventPromise_wantUntrustedEvent() {
for (
const wantUntrusted of [undefined,
false,
true]) {
let expected_untrusted = wantUntrusted ??
false;
const element =
new MockElement();
const clicked =
new EventPromise(element,
"click", { wantUntrusted });
element.dispatchEvent(
new CustomEvent(
"click", {}));
const event = await clicked;
equal(element, event.target);
equal(expected_untrusted, event.untrusted);
}
});
add_task(
function test_executeSoon_callback() {
// executeSoon() is already defined for xpcshell in head.js. As such import
// our implementation into a custom namespace.
let sync = ChromeUtils.importESModule(
"chrome://remote/content/shared/Sync.sys.mjs"
);
for (let func of [
"foo",
null,
true, [], {}]) {
Assert.
throws(() => sync.executeSoon(func), /TypeError/);
}
let a;
sync.executeSoon(() => {
a = 1;
});
executeSoon(() => equal(1, a));
});
add_task(
function test_PollPromise_funcTypes() {
for (let type of [
"foo", 42,
null, undefined,
true, [], {}]) {
Assert.
throws(() =>
new PollPromise(type), /TypeError/);
}
new PollPromise(() => {});
new PollPromise(
function () {});
});
add_task(
function test_PollPromise_timeoutTypes() {
for (let timeout of [
"foo",
true, [], {}]) {
Assert.
throws(() =>
new PollPromise(() => {}, { timeout }), /TypeError/);
}
for (let timeout of [1.2, -1]) {
Assert.
throws(() =>
new PollPromise(() => {}, { timeout }), /RangeError/);
}
for (let timeout of [
null, undefined, 42]) {
new PollPromise(resolve => resolve(1), { timeout });
}
});
add_task(
function test_PollPromise_intervalTypes() {
for (let interval of [
"foo",
null,
true, [], {}]) {
Assert.
throws(() =>
new PollPromise(() => {}, { interval }), /TypeError/);
}
for (let interval of [1.2, -1]) {
Assert.
throws(() =>
new PollPromise(() => {}, { interval }), /RangeError/);
}
new PollPromise(() => {}, { interval: 42 });
});
add_task(async
function test_PollPromise_retvalTypes() {
for (let typ of [
true,
false,
"foo", 42, [], {}]) {
strictEqual(typ, await
new PollPromise(resolve => resolve(typ)));
}
});
add_task(async
function test_PollPromise_rethrowError() {
let nevals = 0;
let err;
try {
await PollPromise(() => {
++nevals;
throw new Error();
});
}
catch (e) {
err = e;
}
equal(1, nevals);
ok(err
instanceof Error);
});
add_task(async
function test_PollPromise_noTimeout() {
let nevals = 0;
await
new PollPromise((resolve, reject) => {
++nevals;
nevals < 100 ? reject() : resolve();
});
equal(100, nevals);
});
add_task(async
function test_PollPromise_zeroTimeout() {
// run at least once when timeout is 0
let nevals = 0;
let start =
new Date().getTime();
await
new PollPromise(
(resolve, reject) => {
++nevals;
reject();
},
{ timeout: 0 }
);
let end =
new Date().getTime();
equal(1, nevals);
less(end - start, 500);
});
add_task(async
function test_PollPromise_timeoutElapse() {
let nevals = 0;
let start =
new Date().getTime();
await
new PollPromise(
(resolve, reject) => {
++nevals;
reject();
},
{ timeout: 100 }
);
let end =
new Date().getTime();
lessOrEqual(nevals, 11);
greaterOrEqual(end - start, 100);
});
add_task(async
function test_PollPromise_interval() {
let nevals = 0;
await
new PollPromise(
(resolve, reject) => {
++nevals;
reject();
},
{ timeout: 100, interval: 100 }
);
equal(2, nevals);
});
add_task(async
function test_PollPromise_resolve() {
const log = Log.repository.getLogger(
"RemoteAgent");
const appender =
new MockAppender(
new Log.BasicFormatter());
appender.level = Log.Level.Info;
log.addAppender(appender);
const errorMessage =
"PollingFailed";
const timeout = 100;
await
new PollPromise(
resolve => {
resolve();
},
{ timeout, errorMessage }
);
Assert.equal(appender.messages.length, 0);
await
new PollPromise(
(resolve, reject) => {
reject();
},
{ timeout, errorMessage:
"PollingFailed" }
);
Assert.equal(appender.messages.length, 1);
Assert.equal(appender.messages[0].level, Log.Level.Warn);
Assert.equal(appender.messages[0].message,
"PollingFailed after 100 ms");
});