Closed Bug 1642590 Opened 5 years ago Closed 5 years ago

ratechange fires twice

Categories

(Core :: Audio/Video: Playback, defect, P3)

76 Branch
defect

Tracking

()

RESOLVED FIXED
mozilla79
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

Additional info:

  1. Chrome produces the expected results.
  2. The toggle event is just added to show how the expected result looks in a similar element/event pair in FF.
  3. The second bug is a NotFoundError printed when there should be no such error.
  4. 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

Component: Untriaged → Web Audio
Product: Firefox → Core

-> Playback for <audio> HTML 5 media elements.
Web Audio is a different API.

Component: Web Audio → Audio/Video: Playback

I've put the code from the description in a fiddle here:

https://jsfiddle.net/qbv5ag7f/

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:

https://jsfiddle.net/qbv5ag7f/1/

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

I can reproduce this, will take a look.

Assignee: nobody → alwu
Severity: -- → S4
Priority: -- → P3

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

Pushed by alwu@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/dc2606225a5c part1 : run internal pause when unbind media from DOM tree. r=bryce https://hg.mozilla.org/integration/autoland/rev/cb580c56e5db part2 : add test. r=bryce
Status: UNCONFIRMED → RESOLVED
Closed: 5 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla79
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: