diff --git a/dom/observable/tentative/observable-constructor.window.js b/dom/observable/tentative/observable-constructor.window.js index 34776f8fc56b5b..d2b597c819054f 100644 --- a/dom/observable/tentative/observable-constructor.window.js +++ b/dom/observable/tentative/observable-constructor.window.js @@ -98,3 +98,30 @@ promise_test(async t => { assert_array_equals(results, ["detached"], "Subscribe callback is never invoked"); }, "Cannot subscribe to an Observable in a detached document"); + +promise_test(async t => { + // Make this available off the global so the child can reach it. + window.results = []; + const contentWin = await loadIframeAndReturnContentWindow(); + + contentWin.eval(` + const parentResults = parent.results; + const event_target = new EventTarget(); + // Set up two event listeners, both of which will mutate |parentResults|: + // 1. A traditional event listener + // 2. An observable + event_target.addEventListener('customevent', e => parentResults.push(e)); + const source = event_target.on('customevent'); + source.subscribe(e => parentResults.push(e)); + + // Detach the iframe and fire an event at the event target. The parent will + // confirm that the observable's next handler did not get invoked, because + // the window is detached. + const event = new Event('customevent'); + window.frameElement.remove(); + parentResults.push('detached'); + event_target.dispatchEvent(event); + `); + + assert_array_equals(results, ["detached"], "Subscribe callback is never invoked"); +}, "Observable from EventTarget does not get notified for events in detached documents"); diff --git a/dom/observable/tentative/observable-event-target.any.js b/dom/observable/tentative/observable-event-target.any.js new file mode 100644 index 00000000000000..1979afc7d588c6 --- /dev/null +++ b/dom/observable/tentative/observable-event-target.any.js @@ -0,0 +1,57 @@ +test(() => { + const target = new EventTarget(); + assert_implements(target.on, "The EventTarget interface has an `on` method"); + assert_equals(typeof target.on, "function", + "EventTarget should have the on method"); + + const testEvents = target.on("test"); + assert_true(testEvents instanceof Observable, + "EventTarget.on returns an Observable"); + + const results = []; + testEvents.subscribe({ + next: value => results.push(value), + error: () => results.push("error"), + complete: () => results.push("complete"), + }); + + assert_array_equals(results, [], + "Observable does not emit events until event is fired"); + + const event = new Event("test"); + target.dispatchEvent(event); + assert_array_equals(results, [event]); + + target.dispatchEvent(event); + assert_array_equals(results, [event, event]); +}, "EventTarget.on() returns an Observable"); + +test(() => { + const target = new EventTarget(); + const testEvents = target.on("test"); + const ac = new AbortController(); + const results = []; + testEvents.subscribe({ + next: (value) => results.push(value), + error: () => results.push('error'), + complete: () => results.complete('complete'), + }, { signal: ac.signal }); + + assert_array_equals(results, [], + "Observable does not emit events until event is fired"); + + const event1 = new Event("test"); + const event2 = new Event("test"); + const event3 = new Event("test"); + target.dispatchEvent(event1); + target.dispatchEvent(event2); + + assert_array_equals(results, [event1, event2]); + + ac.abort(); + target.dispatchEvent(event3); + + assert_array_equals(results, [event1, event2], + "Aborting the subscription removes the event listener and stops the " + + "emission of events"); +}, "Aborting the subscription should stop the emission of events"); diff --git a/trusted-types/trusted-types-event-handlers.html b/trusted-types/trusted-types-event-handlers.html index 57f8d3d90c4162..9dd7133cbb0b5b 100644 --- a/trusted-types/trusted-types-event-handlers.html +++ b/trusted-types/trusted-types-event-handlers.html @@ -37,7 +37,9 @@ // element about which attributes it knows. const div = document.createElement("div"); for(name in div.__proto__) { - const should_be_event_handler = name.startsWith("on"); + // This captures all "on{foo}" handlers, but not "on" itself, which is an IDL + // attribute that returns an Observable. + const should_be_event_handler = name.startsWith("on") && name !== "on"; if (should_be_event_handler) { test(t => { assert_throws_js(TypeError,