browser_events_dispatcher.js (17543B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * Check the basic behavior of on/off. 8 */ 9 add_task(async function test_add_remove_event_listener() { 10 const tab = await addTab("https://example.com/document-builder.sjs?html=tab"); 11 const browsingContext = tab.linkedBrowser.browsingContext; 12 const contextDescriptor = { 13 type: ContextDescriptorType.TopBrowsingContext, 14 id: browsingContext.browserId, 15 }; 16 17 const root = createRootMessageHandler("session-id-event"); 18 const monitoringEvents = await setupEventMonitoring(root); 19 await emitTestEvent(root, browsingContext, monitoringEvents); 20 is(await isSubscribed(root, browsingContext), false); 21 22 info("Add an listener for eventemitter.testEvent"); 23 const events = []; 24 const onEvent = (event, data) => events.push(data.text); 25 await root.eventsDispatcher.on( 26 "eventemitter.testEvent", 27 contextDescriptor, 28 onEvent 29 ); 30 is(await isSubscribed(root, browsingContext), true); 31 32 await emitTestEvent(root, browsingContext, monitoringEvents); 33 is(events.length, 1); 34 35 info( 36 "Remove a listener for a callback not added before and check that the first one is still registered" 37 ); 38 const anotherCallback = () => {}; 39 await root.eventsDispatcher.off( 40 "eventemitter.testEvent", 41 contextDescriptor, 42 anotherCallback 43 ); 44 is(await isSubscribed(root, browsingContext), true); 45 46 await emitTestEvent(root, browsingContext, monitoringEvents); 47 is(events.length, 2); 48 49 info("Remove the listener for eventemitter.testEvent"); 50 await root.eventsDispatcher.off( 51 "eventemitter.testEvent", 52 contextDescriptor, 53 onEvent 54 ); 55 is(await isSubscribed(root, browsingContext), false); 56 57 await emitTestEvent(root, browsingContext, monitoringEvents); 58 is(events.length, 2); 59 60 info("Add the listener for eventemitter.testEvent again"); 61 await root.eventsDispatcher.on( 62 "eventemitter.testEvent", 63 contextDescriptor, 64 onEvent 65 ); 66 is(await isSubscribed(root, browsingContext), true); 67 68 await emitTestEvent(root, browsingContext, monitoringEvents); 69 is(events.length, 3); 70 71 info("Remove the listener for eventemitter.testEvent"); 72 await root.eventsDispatcher.off( 73 "eventemitter.testEvent", 74 contextDescriptor, 75 onEvent 76 ); 77 is(await isSubscribed(root, browsingContext), false); 78 79 info("Remove the listener again to check the API will not throw"); 80 await root.eventsDispatcher.off( 81 "eventemitter.testEvent", 82 contextDescriptor, 83 onEvent 84 ); 85 86 root.destroy(); 87 gBrowser.removeTab(tab); 88 }); 89 90 add_task(async function test_add_remove_event_listener_for_user_context() { 91 const tab = await addTab("https://example.com/document-builder.sjs?html=tab"); 92 const browsingContext = tab.linkedBrowser.browsingContext; 93 const contextDescriptor = { 94 type: ContextDescriptorType.UserContext, 95 id: browsingContext.originAttributes.userContextId, 96 }; 97 98 const root = createRootMessageHandler("session-id-event"); 99 const monitoringEvents = await setupEventMonitoring(root); 100 await emitTestEvent(root, browsingContext, monitoringEvents); 101 is(await isSubscribed(root, browsingContext), false); 102 103 info("Add an listener for eventemitter.testEvent"); 104 const events = []; 105 const onEvent = (event, data) => events.push(data.text); 106 await root.eventsDispatcher.on( 107 "eventemitter.testEvent", 108 contextDescriptor, 109 onEvent 110 ); 111 is(await isSubscribed(root, browsingContext), true); 112 113 await emitTestEvent(root, browsingContext, monitoringEvents); 114 is(events.length, 1); 115 116 info("Remove the listener for eventemitter.testEvent"); 117 await root.eventsDispatcher.off( 118 "eventemitter.testEvent", 119 contextDescriptor, 120 onEvent 121 ); 122 is(await isSubscribed(root, browsingContext), false); 123 124 root.destroy(); 125 gBrowser.removeTab(tab); 126 }); 127 128 add_task(async function test_has_listener() { 129 const tab1 = await addTab("https://example.com/document-builder.sjs?html=1"); 130 const browsingContext1 = tab1.linkedBrowser.browsingContext; 131 132 const tab2 = await addTab("https://example.com/document-builder.sjs?html=2"); 133 const browsingContext2 = tab2.linkedBrowser.browsingContext; 134 135 const contextDescriptor1 = { 136 type: ContextDescriptorType.TopBrowsingContext, 137 id: browsingContext1.browserId, 138 }; 139 const contextDescriptor2 = { 140 type: ContextDescriptorType.TopBrowsingContext, 141 id: browsingContext2.browserId, 142 }; 143 144 const root = createRootMessageHandler("session-id-event"); 145 146 // Shortcut for the EventsDispatcher.hasListener API. 147 function hasListener(contextId) { 148 return root.eventsDispatcher.hasListener("eventemitter.testEvent", { 149 contextId, 150 }); 151 } 152 153 const onEvent = () => {}; 154 await root.eventsDispatcher.on( 155 "eventemitter.testEvent", 156 contextDescriptor1, 157 onEvent 158 ); 159 ok(hasListener(browsingContext1.id), "Has a listener for browsingContext1"); 160 ok(!hasListener(browsingContext2.id), "No listener for browsingContext2"); 161 162 await root.eventsDispatcher.on( 163 "eventemitter.testEvent", 164 contextDescriptor2, 165 onEvent 166 ); 167 ok(hasListener(browsingContext1.id), "Still a listener for browsingContext1"); 168 ok(hasListener(browsingContext2.id), "Has a listener for browsingContext2"); 169 170 await root.eventsDispatcher.off( 171 "eventemitter.testEvent", 172 contextDescriptor1, 173 onEvent 174 ); 175 ok(!hasListener(browsingContext1.id), "No listener for browsingContext1"); 176 ok(hasListener(browsingContext2.id), "Still a listener for browsingContext2"); 177 178 await root.eventsDispatcher.off( 179 "eventemitter.testEvent", 180 contextDescriptor2, 181 onEvent 182 ); 183 ok(!hasListener(browsingContext1.id), "No listener for browsingContext1"); 184 ok(!hasListener(browsingContext2.id), "No listener for browsingContext2"); 185 186 await root.eventsDispatcher.on( 187 "eventemitter.testEvent", 188 { 189 type: ContextDescriptorType.All, 190 }, 191 onEvent 192 ); 193 ok(hasListener(browsingContext1.id), "Has a listener for browsingContext1"); 194 ok(hasListener(browsingContext2.id), "Has a listener for browsingContext2"); 195 196 await root.eventsDispatcher.off( 197 "eventemitter.testEvent", 198 { 199 type: ContextDescriptorType.All, 200 }, 201 onEvent 202 ); 203 ok(!hasListener(browsingContext1.id), "No listener for browsingContext1"); 204 ok(!hasListener(browsingContext2.id), "No listener for browsingContext2"); 205 206 root.destroy(); 207 gBrowser.removeTab(tab2); 208 gBrowser.removeTab(tab1); 209 }); 210 211 /** 212 * Check that two callbacks can subscribe to the same event in the same context 213 * in parallel. 214 */ 215 add_task(async function test_two_callbacks() { 216 const tab = await addTab("https://example.com/document-builder.sjs?html=tab"); 217 const browsingContext = tab.linkedBrowser.browsingContext; 218 const contextDescriptor = { 219 type: ContextDescriptorType.TopBrowsingContext, 220 id: browsingContext.browserId, 221 }; 222 223 const root = createRootMessageHandler("session-id-event"); 224 const monitoringEvents = await setupEventMonitoring(root); 225 226 info("Add an listener for eventemitter.testEvent"); 227 const events = []; 228 const onEvent = (event, data) => events.push(data.text); 229 await root.eventsDispatcher.on( 230 "eventemitter.testEvent", 231 contextDescriptor, 232 onEvent 233 ); 234 235 await emitTestEvent(root, browsingContext, monitoringEvents); 236 is(events.length, 1); 237 238 info("Add another listener for eventemitter.testEvent"); 239 const otherevents = []; 240 const otherCallback = (event, data) => otherevents.push(data.text); 241 await root.eventsDispatcher.on( 242 "eventemitter.testEvent", 243 contextDescriptor, 244 otherCallback 245 ); 246 is(await isSubscribed(root, browsingContext), true); 247 248 await emitTestEvent(root, browsingContext, monitoringEvents); 249 is(events.length, 2); 250 is(otherevents.length, 1); 251 252 info("Remove the other listener for eventemitter.testEvent"); 253 await root.eventsDispatcher.off( 254 "eventemitter.testEvent", 255 contextDescriptor, 256 otherCallback 257 ); 258 is(await isSubscribed(root, browsingContext), true); 259 260 await emitTestEvent(root, browsingContext, monitoringEvents); 261 is(events.length, 3); 262 is(otherevents.length, 1); 263 264 info("Remove the first listener for eventemitter.testEvent"); 265 await root.eventsDispatcher.off( 266 "eventemitter.testEvent", 267 contextDescriptor, 268 onEvent 269 ); 270 is(await isSubscribed(root, browsingContext), false); 271 272 await emitTestEvent(root, browsingContext, monitoringEvents); 273 is(events.length, 3); 274 is(otherevents.length, 1); 275 276 root.destroy(); 277 gBrowser.removeTab(tab); 278 }); 279 280 /** 281 * Check that two callbacks can subscribe to the same event in the two contexts. 282 */ 283 add_task(async function test_two_contexts() { 284 const tab1 = await addTab("https://example.com/document-builder.sjs?html=1"); 285 const browsingContext1 = tab1.linkedBrowser.browsingContext; 286 287 const tab2 = await addTab("https://example.com/document-builder.sjs?html=2"); 288 const browsingContext2 = tab2.linkedBrowser.browsingContext; 289 290 const contextDescriptor1 = { 291 type: ContextDescriptorType.TopBrowsingContext, 292 id: browsingContext1.browserId, 293 }; 294 const contextDescriptor2 = { 295 type: ContextDescriptorType.TopBrowsingContext, 296 id: browsingContext2.browserId, 297 }; 298 299 const root = createRootMessageHandler("session-id-event"); 300 301 const monitoringEvents = await setupEventMonitoring(root); 302 303 const events1 = []; 304 const onEvent1 = (event, data) => events1.push(data.text); 305 await root.eventsDispatcher.on( 306 "eventemitter.testEvent", 307 contextDescriptor1, 308 onEvent1 309 ); 310 is(await isSubscribed(root, browsingContext1), true); 311 is(await isSubscribed(root, browsingContext2), false); 312 313 const events2 = []; 314 const onEvent2 = (event, data) => events2.push(data.text); 315 await root.eventsDispatcher.on( 316 "eventemitter.testEvent", 317 contextDescriptor2, 318 onEvent2 319 ); 320 is(await isSubscribed(root, browsingContext1), true); 321 is(await isSubscribed(root, browsingContext2), true); 322 323 await emitTestEvent(root, browsingContext1, monitoringEvents); 324 is(events1.length, 1); 325 is(events2.length, 0); 326 327 await emitTestEvent(root, browsingContext2, monitoringEvents); 328 is(events1.length, 1); 329 is(events2.length, 1); 330 331 await root.eventsDispatcher.off( 332 "eventemitter.testEvent", 333 contextDescriptor1, 334 onEvent1 335 ); 336 is(await isSubscribed(root, browsingContext1), false); 337 is(await isSubscribed(root, browsingContext2), true); 338 339 // No event expected here since the module for browsingContext1 is no longer 340 // subscribed 341 await emitTestEvent(root, browsingContext1, monitoringEvents); 342 is(events1.length, 1); 343 is(events2.length, 1); 344 345 // Whereas the module for browsingContext2 is still subscribed 346 await emitTestEvent(root, browsingContext2, monitoringEvents); 347 is(events1.length, 1); 348 is(events2.length, 2); 349 350 await root.eventsDispatcher.off( 351 "eventemitter.testEvent", 352 contextDescriptor2, 353 onEvent2 354 ); 355 is(await isSubscribed(root, browsingContext1), false); 356 is(await isSubscribed(root, browsingContext2), false); 357 358 await emitTestEvent(root, browsingContext1, monitoringEvents); 359 await emitTestEvent(root, browsingContext2, monitoringEvents); 360 is(events1.length, 1); 361 is(events2.length, 2); 362 363 root.destroy(); 364 gBrowser.removeTab(tab2); 365 gBrowser.removeTab(tab1); 366 }); 367 368 /** 369 * Check that adding and removing first listener for the specific context and then 370 * for the global context works as expected. 371 */ 372 add_task( 373 async function test_remove_context_event_listener_and_then_global_event_listener() { 374 const tab = await addTab( 375 "https://example.com/document-builder.sjs?html=tab" 376 ); 377 const browsingContext = tab.linkedBrowser.browsingContext; 378 const contextDescriptor = { 379 type: ContextDescriptorType.TopBrowsingContext, 380 id: browsingContext.browserId, 381 }; 382 const contextDescriptorAll = { 383 type: ContextDescriptorType.All, 384 }; 385 386 const root = createRootMessageHandler("session-id-event"); 387 const monitoringEvents = await setupEventMonitoring(root); 388 await emitTestEvent(root, browsingContext, monitoringEvents); 389 is(await isSubscribed(root, browsingContext), false); 390 391 info("Add an listener for eventemitter.testEvent"); 392 const events = []; 393 const onEvent = (event, data) => events.push(data.text); 394 await root.eventsDispatcher.on( 395 "eventemitter.testEvent", 396 contextDescriptor, 397 onEvent 398 ); 399 is(await isSubscribed(root, browsingContext), true); 400 401 await emitTestEvent(root, browsingContext, monitoringEvents); 402 is(events.length, 1); 403 404 info( 405 "Add another listener for eventemitter.testEvent, using global context" 406 ); 407 const eventsAll = []; 408 const onEventAll = (event, data) => eventsAll.push(data.text); 409 await root.eventsDispatcher.on( 410 "eventemitter.testEvent", 411 contextDescriptorAll, 412 onEventAll 413 ); 414 await emitTestEvent(root, browsingContext, monitoringEvents); 415 is(eventsAll.length, 1); 416 is(events.length, 2); 417 418 info("Remove the first listener for eventemitter.testEvent"); 419 await root.eventsDispatcher.off( 420 "eventemitter.testEvent", 421 contextDescriptor, 422 onEvent 423 ); 424 425 info("Check that we are still subscribed to eventemitter.testEvent"); 426 is(await isSubscribed(root, browsingContext), true); 427 await emitTestEvent(root, browsingContext, monitoringEvents); 428 is(eventsAll.length, 2); 429 is(events.length, 2); 430 431 await root.eventsDispatcher.off( 432 "eventemitter.testEvent", 433 contextDescriptorAll, 434 onEventAll 435 ); 436 is(await isSubscribed(root, browsingContext), false); 437 await emitTestEvent(root, browsingContext, monitoringEvents); 438 is(eventsAll.length, 2); 439 is(events.length, 2); 440 441 root.destroy(); 442 gBrowser.removeTab(tab); 443 } 444 ); 445 446 /** 447 * Check that adding and removing first listener for the global context and then 448 * for the specific context works as expected. 449 */ 450 add_task( 451 async function test_global_event_listener_and_then_remove_context_event_listener() { 452 const tab = await addTab( 453 "https://example.com/document-builder.sjs?html=tab" 454 ); 455 const browsingContext = tab.linkedBrowser.browsingContext; 456 const contextDescriptor = { 457 type: ContextDescriptorType.TopBrowsingContext, 458 id: browsingContext.browserId, 459 }; 460 const contextDescriptorAll = { 461 type: ContextDescriptorType.All, 462 }; 463 464 const root = createRootMessageHandler("session-id-event"); 465 const monitoringEvents = await setupEventMonitoring(root); 466 await emitTestEvent(root, browsingContext, monitoringEvents); 467 is(await isSubscribed(root, browsingContext), false); 468 469 info("Add an listener for eventemitter.testEvent"); 470 const events = []; 471 const onEvent = (event, data) => events.push(data.text); 472 await root.eventsDispatcher.on( 473 "eventemitter.testEvent", 474 contextDescriptor, 475 onEvent 476 ); 477 is(await isSubscribed(root, browsingContext), true); 478 479 await emitTestEvent(root, browsingContext, monitoringEvents); 480 is(events.length, 1); 481 482 info( 483 "Add another listener for eventemitter.testEvent, using global context" 484 ); 485 const eventsAll = []; 486 const onEventAll = (event, data) => eventsAll.push(data.text); 487 await root.eventsDispatcher.on( 488 "eventemitter.testEvent", 489 contextDescriptorAll, 490 onEventAll 491 ); 492 await emitTestEvent(root, browsingContext, monitoringEvents); 493 is(eventsAll.length, 1); 494 is(events.length, 2); 495 496 info("Remove the global listener for eventemitter.testEvent"); 497 await root.eventsDispatcher.off( 498 "eventemitter.testEvent", 499 contextDescriptorAll, 500 onEventAll 501 ); 502 503 info( 504 "Check that we are still subscribed to eventemitter.testEvent for the specific context" 505 ); 506 is(await isSubscribed(root, browsingContext), true); 507 await emitTestEvent(root, browsingContext, monitoringEvents); 508 is(eventsAll.length, 1); 509 is(events.length, 3); 510 511 await root.eventsDispatcher.off( 512 "eventemitter.testEvent", 513 contextDescriptor, 514 onEvent 515 ); 516 517 is(await isSubscribed(root, browsingContext), false); 518 await emitTestEvent(root, browsingContext, monitoringEvents); 519 is(eventsAll.length, 1); 520 is(events.length, 3); 521 522 root.destroy(); 523 gBrowser.removeTab(tab); 524 } 525 ); 526 527 async function setupEventMonitoring(root) { 528 const monitoringEvents = []; 529 const onMonitoringEvent = (event, data) => monitoringEvents.push(data.text); 530 root.on("eventemitter.monitoringEvent", onMonitoringEvent); 531 532 registerCleanupFunction(() => 533 root.off("eventemitter.monitoringEvent", onMonitoringEvent) 534 ); 535 536 return monitoringEvents; 537 } 538 539 async function emitTestEvent(root, browsingContext, monitoringEvents) { 540 const count = monitoringEvents.length; 541 info("Call eventemitter.emitTestEvent"); 542 await root.handleCommand({ 543 moduleName: "eventemitter", 544 commandName: "emitTestEvent", 545 destination: { 546 type: WindowGlobalMessageHandler.type, 547 id: browsingContext.id, 548 }, 549 }); 550 551 // The monitoring event is always emitted, regardless of the status of the 552 // module. Wait for catching this event before resuming the assertions. 553 info("Wait for the monitoring event"); 554 await BrowserTestUtils.waitForCondition( 555 () => monitoringEvents.length >= count + 1 556 ); 557 is(monitoringEvents.length, count + 1); 558 } 559 560 function isSubscribed(root, browsingContext) { 561 info("Call eventemitter.isSubscribed"); 562 return root.handleCommand({ 563 moduleName: "eventemitter", 564 commandName: "isSubscribed", 565 destination: { 566 type: WindowGlobalMessageHandler.type, 567 id: browsingContext.id, 568 }, 569 }); 570 }