mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-15 23:09:05 +00:00
LibWeb: Do not spin the event loop awaiting text track state changes
Some checks are pending
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Some checks are pending
CI / macOS, arm64, Sanitizer_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers_CI, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer_CI, Clang (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
The text track processing model would previously spin forever waiting for the track URL to change. It would then recursively invoke itself to handle the new URL, again entering the spin loop. This meant that tracks could easily cause event loop hangs. We now have an observer system to be notified when the track state changes instead. This lets us exit the processing model and move on.
This commit is contained in:
parent
24ac860998
commit
de34143a4e
Notes:
github-actions[bot]
2025-06-12 16:26:50 +00:00
Author: https://github.com/trflynn89
Commit: de34143a4e
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5068
Reviewed-by: https://github.com/tcl3
11 changed files with 182 additions and 17 deletions
|
@ -543,6 +543,7 @@ set(SOURCES
|
||||||
HTML/TextTrackCue.cpp
|
HTML/TextTrackCue.cpp
|
||||||
HTML/TextTrackCueList.cpp
|
HTML/TextTrackCueList.cpp
|
||||||
HTML/TextTrackList.cpp
|
HTML/TextTrackList.cpp
|
||||||
|
HTML/TextTrackObserver.cpp
|
||||||
HTML/Timer.cpp
|
HTML/Timer.cpp
|
||||||
HTML/TimeRanges.cpp
|
HTML/TimeRanges.cpp
|
||||||
HTML/ToggleEvent.cpp
|
HTML/ToggleEvent.cpp
|
||||||
|
|
|
@ -626,6 +626,7 @@ class TextTrack;
|
||||||
class TextTrackCue;
|
class TextTrackCue;
|
||||||
class TextTrackCueList;
|
class TextTrackCueList;
|
||||||
class TextTrackList;
|
class TextTrackList;
|
||||||
|
class TextTrackObserver;
|
||||||
class Timer;
|
class Timer;
|
||||||
class TimeRanges;
|
class TimeRanges;
|
||||||
class ToggleEvent;
|
class ToggleEvent;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <LibWeb/HTML/HTMLTrackElement.h>
|
#include <LibWeb/HTML/HTMLTrackElement.h>
|
||||||
#include <LibWeb/HTML/PotentialCORSRequest.h>
|
#include <LibWeb/HTML/PotentialCORSRequest.h>
|
||||||
#include <LibWeb/HTML/TextTrack.h>
|
#include <LibWeb/HTML/TextTrack.h>
|
||||||
|
#include <LibWeb/HTML/TextTrackObserver.h>
|
||||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
@ -37,12 +38,14 @@ void HTMLTrackElement::initialize(JS::Realm& realm)
|
||||||
Base::initialize(realm);
|
Base::initialize(realm);
|
||||||
|
|
||||||
m_track = TextTrack::create(realm);
|
m_track = TextTrack::create(realm);
|
||||||
|
m_track_observer = realm.create<TextTrackObserver>(realm, *m_track);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLTrackElement::visit_edges(Cell::Visitor& visitor)
|
void HTMLTrackElement::visit_edges(Cell::Visitor& visitor)
|
||||||
{
|
{
|
||||||
Base::visit_edges(visitor);
|
Base::visit_edges(visitor);
|
||||||
visitor.visit(m_track);
|
visitor.visit(m_track);
|
||||||
|
visitor.visit(m_track_observer);
|
||||||
visitor.visit(m_fetch_algorithms);
|
visitor.visit(m_fetch_algorithms);
|
||||||
visitor.visit(m_fetch_controller);
|
visitor.visit(m_fetch_controller);
|
||||||
}
|
}
|
||||||
|
@ -132,16 +135,28 @@ void HTMLTrackElement::set_track_url(String track_url)
|
||||||
|
|
||||||
m_track_url = move(track_url);
|
m_track_url = move(track_url);
|
||||||
|
|
||||||
|
auto track_is_hidden_or_showing = first_is_one_of(m_track->mode(), Bindings::TextTrackMode::Hidden, Bindings::TextTrackMode::Showing);
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model
|
// https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model
|
||||||
if (m_loading && m_fetch_controller && first_is_one_of(m_track->mode(), Bindings::TextTrackMode::Hidden, Bindings::TextTrackMode::Showing)) {
|
if (m_loading && m_fetch_controller && track_is_hidden_or_showing) {
|
||||||
m_loading = false;
|
m_loading = false;
|
||||||
m_fetch_controller->abort(realm(), {});
|
m_fetch_controller->abort(realm(), {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model
|
||||||
|
if (m_awaiting_track_url_change && track_is_hidden_or_showing) {
|
||||||
|
m_awaiting_track_url_change = false;
|
||||||
|
|
||||||
|
// 13. Jump to the step labeled top.
|
||||||
|
start_the_track_processing_model_parallel_steps();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model
|
// https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model
|
||||||
void HTMLTrackElement::start_the_track_processing_model()
|
void HTMLTrackElement::start_the_track_processing_model()
|
||||||
{
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
|
||||||
// 1. If another occurrence of this algorithm is already running for this text track and its track element, return,
|
// 1. If another occurrence of this algorithm is already running for this text track and its track element, return,
|
||||||
// letting that other algorithm take care of this element.
|
// letting that other algorithm take care of this element.
|
||||||
if (m_loading)
|
if (m_loading)
|
||||||
|
@ -158,14 +173,15 @@ void HTMLTrackElement::start_the_track_processing_model()
|
||||||
m_loading = true;
|
m_loading = true;
|
||||||
|
|
||||||
// 4. Run the remainder of these steps in parallel, allowing whatever caused these steps to run to continue.
|
// 4. Run the remainder of these steps in parallel, allowing whatever caused these steps to run to continue.
|
||||||
auto& realm = this->realm();
|
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this]() {
|
||||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [this, &realm] {
|
start_the_track_processing_model_parallel_steps();
|
||||||
start_the_track_processing_model_parallel_steps(realm);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLTrackElement::start_the_track_processing_model_parallel_steps(JS::Realm& realm)
|
void HTMLTrackElement::start_the_track_processing_model_parallel_steps()
|
||||||
{
|
{
|
||||||
|
auto& realm = this->realm();
|
||||||
|
|
||||||
// 5. Top: Await a stable state. The synchronous section consists of the following steps.
|
// 5. Top: Await a stable state. The synchronous section consists of the following steps.
|
||||||
|
|
||||||
// 6. ⌛ Set the text track readiness state to loading.
|
// 6. ⌛ Set the text track readiness state to loading.
|
||||||
|
@ -236,17 +252,22 @@ void HTMLTrackElement::start_the_track_processing_model_parallel_steps(JS::Realm
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. Wait until the text track readiness state is no longer set to loading.
|
// 11. Wait until the text track readiness state is no longer set to loading.
|
||||||
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.heap(), [this] {
|
if (m_track->readiness_state() == TextTrack::ReadinessState::Loading) {
|
||||||
return m_track->readiness_state() != TextTrack::ReadinessState::Loading;
|
m_track_observer->set_track_readiness_observer([this, url = move(url)](TextTrack::ReadinessState) mutable {
|
||||||
}));
|
if (m_track->readiness_state() != TextTrack::ReadinessState::Loading)
|
||||||
|
track_became_ready();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
track_became_ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTMLTrackElement::track_became_ready()
|
||||||
|
{
|
||||||
|
m_track_observer->set_track_readiness_observer({});
|
||||||
|
|
||||||
// 12. Wait until the track URL is no longer equal to URL, at the same time as the text track mode is set to hidden or showing.
|
// 12. Wait until the track URL is no longer equal to URL, at the same time as the text track mode is set to hidden or showing.
|
||||||
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.heap(), [this, url = move(url)] {
|
m_awaiting_track_url_change = true;
|
||||||
return track_url() != url && first_is_one_of(m_track->mode(), Bindings::TextTrackMode::Hidden, Bindings::TextTrackMode::Showing);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 13. Jump to the step labeled top.
|
|
||||||
start_the_track_processing_model_parallel_steps(realm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTMLTrackElement::track_failed_to_load()
|
void HTMLTrackElement::track_failed_to_load()
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/Forward.h>
|
||||||
#include <LibWeb/HTML/HTMLElement.h>
|
#include <LibWeb/HTML/HTMLElement.h>
|
||||||
#include <LibWeb/HTML/TextTrack.h>
|
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
@ -33,8 +33,9 @@ private:
|
||||||
void set_track_url(String);
|
void set_track_url(String);
|
||||||
|
|
||||||
void start_the_track_processing_model();
|
void start_the_track_processing_model();
|
||||||
void start_the_track_processing_model_parallel_steps(JS::Realm& realm);
|
void start_the_track_processing_model_parallel_steps();
|
||||||
|
|
||||||
|
void track_became_ready();
|
||||||
void track_failed_to_load();
|
void track_failed_to_load();
|
||||||
|
|
||||||
// ^DOM::Element
|
// ^DOM::Element
|
||||||
|
@ -42,6 +43,7 @@ private:
|
||||||
virtual void inserted() override;
|
virtual void inserted() override;
|
||||||
|
|
||||||
GC::Ptr<TextTrack> m_track;
|
GC::Ptr<TextTrack> m_track;
|
||||||
|
GC::Ptr<TextTrackObserver> m_track_observer;
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/media.html#track-url
|
// https://html.spec.whatwg.org/multipage/media.html#track-url
|
||||||
String m_track_url {};
|
String m_track_url {};
|
||||||
|
@ -50,6 +52,7 @@ private:
|
||||||
GC::Ptr<Fetch::Infrastructure::FetchController> m_fetch_controller;
|
GC::Ptr<Fetch::Infrastructure::FetchController> m_fetch_controller;
|
||||||
|
|
||||||
bool m_loading { false };
|
bool m_loading { false };
|
||||||
|
bool m_awaiting_track_url_change { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <LibWeb/Bindings/Intrinsics.h>
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
#include <LibWeb/HTML/EventNames.h>
|
#include <LibWeb/HTML/EventNames.h>
|
||||||
#include <LibWeb/HTML/TextTrack.h>
|
#include <LibWeb/HTML/TextTrack.h>
|
||||||
|
#include <LibWeb/HTML/TextTrackObserver.h>
|
||||||
|
|
||||||
namespace Web::HTML {
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
@ -31,6 +32,12 @@ void TextTrack::initialize(JS::Realm& realm)
|
||||||
Base::initialize(realm);
|
Base::initialize(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextTrack::visit_edges(Cell::Visitor& visitor)
|
||||||
|
{
|
||||||
|
Base::visit_edges(visitor);
|
||||||
|
visitor.visit(m_observers);
|
||||||
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/media.html#dom-texttrack-kind
|
// https://html.spec.whatwg.org/multipage/media.html#dom-texttrack-kind
|
||||||
Bindings::TextTrackKind TextTrack::kind()
|
Bindings::TextTrackKind TextTrack::kind()
|
||||||
{
|
{
|
||||||
|
@ -98,6 +105,28 @@ WebIDL::CallbackType* TextTrack::oncuechange()
|
||||||
return event_handler_attribute(HTML::EventNames::cuechange);
|
return event_handler_attribute(HTML::EventNames::cuechange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextTrack::set_readiness_state(ReadinessState readiness_state)
|
||||||
|
{
|
||||||
|
m_readiness_state = readiness_state;
|
||||||
|
|
||||||
|
for (auto observer : m_observers) {
|
||||||
|
if (auto callback = observer->track_readiness_observer())
|
||||||
|
callback->function()(m_readiness_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextTrack::register_observer(Badge<TextTrackObserver>, TextTrackObserver& observer)
|
||||||
|
{
|
||||||
|
auto result = m_observers.set(observer);
|
||||||
|
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextTrack::unregister_observer(Badge<TextTrackObserver>, TextTrackObserver& observer)
|
||||||
|
{
|
||||||
|
bool was_removed = m_observers.remove(observer);
|
||||||
|
VERIFY(was_removed);
|
||||||
|
}
|
||||||
|
|
||||||
Bindings::TextTrackKind text_track_kind_from_string(String value)
|
Bindings::TextTrackKind text_track_kind_from_string(String value)
|
||||||
{
|
{
|
||||||
// https://html.spec.whatwg.org/multipage/media.html#attr-track-kind
|
// https://html.spec.whatwg.org/multipage/media.html#attr-track-kind
|
||||||
|
|
|
@ -51,12 +51,16 @@ public:
|
||||||
WebIDL::CallbackType* oncuechange();
|
WebIDL::CallbackType* oncuechange();
|
||||||
|
|
||||||
ReadinessState readiness_state() { return m_readiness_state; }
|
ReadinessState readiness_state() { return m_readiness_state; }
|
||||||
void set_readiness_state(ReadinessState readiness_state) { m_readiness_state = readiness_state; }
|
void set_readiness_state(ReadinessState readiness_state);
|
||||||
|
|
||||||
|
void register_observer(Badge<TextTrackObserver>, TextTrackObserver&);
|
||||||
|
void unregister_observer(Badge<TextTrackObserver>, TextTrackObserver&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TextTrack(JS::Realm&);
|
TextTrack(JS::Realm&);
|
||||||
|
|
||||||
virtual void initialize(JS::Realm&) override;
|
virtual void initialize(JS::Realm&) override;
|
||||||
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
|
||||||
Bindings::TextTrackKind m_kind { Bindings::TextTrackKind::Subtitles };
|
Bindings::TextTrackKind m_kind { Bindings::TextTrackKind::Subtitles };
|
||||||
String m_label {};
|
String m_label {};
|
||||||
|
@ -67,6 +71,8 @@ private:
|
||||||
Bindings::TextTrackMode m_mode { Bindings::TextTrackMode::Disabled };
|
Bindings::TextTrackMode m_mode { Bindings::TextTrackMode::Disabled };
|
||||||
|
|
||||||
ReadinessState m_readiness_state { ReadinessState::NotLoaded };
|
ReadinessState m_readiness_state { ReadinessState::NotLoaded };
|
||||||
|
|
||||||
|
HashTable<GC::Ref<TextTrackObserver>> m_observers;
|
||||||
};
|
};
|
||||||
|
|
||||||
Bindings::TextTrackKind text_track_kind_from_string(String);
|
Bindings::TextTrackKind text_track_kind_from_string(String);
|
||||||
|
|
43
Libraries/LibWeb/HTML/TextTrackObserver.cpp
Normal file
43
Libraries/LibWeb/HTML/TextTrackObserver.cpp
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibJS/Runtime/Realm.h>
|
||||||
|
#include <LibJS/Runtime/VM.h>
|
||||||
|
#include <LibWeb/HTML/TextTrackObserver.h>
|
||||||
|
|
||||||
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
GC_DEFINE_ALLOCATOR(TextTrackObserver);
|
||||||
|
|
||||||
|
TextTrackObserver::TextTrackObserver(JS::Realm& realm, TextTrack& text_track)
|
||||||
|
: Bindings::PlatformObject(realm)
|
||||||
|
, m_text_track(text_track)
|
||||||
|
{
|
||||||
|
m_text_track->register_observer({}, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextTrackObserver::visit_edges(Cell::Visitor& visitor)
|
||||||
|
{
|
||||||
|
Base::visit_edges(visitor);
|
||||||
|
visitor.visit(m_text_track);
|
||||||
|
visitor.visit(m_track_readiness_observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextTrackObserver::finalize()
|
||||||
|
{
|
||||||
|
Base::finalize();
|
||||||
|
m_text_track->unregister_observer({}, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextTrackObserver::set_track_readiness_observer(Function<void(TextTrack::ReadinessState)> callback)
|
||||||
|
{
|
||||||
|
if (callback)
|
||||||
|
m_track_readiness_observer = GC::create_function(vm().heap(), move(callback));
|
||||||
|
else
|
||||||
|
m_track_readiness_observer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
Libraries/LibWeb/HTML/TextTrackObserver.h
Normal file
35
Libraries/LibWeb/HTML/TextTrackObserver.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibGC/Function.h>
|
||||||
|
#include <LibJS/Forward.h>
|
||||||
|
#include <LibWeb/Bindings/PlatformObject.h>
|
||||||
|
#include <LibWeb/Bindings/TextTrackPrototype.h>
|
||||||
|
#include <LibWeb/HTML/TextTrack.h>
|
||||||
|
|
||||||
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
class TextTrackObserver final : public Bindings::PlatformObject {
|
||||||
|
WEB_PLATFORM_OBJECT(TextTrackObserver, Bindings::PlatformObject);
|
||||||
|
GC_DECLARE_ALLOCATOR(TextTrackObserver);
|
||||||
|
|
||||||
|
public:
|
||||||
|
[[nodiscard]] GC::Ptr<GC::Function<void(TextTrack::ReadinessState)>> track_readiness_observer() const { return m_track_readiness_observer; }
|
||||||
|
void set_track_readiness_observer(Function<void(TextTrack::ReadinessState)>);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit TextTrackObserver(JS::Realm&, TextTrack&);
|
||||||
|
|
||||||
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
virtual void finalize() override;
|
||||||
|
|
||||||
|
GC::Ref<TextTrack> m_text_track;
|
||||||
|
GC::Ptr<GC::Function<void(TextTrack::ReadinessState)>> m_track_readiness_observer;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
4
Tests/LibWeb/Assets/track.vtt
Normal file
4
Tests/LibWeb/Assets/track.vtt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
WEBVTT
|
||||||
|
|
||||||
|
00:00:00.000 --> 00:00:04.299 line:80%
|
||||||
|
WFH!
|
|
@ -0,0 +1 @@
|
||||||
|
PASS!
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- Ensure the processing model does not block other resources from loading. -->
|
||||||
|
<img id="image" src="../../../Assets/120.png" />
|
||||||
|
<video>
|
||||||
|
<track src="../../../Assets/track.vtt" />
|
||||||
|
</video>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
asyncTest(done => {
|
||||||
|
const complete = () => {
|
||||||
|
println("PASS!");
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (image.complete) {
|
||||||
|
complete();
|
||||||
|
} else {
|
||||||
|
image.addEventListener("load", complete);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue