Skip to content
Permalink
Browse files
[:has() pseudo-class] Support invalidation for :playing, :paused, :se…
…eking, :muted and :volume-locked pseudo-classes

https://bugs.webkit.org/show_bug.cgi?id=238994
rdar://91802717

Reviewed by Antti Koivisto.

* LayoutTests/platform/gtk/imported/w3c/web-platform-tests/css/selectors/invalidation/media-pseudo-classes-in-has-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/media-pseudo-classes-in-has-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/selectors/invalidation/media-pseudo-classes-in-has.html: Added.
* LayoutTests/media/media-css-volume-locked-expected.txt:
* LayoutTests/media/media-css-volume-locked.html:
* Source/WebCore/html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::seekWithTolerance):
(WebCore::HTMLMediaElement::seekTask):
(WebCore::HTMLMediaElement::clearSeeking):
(WebCore::HTMLMediaElement::setSeeking):
(WebCore::HTMLMediaElement::setPaused):
(WebCore::HTMLMediaElement::setMuted):
(WebCore::HTMLMediaElement::setVolumeLocked):
* Source/WebCore/html/HTMLMediaElement.h:

Canonical link: https://commits.webkit.org/257991@main
  • Loading branch information
nt1m committed Dec 16, 2022
1 parent 8ff0463 commit 703f285930dc2b9c229d5630872d7bd52858171d
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 9 deletions.
@@ -0,0 +1,6 @@
Test media pseudo-classes invalidation with :has()

PASS Test :playing pseudo-classes
PASS Test :seeking pseudo-class
PASS Test :muted pseudo-class

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<title>:has() invalidation with :playing, :paused, :seeking and :muted pseudo-classes</title>
<link rel="author" title="Tim Nguyen" href="https://github.com/nt1m">
<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
<link rel="help" href="https://w3c.github.io/csswg-drafts/selectors/#video-state">
<style>
#subject {
background-color: black;
accent-color: black;
color: black;
border: 2px solid black;
}
#subject:has(:muted) {
background-color: red;
}
#subject:has(:playing) {
border-color: green;
}
#subject:has(:paused) {
color: orange;
}
#subject:has(:seeking) {
accent-color: blue;
}
</style>
<body>
<div id="subject">
Test media pseudo-classes invalidation with :has()
<input type="checkbox">
<video width="300" height="300" loop></video>
</div>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
const GREEN = "rgb(0, 128, 0)";
const ORANGE = "rgb(255, 165, 0)";
const BLUE = "rgb(0, 0, 255)";
const RED = "rgb(255, 0, 0)";
const BLACK = "rgb(0, 0, 0)";

function assert_matches_muted(muted) {
assert_equals(getComputedStyle(subject).backgroundColor, muted ? RED : BLACK);
}

function assert_matches_playing(playing) {
assert_equals(getComputedStyle(subject).borderColor, playing ? GREEN : BLACK);
assert_equals(getComputedStyle(subject).color, !playing ? ORANGE : BLACK);
}

function assert_matches_seeking(seeking) {
assert_equals(getComputedStyle(subject).accentColor, seeking ? BLUE : BLACK);
}

promise_test(async (t) => {
t.add_cleanup(() => {
video.muted = false;
video.pause();
video.removeAttribute("src");
});
const video = document.querySelector("video");
assert_matches_muted(false);
assert_matches_playing(false);
assert_matches_seeking(false);
await new Promise((r) => {
video.addEventListener("canplay", r, { once: true });
video.src = "/media/counting.mp4";
});
video.muted = true; // allows us to play the video
assert_matches_muted(true);
await new Promise((r) => {
video.addEventListener("playing", r, { once: true });
video.play();
});
assert_matches_playing(true);
}, "Test :playing pseudo-classes");

promise_test(async (t) => {
t.add_cleanup(() => {
video.removeAttribute("src");
});
const video = document.querySelector("video");
assert_matches_muted(false);
assert_matches_playing(false);
assert_matches_seeking(false);
await new Promise((r) => {
video.addEventListener("canplay", r, { once: true });
video.src = "/media/counting.mp4";
});

assert_matches_seeking(false);
await new Promise((r) => {
video.addEventListener("seeking", r, { once: true });
video.currentTime = 10;
});
assert_matches_seeking(true);
}, "Test :seeking pseudo-class");

promise_test(async (t) => {
t.add_cleanup(() => {
video.removeAttribute("src");
});
const video = document.querySelector("video");
await new Promise((r) => {
video.addEventListener("canplay", r, { once: true });
video.src = "/media/counting.mp4";
});
assert_matches_muted(false);
video.muted = true;
assert_matches_muted(true);
video.muted = false;
assert_matches_muted(false);
}, "Test :muted pseudo-class");
</script>
</body>
@@ -4,8 +4,10 @@ EVENT(canplay)
RUN(internals.setMediaElementVolumeLocked(video, false))
EXPECTED (document.querySelector("video:volume-locked") == 'null') OK
EXPECTED (document.querySelector("video:not(:volume-locked)") == '[object HTMLVideoElement]') OK
EXPECTED (getComputedStyle(subject).backgroundColor == 'rgb(255, 0, 0)') OK
RUN(internals.setMediaElementVolumeLocked(video, true))
EXPECTED (document.querySelector("video:volume-locked") == '[object HTMLVideoElement]') OK
EXPECTED (document.querySelector("video:not(:volume-locked)") == 'null') OK
EXPECTED (getComputedStyle(subject).backgroundColor == 'rgb(0, 128, 0)') OK
END OF TEST

@@ -1,7 +1,15 @@
<!doctype HTML>
<html>
<head>
<title>media-css-muted</title>
<title>Test :volume-locked pseudo-class</title>
<style>
#subject {
background-color: red;
}
#subject:has(:volume-locked) {
background-color: green;
}
</style>
<script src="video-test.js"></script>
<script src="media-file.js"></script>
<script>
@@ -12,14 +20,18 @@
run('internals.setMediaElementVolumeLocked(video, false)');
testExpected('document.querySelector("video:volume-locked")', null);
testExpected('document.querySelector("video:not(:volume-locked)")', video);
testExpected('getComputedStyle(subject).backgroundColor', "rgb(255, 0, 0)");
run('internals.setMediaElementVolumeLocked(video, true)');
testExpected('document.querySelector("video:volume-locked")', video);
testExpected('document.querySelector("video:not(:volume-locked)")', null);
testExpected('getComputedStyle(subject).backgroundColor', "rgb(0, 128, 0)");
endTest();
});
</script>
<head>
<body>
<video></video>
<div id="subject">
<video></video>
</div>
</body>
</html>
@@ -0,0 +1,8 @@
Test media pseudo-classes invalidation with :has()

Harness Error (TIMEOUT), message = null

PASS Test :playing pseudo-classes
TIMEOUT Test :seeking pseudo-class Test timed out
NOTRUN Test :muted pseudo-class

@@ -94,6 +94,7 @@
#include "PlatformMediaSessionManager.h"
#include "PlatformTextTrack.h"
#include "ProgressTracker.h"
#include "PseudoClassChangeInvalidation.h"
#include "Quirks.h"
#include "RegistrableDomain.h"
#include "RenderLayerCompositor.h"
@@ -3430,7 +3431,7 @@ void HTMLMediaElement::seekWithTolerance(const MediaTime& inTime, const MediaTim

// 4 - Set the seeking IDL attribute to true.
// The flag will be cleared when the engine tells us the time has actually changed.
m_seeking = true;
setSeeking(true);
if (m_playing) {
if (m_lastSeekTime < now)
addPlayedRange(m_lastSeekTime, now);
@@ -3533,7 +3534,7 @@ void HTMLMediaElement::seekTask()
m_sentEndEvent = false;
m_lastSeekTime = time;
m_pendingSeekType = thisSeekType;
m_seeking = true;
setSeeking(true);

// 10 - Queue a task to fire a simple event named seeking at the element.
scheduleEvent(eventNames().seekingEvent);
@@ -3552,7 +3553,7 @@ void HTMLMediaElement::seekTask()

void HTMLMediaElement::clearSeeking()
{
m_seeking = false;
setSeeking(false);
m_seekRequested = false;
m_pendingSeekType = NoSeek;
m_wasPlayingBeforeSeeking = false;
@@ -3625,6 +3626,14 @@ bool HTMLMediaElement::seeking() const
return m_seeking;
}

void HTMLMediaElement::setSeeking(bool seeking)
{
if (m_seeking == seeking)
return;
Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassSeeking, seeking);
m_seeking = seeking;
}

void HTMLMediaElement::refreshCachedTime() const
{
if (!m_player)
@@ -3784,7 +3793,12 @@ void HTMLMediaElement::setPaused(bool paused)
{
if (m_paused == paused)
return;
Style::PseudoClassChangeInvalidation styleInvalidation(*this, {
{ CSSSelector::PseudoClassPaused, paused },
{ CSSSelector::PseudoClassPlaying, !paused },
});
m_paused = paused;
// FIXME: Use PseudoClassChangeInvalidation for :buffering/:stalling and remove the line below.
invalidateStyle();
}

@@ -4231,7 +4245,7 @@ void HTMLMediaElement::setMuted(bool muted)
if (hasAudio() && muted)
userDidInterfereWithAutoplay();
}

Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassMuted, muted);
m_muted = muted;
m_explicitlyMuted = true;

@@ -4254,8 +4268,6 @@ void HTMLMediaElement::setMuted(bool muted)
#endif
mediaSession().canProduceAudioChanged();
updateSleepDisabling();

invalidateStyle();
}

schedulePlaybackControlsManagerUpdate();
@@ -4266,8 +4278,8 @@ void HTMLMediaElement::setVolumeLocked(bool locked)
if (m_volumeLocked == locked)
return;

Style::PseudoClassChangeInvalidation styleInvalidation(*this, CSSSelector::PseudoClassVolumeLocked, locked);
m_volumeLocked = locked;
invalidateStyle();
}

bool HTMLMediaElement::buffering() const
@@ -241,6 +241,7 @@ class HTMLMediaElement
using HTMLMediaElementEnums::ReadyState;
ReadyState readyState() const override;
WEBCORE_EXPORT bool seeking() const;
void setSeeking(bool);

// playback state
WEBCORE_EXPORT double currentTime() const override;

0 comments on commit 703f285

Please sign in to comment.