ratechange fires twice
Categories
(Core :: Audio/Video: Playback, defect, P3)
Tracking
()
| Tracking | Status | |
|---|---|---|
| firefox79 | --- | fixed |
People
(Reporter: orstavik77, Assigned: alwu)
Details
Attachments
(2 files)
User Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36
Steps to reproduce:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<h1>hello world</h1>
<script>
//bug nr 1 (important error!!)
const audio = document.createElement("audio");
audio.onratechange = function (e) {
this.remove(); //bug, somehow, this.remove triggers the onratechange callback a second time.
console.log("ratechange bug1", e.timeStamp);
};
document.head.appendChild(audio);
audio.playbackRate = 0.1;
//bug nr 2 (just produce an error message that is not an Error, it seems like)
const audio2 = document.createElement("audio");
audio2.onratechange = function (e) {
document.head.removeChild(this); //throws NotFoundError: Node.removeChild: The node to be removed is not a child of this node
console.log("ratechange bug2", e.timeStamp);
};
document.head.appendChild(audio2);
audio2.playbackRate = 0.1;
//needed to run correctly in FF
const audio3 = document.createElement("audio");
audio3.onratechange = function (e) {
this.onratechange = undefined; //patch bug 1: by removing the onratechange listener, it doesn't trigger a second time
this.remove(); //patch bug 2: this.remove() doesn't throw an NotFoundError
console.log("ratechange no bug", e.timeStamp);
};
document.head.appendChild(audio3);
audio3.playbackRate = 0.1;
//illustrate the correct behavior of a similar event toggle in FF
const details = document.createElement("details");
details.ontoggle = function () {
document.body.removeChild(this); //doesn't throw a NotFoundError: Node.removeChild: The node to be removed is not a child of this node
//this.remove();
console.log("toggle");
};
document.body.appendChild(details);
details.open = true;
</script>
</body>
</html>
Actual results:
ratechange bug1 69
ratechange bug2 79
ratechange no bug 87
toggle
ratechange bug1 89
NotFoundError: Node.removeChild: The node to be removed is not a child of this node
Expected results:
ratechange bug1 69
ratechange bug2 79
ratechange no bug 87
toggle
| Reporter | ||
Comment 1•5 years ago
|
||
Additional info:
- Chrome produces the expected results.
- The toggle event is just added to show how the expected result looks in a similar element/event pair in FF.
- The second bug is a NotFoundError printed when there should be no such error.
- The timestamp on the event is printed. It illustrates that the bug is not that the same event triggers the onratechange callback twice, but that the most likely culprit is that a function inside HTMLAudioElement or one of its super classes dispatches the ratechange event when the element is removed from the DOM.
My best guess is that the bug is:
a) the HTMLAudioElement resets the playbackRate property when the element is disconnected from the DOM, and
b) then that the method that sets the playbackrate also dispatches a ratechange event and
c) that this ratechange event will trigger event listeners attached to the audioelement, even though it is not connected.
For more elaborate tests of event listeners, try this code too:
xxxxxxxxxxx
let one;
//bug nr 1 (important error!!)
const audio = document.createElement("audio");
audio.addEventListener("ratechange", e => console.log("ratechange bug1b", e.timeStamp));
audio.onratechange = function (e) {
this.remove(); //bug, somehow, this.remove triggers the onratechange callback a second time.
console.log("ratechange bug1", e.timeStamp);
if (!one)
one = e;
else if (one !== e)
console.log("the extra event is not the same event as the first event");
};
document.head.appendChild(audio);
audio.playbackRate = 0.1;
xxxxxxxxxxxxxxx
Suggestion for bugfix. The ratechange event should not be fired when the element is taken out of the DOM. I imagine that this resetting of the audio element is done because the audio element might be reused, but this internal reset should not trigger any event listeners on the object. Also, please, please, please do not fix this bug by simply filtering the dispatch of the event when the audio element is not connected to the DOM. I don't think you would do that, but if you do that, that would provide a much worse API and it would break with the behavior of the other browsers and other events. Having events being dispatched on elements no longer connected to the DOM has many usecases which cannot be handled in any other way.
Hi orstavik77,
Thanks for reporting this bug and for providing us with your insights.
I'll add this ticket to the Core: WebAudio component in the hope that someone from their team can provide their feedback and analyse this case.
Regards,
Virginia
Comment 3•5 years ago
|
||
-> Playback for <audio> HTML 5 media elements.
Web Audio is a different API.
Comment 4•5 years ago
|
||
I've put the code from the description in a fiddle here:
Comment 5•5 years ago
|
||
I have refined the fiddle to be a little more illustrative. Apparently removing the audio element sets its playback rate to 1, which is what results in the extra event:
Comment 6•5 years ago
|
||
Perhaps we're running the "media element load algorithm" when the element is removed:
https://www.w3.org/TR/2011/WD-html5-20110113/video.html#media-element-load-algorithm
| Assignee | ||
Comment 7•5 years ago
|
||
I can reproduce this, will take a look.
| Assignee | ||
Comment 8•5 years ago
|
||
This patch will
- run internal pause when removing a media from a document, instead of running
pause()method
The advantage of doing so is
- to follow the spec [1]. In step 3, it mentions we should run internal pause, not pause.
[1] https://html.spec.whatwg.org/#playing-the-media-resource:remove-an-element-from-a-document
| Assignee | ||
Comment 9•5 years ago
|
||
Comment 10•5 years ago
|
||
Comment 11•5 years ago
|
||
| bugherder | ||
https://hg.mozilla.org/mozilla-central/rev/dc2606225a5c
https://hg.mozilla.org/mozilla-central/rev/cb580c56e5db
Description
•