Skip to content

Commit 2974b49

Browse files
committed
[:has() pseudo-class] Support invalidation for :buffering and :stalled pseudo-classes
https://bugs.webkit.org/show_bug.cgi?id=249455 rdar://103438119 Reviewed by Antti Koivisto. * LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/media-loading-pseudo-classes-in-has-expected.txt: Added. * LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/media-loading-pseudo-classes-in-has.html: Added. * LayoutTests/platform/gtk/imported/w3c/web-platform-tests/css/selectors/invalidation/media-loading-pseudo-classes-in-has-expected.txt: Added. * Source/WebCore/html/HTMLMediaElement.cpp: (WebCore::HTMLMediaElement::parseAttribute): Remove `invalidateStyle()` call since we already switched it to PseudoClassChangeInvalidation in 257991@main (WebCore::HTMLMediaElement::setNetworkState): (WebCore::HTMLMediaElement::setReadyState): (WebCore::HTMLMediaElement::progressEventTimerFired): (WebCore::HTMLMediaElement::setPaused): (WebCore::HTMLMediaElement::updateBufferingState): (WebCore::HTMLMediaElement::updateStalledState): (WebCore::HTMLMediaElement::buffering const): Deleted. (WebCore::HTMLMediaElement::stalled const): Deleted. * Source/WebCore/html/HTMLMediaElement.h: (WebCore::HTMLMediaElement::buffering const): (WebCore::HTMLMediaElement::stalled const): Canonical link: https://commits.webkit.org/258891@main
1 parent aceba59 commit 2974b49

File tree

5 files changed

+121
-17
lines changed

5 files changed

+121
-17
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3+
PASS Test :has(:stalled) invalidation
4+
PASS Test :has(:buffering) invalidation
5+
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<!DOCTYPE html>
2+
<meta name="timeout" content="long" />
3+
<title>:has() invalidation with :buffering & :stalled pseudo-classes</title>
4+
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
5+
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
6+
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors/#media-loading-state">
7+
8+
<style>
9+
#subject:has(video:buffering) {
10+
background-color: blue;
11+
}
12+
#subject:has(video:stalled) {
13+
border-color: green;
14+
}
15+
</style>
16+
17+
<div id="subject">
18+
<video width="300" height="300" muted loop controls></video>
19+
</div>
20+
21+
<script src="/resources/testharness.js"></script>
22+
<script src="/resources/testharnessreport.js"></script>
23+
<script>
24+
const BLUE = "rgb(0, 0, 255)";
25+
const GREEN = "rgb(0, 128, 0)";
26+
27+
function assert_buffering_state(buffering) {
28+
if (buffering)
29+
assert_equals(getComputedStyle(subject).backgroundColor, BLUE);
30+
else
31+
assert_not_equals(getComputedStyle(subject).backgroundColor, BLUE);
32+
}
33+
34+
function assert_stalled_state(stalled) {
35+
if (stalled)
36+
assert_equals(getComputedStyle(subject).borderColor, GREEN);
37+
else
38+
assert_not_equals(getComputedStyle(subject).borderColor, GREEN);
39+
}
40+
41+
promise_test(async (t) => {
42+
const video = document.querySelector("video");
43+
assert_stalled_state(false);
44+
await new Promise((r) => {
45+
video.addEventListener("stalled", r, { once: true });
46+
video.src = `/media/counting.mp4?pipe=trickle(100:d1:r2)&random=${Math.random()}`;
47+
});
48+
assert_stalled_state(false);
49+
const promise = video.play();
50+
assert_stalled_state(true);
51+
video.src = "";
52+
// Wait for the video to abort trying to play
53+
try {
54+
await promise;
55+
} catch (err) {}
56+
await video.pause();
57+
assert_stalled_state(false);
58+
}, "Test :has(:stalled) invalidation");
59+
60+
promise_test(async (t) => {
61+
const video = document.querySelector("video");
62+
assert_buffering_state(false);
63+
await new Promise((r) => {
64+
video.addEventListener("stalled", r, { once: true });
65+
video.src = `/media/counting.mp4?pipe=trickle(100:d1:r2)&random=${Math.random()}`;
66+
});
67+
video.currentTime = 10;
68+
assert_buffering_state(false);
69+
const promise = video.play();
70+
assert_buffering_state(true);
71+
video.src = "";
72+
// Wait for the video to abort trying to play
73+
try {
74+
await promise;
75+
} catch (err) {}
76+
await video.pause();
77+
assert_buffering_state(false);
78+
}, "Test :has(:buffering) invalidation");
79+
</script>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
3+
Harness Error (TIMEOUT), message = null
4+
5+
TIMEOUT Test :has(:stalled) invalidation Test timed out
6+
NOTRUN Test :has(:buffering) invalidation
7+

Source/WebCore/html/HTMLMediaElement.cpp

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -828,10 +828,6 @@ void HTMLMediaElement::parseAttribute(const QualifiedName& name, const AtomStrin
828828
}
829829
else
830830
HTMLElement::parseAttribute(name, value);
831-
832-
// Changing the "muted" attribue could affect ":muted"
833-
if (name == mutedAttr)
834-
invalidateStyle();
835831
}
836832

837833
void HTMLMediaElement::finishParsingChildren()
@@ -2605,7 +2601,8 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
26052601
if (state == MediaPlayer::NetworkState::Empty) {
26062602
// Just update the cached state and leave, we can't do anything.
26072603
m_networkState = NETWORK_EMPTY;
2608-
invalidateStyle();
2604+
updateBufferingState();
2605+
updateStalledState();
26092606
return;
26102607
}
26112608

@@ -2635,7 +2632,8 @@ void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state)
26352632
m_completelyLoaded = true;
26362633
}
26372634

2638-
invalidateStyle();
2635+
updateBufferingState();
2636+
updateStalledState();
26392637
}
26402638

26412639
void HTMLMediaElement::changeNetworkStateFromLoadingToIdle()
@@ -2918,7 +2916,8 @@ void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state)
29182916
updateMediaController();
29192917
updateActiveTextTrackCues(currentMediaTime());
29202918

2921-
invalidateStyle();
2919+
updateBufferingState();
2920+
updateStalledState();
29222921
}
29232922

29242923
#if ENABLE(LEGACY_ENCRYPTED_MEDIA)
@@ -3270,13 +3269,13 @@ void HTMLMediaElement::progressEventTimerFired()
32703269
m_previousProgressTime = time;
32713270
if (m_sentStalledEvent) {
32723271
m_sentStalledEvent = false;
3273-
invalidateStyle();
3272+
updateStalledState();
32743273
}
32753274
updateRenderer();
32763275
} else if (timedelta > 3_s && !m_sentStalledEvent) {
32773276
scheduleEvent(eventNames().stalledEvent);
32783277
m_sentStalledEvent = true;
3279-
invalidateStyle();
3278+
updateStalledState();
32803279
setShouldDelayLoadEvent(false);
32813280
}
32823281
});
@@ -3796,8 +3795,8 @@ void HTMLMediaElement::setPaused(bool paused)
37963795
{ CSSSelector::PseudoClassPlaying, !paused },
37973796
});
37983797
m_paused = paused;
3799-
// FIXME: Use PseudoClassChangeInvalidation for :buffering/:stalling and remove the line below.
3800-
invalidateStyle();
3798+
updateBufferingState();
3799+
updateStalledState();
38013800
}
38023801

38033802
double HTMLMediaElement::defaultPlaybackRate() const
@@ -4280,7 +4279,7 @@ void HTMLMediaElement::setVolumeLocked(bool locked)
42804279
m_volumeLocked = locked;
42814280
}
42824281

4283-
bool HTMLMediaElement::buffering() const
4282+
void HTMLMediaElement::updateBufferingState()
42844283
{
42854284
// CSS Selectors Level 4; Editor's Draft, 2 July 2021
42864285
// <https://drafts.csswg.org/selectors/>
@@ -4291,10 +4290,14 @@ bool HTMLMediaElement::buffering() const
42914290
// but has not yet obtained enough data to resume playback. (Note that the element is still considered
42924291
// to be “playing” when it is “buffering”. Whenever :buffering matches an element, :playing also
42934292
// matches the element.)
4294-
return !paused() && m_networkState == NETWORK_LOADING && m_readyState <= HAVE_CURRENT_DATA;
4293+
bool buffering = !paused() && m_networkState == NETWORK_LOADING && m_readyState <= HAVE_CURRENT_DATA;
4294+
if (m_buffering != buffering) {
4295+
Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassBuffering, buffering);
4296+
m_buffering = buffering;
4297+
}
42954298
}
42964299

4297-
bool HTMLMediaElement::stalled() const
4300+
void HTMLMediaElement::updateStalledState()
42984301
{
42994302
// CSS Selectors Level 4; Editor's Draft, 2 July 2021
43004303
// <https://drafts.csswg.org/selectors/>
@@ -4306,7 +4309,11 @@ bool HTMLMediaElement::stalled() const
43064309
// stall timeout. [HTML] (Note that, like with the :buffering pseudo-class, the element is still
43074310
// considered to be “playing” when it is “stalled”. Whenever :stalled matches an element, :playing
43084311
// also matches the element.)
4309-
return !paused() && m_networkState == NETWORK_LOADING && m_readyState <= HAVE_CURRENT_DATA && m_sentStalledEvent;
4312+
bool stalled = !paused() && m_networkState == NETWORK_LOADING && m_readyState <= HAVE_CURRENT_DATA && m_sentStalledEvent;
4313+
if (m_stalled != stalled) {
4314+
Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassStalled, stalled);
4315+
m_stalled = stalled;
4316+
}
43104317
}
43114318

43124319
#if USE(AUDIO_SESSION) && PLATFORM(MAC)

Source/WebCore/html/HTMLMediaElement.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,12 @@ class HTMLMediaElement
336336

337337
bool volumeLocked() const { return m_volumeLocked; }
338338
WEBCORE_EXPORT void setVolumeLocked(bool);
339-
bool buffering() const;
340-
bool stalled() const;
339+
340+
bool buffering() const { return m_buffering; }
341+
void updateBufferingState();
342+
343+
bool stalled() const { return m_stalled; }
344+
void updateStalledState();
341345

342346
WEBCORE_EXPORT void togglePlayState();
343347
WEBCORE_EXPORT void beginScrubbing() override;
@@ -1149,6 +1153,8 @@ class HTMLMediaElement
11491153
bool m_initiallyMuted : 1;
11501154
bool m_paused : 1;
11511155
bool m_seeking : 1;
1156+
bool m_buffering : 1;
1157+
bool m_stalled : 1;
11521158
bool m_seekRequested : 1;
11531159
bool m_wasPlayingBeforeSeeking : 1;
11541160

0 commit comments

Comments
 (0)