test_Sync.js (13954B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const { setTimeout } = ChromeUtils.importESModule( 6 "resource://gre/modules/Timer.sys.mjs" 7 ); 8 9 const { 10 AnimationFramePromise, 11 Deferred, 12 EventPromise, 13 PollPromise, 14 TimedPromise, 15 } = ChromeUtils.importESModule("chrome://remote/content/shared/Sync.sys.mjs"); 16 17 const { Log } = ChromeUtils.importESModule( 18 "resource://gre/modules/Log.sys.mjs" 19 ); 20 21 /** 22 * Mimic a DOM node for listening for events. 23 */ 24 class MockElement { 25 constructor() { 26 this.capture = false; 27 this.eventName = null; 28 this.func = null; 29 this.mozSystemGroup = false; 30 this.wantUntrusted = false; 31 this.untrusted = false; 32 } 33 34 addEventListener(name, func, options = {}) { 35 const { capture, mozSystemGroup, wantUntrusted } = options; 36 37 this.eventName = name; 38 this.func = func; 39 this.capture = capture ?? false; 40 this.mozSystemGroup = mozSystemGroup ?? false; 41 this.wantUntrusted = wantUntrusted ?? false; 42 } 43 44 click() { 45 if (this.func) { 46 const event = { 47 capture: this.capture, 48 mozSystemGroup: this.mozSystemGroup, 49 target: this, 50 type: this.eventName, 51 untrusted: this.untrusted, 52 wantUntrusted: this.wantUntrusted, 53 }; 54 this.func(event); 55 } 56 } 57 58 dispatchEvent() { 59 if (this.wantUntrusted) { 60 this.untrusted = true; 61 } 62 this.click(); 63 } 64 65 removeEventListener() { 66 this.capture = false; 67 this.eventName = null; 68 this.func = null; 69 this.mozSystemGroup = false; 70 this.untrusted = false; 71 this.wantUntrusted = false; 72 } 73 } 74 75 class MockAppender extends Log.Appender { 76 constructor(formatter) { 77 super(formatter); 78 this.messages = []; 79 } 80 81 append(message) { 82 this.doAppend(message); 83 } 84 85 doAppend(message) { 86 this.messages.push(message); 87 } 88 } 89 90 add_task(async function test_AnimationFramePromise() { 91 for (const options of [ 92 undefined, // default options 93 {}, //empty options 94 { timeout: null }, // disabled timeout 95 { timeout: 1234 }, // custom timeout 96 ]) { 97 let called = false; 98 let win = { 99 addEventListener(_event, _listener) {}, 100 requestAnimationFrame(callback) { 101 called = true; 102 callback(); 103 }, 104 }; 105 await AnimationFramePromise(win, options); 106 ok(called); 107 } 108 }); 109 110 const mockNoopWindow = { 111 addEventListener(_event, _listener) {}, 112 requestAnimationFrame(_callback) {}, 113 }; 114 115 add_task(async function test_AnimationFramePromiseDefaultTimeout() { 116 await AnimationFramePromise(mockNoopWindow); 117 }); 118 119 add_task(async function test_AnimationFramePromiseCustomTimeout() { 120 await AnimationFramePromise(mockNoopWindow, { timeout: 10 }); 121 }); 122 123 add_task(async function test_AnimationFramePromiseAbortOnPageHide() { 124 let resolvePageHideEvent; 125 126 const mockWindow = { 127 addEventListener(event, listener) { 128 if (event === "pagehide") { 129 resolvePageHideEvent = listener; 130 } 131 }, 132 removeEventListener() {}, 133 requestAnimationFrame(callback) { 134 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 135 setTimeout(() => callback(), 10000); 136 }, 137 }; 138 139 const trackedPromise = trackPromise(AnimationFramePromise(mockWindow)); 140 141 ok(trackedPromise.isPending(), "AnimationFramePromise is pending"); 142 143 // Simulate "pagehide" event. 144 resolvePageHideEvent({}); 145 146 await trackedPromise; 147 }); 148 149 add_task(async function test_AnimationFramePromiseTimeoutErrors() { 150 // not an number or null 151 for (const val of ["foo", true, [], {}]) { 152 Assert.throws( 153 () => AnimationFramePromise(mockNoopWindow, { timeout: val }), 154 /TypeError: timeout must be a number or null/ 155 ); 156 } 157 158 // not a nonnegative integer 159 for (const val of [-1, -100000, -1.2, 1.2]) { 160 Assert.throws( 161 () => AnimationFramePromise(mockNoopWindow, { timeout: val }), 162 /RangeError: timeout must be a non-negative integer/ 163 ); 164 } 165 }); 166 167 add_task(async function test_DeferredPending() { 168 const deferred = Deferred(); 169 ok(deferred.pending); 170 171 deferred.resolve(); 172 await deferred.promise; 173 ok(!deferred.pending); 174 }); 175 176 add_task(async function test_DeferredRejected() { 177 const deferred = Deferred(); 178 179 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 180 setTimeout(() => deferred.reject(new Error("foo")), 100); 181 182 try { 183 await deferred.promise; 184 ok(false); 185 } catch (e) { 186 ok(!deferred.pending); 187 188 ok(!deferred.fulfilled); 189 ok(deferred.rejected); 190 equal(e.message, "foo"); 191 } 192 }); 193 194 add_task(async function test_DeferredResolved() { 195 const deferred = Deferred(); 196 ok(deferred.pending); 197 198 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 199 setTimeout(() => deferred.resolve("foo"), 100); 200 201 const result = await deferred.promise; 202 ok(!deferred.pending); 203 204 ok(deferred.fulfilled); 205 ok(!deferred.rejected); 206 equal(result, "foo"); 207 }); 208 209 add_task(async function test_EventPromise_subjectTypes() { 210 for (const subject of ["foo", 42, null, undefined, true, [], {}]) { 211 Assert.throws(() => new EventPromise(subject, "click"), /TypeError/); 212 } 213 }); 214 215 add_task(async function test_EventPromise_eventNameTypes() { 216 const element = new MockElement(); 217 218 for (const eventName of [42, null, undefined, true, [], {}]) { 219 Assert.throws(() => new EventPromise(element, eventName), /TypeError/); 220 } 221 }); 222 223 add_task(async function test_EventPromise_subjectAndEventNameEvent() { 224 const element = new MockElement(); 225 226 const clicked = new EventPromise(element, "click"); 227 element.click(); 228 const event = await clicked; 229 230 equal(element, event.target); 231 }); 232 233 add_task(async function test_EventPromise_captureTypes() { 234 const element = new MockElement(); 235 236 for (const capture of [null, "foo", 42, [], {}]) { 237 Assert.throws( 238 () => new EventPromise(element, "click", { capture }), 239 /TypeError/ 240 ); 241 } 242 }); 243 244 add_task(async function test_EventPromise_captureEvent() { 245 const element = new MockElement(); 246 247 for (const capture of [undefined, false, true]) { 248 const expectedCapture = capture ?? false; 249 250 const clicked = new EventPromise(element, "click", { capture }); 251 element.click(); 252 const event = await clicked; 253 254 equal(element, event.target); 255 equal(expectedCapture, event.capture); 256 } 257 }); 258 259 add_task(async function test_EventPromise_checkFnTypes() { 260 const element = new MockElement(); 261 262 for (const checkFn of ["foo", 42, true, [], {}]) { 263 Assert.throws( 264 () => new EventPromise(element, "click", { checkFn }), 265 /TypeError/ 266 ); 267 } 268 }); 269 270 add_task(async function test_EventPromise_checkFnCallback() { 271 const element = new MockElement(); 272 273 let count; 274 const data = [ 275 { checkFn: null, expected_count: 0 }, 276 { checkFn: undefined, expected_count: 0 }, 277 { 278 checkFn: () => { 279 throw new Error("foo"); 280 }, 281 expected_count: 0, 282 }, 283 { checkFn: () => count++ > 0, expected_count: 2 }, 284 ]; 285 286 for (const { checkFn, expected_count } of data) { 287 count = 0; 288 289 const clicked = new EventPromise(element, "click", { checkFn }); 290 element.click(); 291 element.click(); 292 const event = await clicked; 293 294 equal(element, event.target); 295 equal(expected_count, count); 296 } 297 }); 298 299 add_task(async function test_EventPromise_mozSystemGroupTypes() { 300 const element = new MockElement(); 301 302 for (const mozSystemGroup of [null, "foo", 42, [], {}]) { 303 Assert.throws( 304 () => new EventPromise(element, "click", { mozSystemGroup }), 305 /TypeError/ 306 ); 307 } 308 }); 309 310 add_task(async function test_EventPromise_mozSystemGroupEvent() { 311 const element = new MockElement(); 312 313 for (const mozSystemGroup of [undefined, false, true]) { 314 const expectedMozSystemGroup = mozSystemGroup ?? false; 315 316 const clicked = new EventPromise(element, "click", { mozSystemGroup }); 317 element.click(); 318 const event = await clicked; 319 320 equal(element, event.target); 321 equal(expectedMozSystemGroup, event.mozSystemGroup); 322 } 323 }); 324 325 add_task(async function test_EventPromise_wantUntrustedTypes() { 326 const element = new MockElement(); 327 328 for (let wantUntrusted of [null, "foo", 42, [], {}]) { 329 Assert.throws( 330 () => new EventPromise(element, "click", { wantUntrusted }), 331 /TypeError/ 332 ); 333 } 334 }); 335 336 add_task(async function test_EventPromise_wantUntrustedEvent() { 337 for (const wantUntrusted of [undefined, false, true]) { 338 let expected_untrusted = wantUntrusted ?? false; 339 340 const element = new MockElement(); 341 342 const clicked = new EventPromise(element, "click", { wantUntrusted }); 343 element.dispatchEvent(new CustomEvent("click", {})); 344 const event = await clicked; 345 346 equal(element, event.target); 347 equal(expected_untrusted, event.untrusted); 348 } 349 }); 350 351 add_task(function test_executeSoon_callback() { 352 // executeSoon() is already defined for xpcshell in head.js. As such import 353 // our implementation into a custom namespace. 354 let sync = ChromeUtils.importESModule( 355 "chrome://remote/content/shared/Sync.sys.mjs" 356 ); 357 358 for (let func of ["foo", null, true, [], {}]) { 359 Assert.throws(() => sync.executeSoon(func), /TypeError/); 360 } 361 362 let a; 363 sync.executeSoon(() => { 364 a = 1; 365 }); 366 executeSoon(() => equal(1, a)); 367 }); 368 369 add_task(function test_PollPromise_funcTypes() { 370 for (let type of ["foo", 42, null, undefined, true, [], {}]) { 371 Assert.throws(() => new PollPromise(type), /TypeError/); 372 } 373 new PollPromise(() => {}); 374 new PollPromise(function () {}); 375 }); 376 377 add_task(function test_PollPromise_timeoutTypes() { 378 for (let timeout of ["foo", true, [], {}]) { 379 Assert.throws(() => new PollPromise(() => {}, { timeout }), /TypeError/); 380 } 381 for (let timeout of [1.2, -1]) { 382 Assert.throws(() => new PollPromise(() => {}, { timeout }), /RangeError/); 383 } 384 for (let timeout of [null, undefined, 42]) { 385 new PollPromise(resolve => resolve(1), { timeout }); 386 } 387 }); 388 389 add_task(function test_PollPromise_intervalTypes() { 390 for (let interval of ["foo", null, true, [], {}]) { 391 Assert.throws(() => new PollPromise(() => {}, { interval }), /TypeError/); 392 } 393 for (let interval of [1.2, -1]) { 394 Assert.throws(() => new PollPromise(() => {}, { interval }), /RangeError/); 395 } 396 new PollPromise(() => {}, { interval: 42 }); 397 }); 398 399 add_task(async function test_PollPromise_retvalTypes() { 400 for (let typ of [true, false, "foo", 42, [], {}]) { 401 strictEqual(typ, await new PollPromise(resolve => resolve(typ))); 402 } 403 }); 404 405 add_task(async function test_PollPromise_rethrowError() { 406 let nevals = 0; 407 let err; 408 try { 409 await PollPromise(() => { 410 ++nevals; 411 throw new Error(); 412 }); 413 } catch (e) { 414 err = e; 415 } 416 equal(1, nevals); 417 ok(err instanceof Error); 418 }); 419 420 add_task(async function test_PollPromise_noTimeout() { 421 let nevals = 0; 422 await new PollPromise((resolve, reject) => { 423 ++nevals; 424 nevals < 100 ? reject() : resolve(); 425 }); 426 equal(100, nevals); 427 }); 428 429 add_task(async function test_PollPromise_zeroTimeout() { 430 // run at least once when timeout is 0 431 let nevals = 0; 432 let start = new Date().getTime(); 433 await new PollPromise( 434 (resolve, reject) => { 435 ++nevals; 436 reject(); 437 }, 438 { timeout: 0 } 439 ); 440 let end = new Date().getTime(); 441 equal(1, nevals); 442 less(end - start, 500); 443 }); 444 445 add_task(async function test_PollPromise_timeoutElapse() { 446 let nevals = 0; 447 let start = new Date().getTime(); 448 await new PollPromise( 449 (resolve, reject) => { 450 ++nevals; 451 reject(); 452 }, 453 { timeout: 100 } 454 ); 455 let end = new Date().getTime(); 456 lessOrEqual(nevals, 11); 457 greaterOrEqual(end - start, 100); 458 }); 459 460 add_task(async function test_PollPromise_interval() { 461 let nevals = 0; 462 await new PollPromise( 463 (resolve, reject) => { 464 ++nevals; 465 reject(); 466 }, 467 { timeout: 100, interval: 100 } 468 ); 469 equal(2, nevals); 470 }); 471 472 add_task(async function test_PollPromise_resolve() { 473 const log = Log.repository.getLogger("RemoteAgent"); 474 const appender = new MockAppender(new Log.BasicFormatter()); 475 appender.level = Log.Level.Info; 476 log.addAppender(appender); 477 478 const errorMessage = "PollingFailed"; 479 const timeout = 100; 480 481 await new PollPromise( 482 resolve => { 483 resolve(); 484 }, 485 { timeout, errorMessage } 486 ); 487 Assert.equal(appender.messages.length, 0); 488 489 await new PollPromise( 490 (resolve, reject) => { 491 reject(); 492 }, 493 { timeout, errorMessage: "PollingFailed" } 494 ); 495 Assert.equal(appender.messages.length, 1); 496 Assert.equal(appender.messages[0].level, Log.Level.Warn); 497 Assert.equal(appender.messages[0].message, "PollingFailed after 100 ms"); 498 }); 499 500 add_task(function test_TimedPromise_funcTypes() { 501 for (let type of ["foo", 42, null, undefined, true, [], {}]) { 502 Assert.throws(() => new TimedPromise(type), /TypeError/); 503 } 504 new TimedPromise(resolve => resolve()); 505 new TimedPromise(function (resolve) { 506 resolve(); 507 }); 508 }); 509 510 add_task(function test_TimedPromise_timeoutTypes() { 511 for (let timeout of ["foo", null, true, [], {}]) { 512 Assert.throws( 513 () => new TimedPromise(resolve => resolve(), { timeout }), 514 /TypeError/ 515 ); 516 } 517 for (let timeout of [1.2, -1]) { 518 Assert.throws( 519 () => new TimedPromise(resolve => resolve(), { timeout }), 520 /RangeError/ 521 ); 522 } 523 new TimedPromise(resolve => resolve(), { timeout: 42 }); 524 }); 525 526 add_task(async function test_TimedPromise_errorMessage() { 527 try { 528 await new TimedPromise(() => {}, { timeout: 0 }); 529 ok(false, "Expected Timeout error not raised"); 530 } catch (e) { 531 ok( 532 e.message.includes("TimedPromise timed out after"), 533 "Expected default error message found" 534 ); 535 } 536 537 try { 538 await new TimedPromise(() => {}, { 539 errorMessage: "Not found", 540 timeout: 0, 541 }); 542 ok(false, "Expected Timeout error not raised"); 543 } catch (e) { 544 ok( 545 e.message.includes("Not found after"), 546 "Expected custom error message found" 547 ); 548 } 549 });