@@ -314,6 +314,8 @@ void HTMLMediaElement::set_current_playback_position(double playback_position)
314314 // which these steps should be invoked, which is when we've reached the end of the media playback.
315315 if (m_current_playback_position == m_duration)
316316 reached_end_of_media_playback ();
317+
318+ upon_has_ended_playback_possibly_changed ();
317319}
318320
319321// https://html.spec.whatwg.org/multipage/media.html#dom-media-duration
@@ -332,8 +334,10 @@ bool HTMLMediaElement::ended() const
332334{
333335 // The ended attribute must return true if, the last time the event loop reached step 1, the media element had ended
334336 // playback and the direction of playback was forwards, and false otherwise.
335- // FIXME: Add a hook into EventLoop::process() to be notified when step 1 is reached.
336- return has_ended_playback () && direction_of_playback () == PlaybackDirection::Forwards;
337+ // NOTE: We queue a task to set this at event loop step 1 whenever something happens that may affect the resulting value.
338+ // Currently, that is when the ready state changes, when the current playback position changes, or the duration
339+ // changes.
340+ return m_ended;
337341}
338342
339343// https://html.spec.whatwg.org/multipage/media.html#durationChange
@@ -354,6 +358,8 @@ void HTMLMediaElement::set_duration(double duration)
354358
355359 m_duration = duration;
356360
361+ upon_has_ended_playback_possibly_changed ();
362+
357363 if (auto * paintable = this ->paintable ())
358364 paintable->set_needs_display ();
359365}
@@ -1456,6 +1462,7 @@ void HTMLMediaElement::set_ready_state(ReadyState ready_state)
14561462{
14571463 ScopeGuard guard { [&] {
14581464 m_ready_state = ready_state;
1465+ upon_has_ended_playback_possibly_changed ();
14591466 set_needs_style_update (true );
14601467 } };
14611468
@@ -1863,6 +1870,11 @@ void HTMLMediaElement::set_paused(bool paused)
18631870 set_needs_style_update (true );
18641871}
18651872
1873+ void HTMLMediaElement::set_ended (bool ended)
1874+ {
1875+ m_ended = ended;
1876+ }
1877+
18661878// https://html.spec.whatwg.org/multipage/media.html#dom-media-defaultplaybackrate
18671879void HTMLMediaElement::set_default_playback_rate (double new_value)
18681880{
@@ -1984,7 +1996,11 @@ bool HTMLMediaElement::has_ended_playback() const
19841996 direction_of_playback () == PlaybackDirection::Forwards &&
19851997
19861998 // The media element does not have a loop attribute specified.
1987- !has_attribute (HTML::AttributeNames::loop)) {
1999+ // AD-HOC: Use the value of the loop attribute from the last time we reached end of playback.
2000+ // Without this change, the ended attribute changes when enabling the loop attribute after
2001+ // playback has ended, and playback will not restart when playing the element.
2002+ // See https://github.com/whatwg/html/issues/11775
2003+ !m_loop_was_specified_when_reaching_end_of_media_resource) {
19882004 return true ;
19892005 }
19902006
@@ -2001,11 +2017,21 @@ bool HTMLMediaElement::has_ended_playback() const
20012017 return false ;
20022018}
20032019
2020+ void HTMLMediaElement::upon_has_ended_playback_possibly_changed ()
2021+ {
2022+ run_when_event_loop_reaches_step_1 (GC::Function<void ()>::create (heap (), [&] {
2023+ // The ended attribute must return true if, the last time the event loop reached step 1, the media element had ended
2024+ // playback and the direction of playback was forwards, and false otherwise.
2025+ set_ended (has_ended_playback () && direction_of_playback () == PlaybackDirection::Forwards);
2026+ }));
2027+ }
2028+
20042029// https://html.spec.whatwg.org/multipage/media.html#reaches-the-end
20052030void HTMLMediaElement::reached_end_of_media_playback ()
20062031{
20072032 // 1. If the media element has a loop attribute specified,
2008- if (has_attribute (HTML::AttributeNames::loop)) {
2033+ m_loop_was_specified_when_reaching_end_of_media_resource = has_attribute (HTML::AttributeNames::loop);
2034+ if (m_loop_was_specified_when_reaching_end_of_media_resource) {
20092035 // then seek to the earliest possible position of the media resource and return.
20102036 seek_element (0 );
20112037 // FIXME: Tell PlaybackManager that we're looping to allow data providers to decode frames ahead when looping
0 commit comments