549 lines
14 KiB
JavaScript
549 lines
14 KiB
JavaScript
/* 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,
|
|
TimedPromise,
|
|
} = 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() {
|
|
for (const options of [
|
|
undefined, // default options
|
|
{}, //empty options
|
|
{ timeout: null }, // disabled timeout
|
|
{ timeout: 1234 }, // custom timeout
|
|
]) {
|
|
let called = false;
|
|
let win = {
|
|
addEventListener(_event, _listener) {},
|
|
requestAnimationFrame(callback) {
|
|
called = true;
|
|
callback();
|
|
},
|
|
};
|
|
await AnimationFramePromise(win, options);
|
|
ok(called);
|
|
}
|
|
});
|
|
|
|
const mockNoopWindow = {
|
|
addEventListener(_event, _listener) {},
|
|
requestAnimationFrame(_callback) {},
|
|
};
|
|
|
|
add_task(async function test_AnimationFramePromiseDefaultTimeout() {
|
|
await AnimationFramePromise(mockNoopWindow);
|
|
});
|
|
|
|
add_task(async function test_AnimationFramePromiseCustomTimeout() {
|
|
await AnimationFramePromise(mockNoopWindow, { timeout: 10 });
|
|
});
|
|
|
|
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_AnimationFramePromiseTimeoutErrors() {
|
|
// not an number or null
|
|
for (const val of ["foo", true, [], {}]) {
|
|
Assert.throws(
|
|
() => AnimationFramePromise(mockNoopWindow, { timeout: val }),
|
|
/TypeError: timeout must be a number or null/
|
|
);
|
|
}
|
|
|
|
// not a nonnegative integer
|
|
for (const val of [-1, -100000, -1.2, 1.2]) {
|
|
Assert.throws(
|
|
() => AnimationFramePromise(mockNoopWindow, { timeout: val }),
|
|
/RangeError: timeout must be a non-negative integer/
|
|
);
|
|
}
|
|
});
|
|
|
|
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");
|
|
});
|
|
|
|
add_task(function test_TimedPromise_funcTypes() {
|
|
for (let type of ["foo", 42, null, undefined, true, [], {}]) {
|
|
Assert.throws(() => new TimedPromise(type), /TypeError/);
|
|
}
|
|
new TimedPromise(resolve => resolve());
|
|
new TimedPromise(function (resolve) {
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
add_task(function test_TimedPromise_timeoutTypes() {
|
|
for (let timeout of ["foo", null, true, [], {}]) {
|
|
Assert.throws(
|
|
() => new TimedPromise(resolve => resolve(), { timeout }),
|
|
/TypeError/
|
|
);
|
|
}
|
|
for (let timeout of [1.2, -1]) {
|
|
Assert.throws(
|
|
() => new TimedPromise(resolve => resolve(), { timeout }),
|
|
/RangeError/
|
|
);
|
|
}
|
|
new TimedPromise(resolve => resolve(), { timeout: 42 });
|
|
});
|
|
|
|
add_task(async function test_TimedPromise_errorMessage() {
|
|
try {
|
|
await new TimedPromise(() => {}, { timeout: 0 });
|
|
ok(false, "Expected Timeout error not raised");
|
|
} catch (e) {
|
|
ok(
|
|
e.message.includes("TimedPromise timed out after"),
|
|
"Expected default error message found"
|
|
);
|
|
}
|
|
|
|
try {
|
|
await new TimedPromise(() => {}, {
|
|
errorMessage: "Not found",
|
|
timeout: 0,
|
|
});
|
|
ok(false, "Expected Timeout error not raised");
|
|
} catch (e) {
|
|
ok(
|
|
e.message.includes("Not found after"),
|
|
"Expected custom error message found"
|
|
);
|
|
}
|
|
});
|