Skip to content

Commit 4c7319e

Browse files
committed
Bug 1707584 - part1 : implement :playing and :paused pseudo classes. r=media-playback-reviewers,firefox-style-system-reviewers,emilio,chunmin
This implements :playing [1] and :paused [2] pseudeo classes for media element. [1] https://html.spec.whatwg.org/multipage/semantics-other.html#selector-playing [2] https://html.spec.whatwg.org/multipage/semantics-other.html#selector-paused Differential Revision: https://phabricator.services.mozilla.com/D281037
1 parent 15d0f5e commit 4c7319e

File tree

13 files changed

+69
-22
lines changed

13 files changed

+69
-22
lines changed

dom/base/rust/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use bitflags::bitflags;
88
use malloc_size_of::malloc_size_of_is_0;
99

10-
pub const HEADING_LEVEL_OFFSET: usize = 52;
10+
pub const HEADING_LEVEL_OFFSET: usize = 53;
1111

1212
bitflags! {
1313
/// Event-based element states.
@@ -144,6 +144,8 @@ bitflags! {
144144
const ACTIVE_VIEW_TRANSITION = 1u64 << 50;
145145
/// For :-moz-suppress-for-print-selection.
146146
const SUPPRESS_FOR_PRINT_SELECTION = 1u64 << 51;
147+
/// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-paused
148+
const PAUSED = 1u64 << 52;
147149
/// https://drafts.csswg.org/selectors-5/#headings
148150
/// These 4 bits are used to pack the elements heading level into the element state
149151
/// Heading levels can be from 1-9 so 4 bits allows us to express the full range.

dom/media/mediaelement/HTMLMediaElement.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,6 +2526,7 @@ void HTMLMediaElement::AbortExistingLoads() {
25262526
// indirectly which depends on mPaused. So we need to update mPaused first.
25272527
if (!mPaused) {
25282528
mPaused = true;
2529+
UpdatePlaybackPseudoClasses();
25292530
PlayPromise::RejectPromises(TakePendingPlayPromises(),
25302531
NS_ERROR_DOM_MEDIA_ABORT_ERR);
25312532
}
@@ -3626,6 +3627,7 @@ void HTMLMediaElement::PauseInternal() {
36263627
}
36273628
bool oldPaused = mPaused;
36283629
mPaused = true;
3630+
UpdatePlaybackPseudoClasses();
36293631
// Step 1,
36303632
// https://html.spec.whatwg.org/multipage/media.html#internal-pause-steps
36313633
mCanAutoplayFlag = false;
@@ -4761,6 +4763,7 @@ void HTMLMediaElement::Init() {
47614763

47624764
OwnerDoc()->SetDocTreeHadMedia();
47634765
mShutdownObserver->Subscribe(this);
4766+
UpdatePlaybackPseudoClasses();
47644767
mInitialized = true;
47654768
}
47664769

@@ -5008,6 +5011,7 @@ void HTMLMediaElement::PlayInternal(bool aHandlingUserInput) {
50085011

50095012
const bool oldPaused = mPaused;
50105013
mPaused = false;
5014+
UpdatePlaybackPseudoClasses();
50115015
// Step 5,
50125016
// https://html.spec.whatwg.org/multipage/media.html#internal-play-steps
50135017
mCanAutoplayFlag = false;
@@ -5100,6 +5104,18 @@ void HTMLMediaElement::UpdateWakeLock() {
51005104
}
51015105
}
51025106

5107+
void HTMLMediaElement::UpdatePlaybackPseudoClasses() {
5108+
MOZ_ASSERT(NS_IsMainThread());
5109+
if (mPaused) {
5110+
// We don’t need to update the playing state because these states are
5111+
// exclusive, and the `:playing` pseudo-class is determined by checking
5112+
// the element's PAUSED state.
5113+
AddStates(ElementState::PAUSED);
5114+
} else {
5115+
RemoveStates(ElementState::PAUSED);
5116+
}
5117+
}
5118+
51035119
void HTMLMediaElement::CreateAudioWakeLockIfNeeded() {
51045120
MOZ_ASSERT(NS_IsMainThread());
51055121
if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) {
@@ -6725,6 +6741,8 @@ void HTMLMediaElement::CheckAutoplayDataReady() {
67256741
void HTMLMediaElement::RunAutoplay() {
67266742
mAllowedToPlayPromise.ResolveIfExists(true, __func__);
67276743
mPaused = false;
6744+
UpdatePlaybackPseudoClasses();
6745+
67286746
// We changed mPaused which can affect AddRemoveSelfReference
67296747
AddRemoveSelfReference();
67306748
UpdateSrcMediaStreamPlaying();
@@ -8068,6 +8086,7 @@ void HTMLMediaElement::AsyncResolvePendingPlayPromises() {
80688086
void HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError) {
80698087
if (!mPaused) {
80708088
mPaused = true;
8089+
UpdatePlaybackPseudoClasses();
80718090
QueueEvent(u"pause"_ns);
80728091
}
80738092

dom/media/mediaelement/HTMLMediaElement.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,11 @@ class HTMLMediaElement : public nsGenericHTMLElement,
915915
virtual void WakeLockRelease();
916916
virtual void UpdateWakeLock();
917917

918+
// This must be called immediately after monitor attributes change, and cannot
919+
// wait for the Watchable notification, because some pseudo-classes are
920+
// required to be applied immediately after the change.
921+
void UpdatePlaybackPseudoClasses();
922+
918923
void CreateAudioWakeLockIfNeeded();
919924
void ReleaseAudioWakeLockIfExists();
920925
void ReleaseAudioWakeLockInternal();

modules/libpref/init/StaticPrefList.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3624,6 +3624,13 @@
36243624
value: true
36253625
mirror: always
36263626

3627+
# Media element pseudo-classes
3628+
- name: dom.media.pseudo-classes.enabled
3629+
type: RelaxedAtomicBool
3630+
value: false
3631+
mirror: always
3632+
rust: true
3633+
36273634
# WebCodecs API
36283635
- name: dom.media.webcodecs.enabled
36293636
type: RelaxedAtomicBool

servo/components/style/gecko/non_ts_pseudo_class_list.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ macro_rules! apply_non_ts_list {
9292
("-moz-native-anonymous", MozNativeAnonymous, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
9393
("-moz-placeholder", MozPlaceholder, _, _),
9494

95+
// Media element pseudo classes
96+
("paused", Paused, PAUSED, _),
97+
("playing", Playing, PAUSED, _),
98+
99+
95100
// NOTE(emilio): Pseudo-classes below only depend on document state, and thus
96101
// conceptually they should probably be media queries instead.
97102
//

servo/components/style/gecko/selector_parser.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ impl NonTSPseudoClass {
227227
if matches!(*self, Self::Heading(..)) {
228228
return static_prefs::pref!("layout.css.heading-selector.enabled");
229229
}
230+
if matches!(*self, Self::Playing | Self::Paused) {
231+
return static_prefs::pref!("dom.media.pseudo-classes.enabled");
232+
}
230233
!self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME)
231234
}
232235

servo/components/style/gecko/wrapper.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,14 @@ impl<'le> GeckoElement<'le> {
932932
pub fn slow_selector_flags(&self) -> ElementSelectorFlags {
933933
slow_selector_flags_from_node_selector_flags(self.as_node().selector_flags())
934934
}
935+
936+
/// Returns whether this element is an HTML <video> or <audio> element.
937+
#[inline]
938+
pub fn is_html_media_element(&self) -> bool {
939+
self.is_html_element()
940+
&& (self.local_name().as_ptr() == local_name!("video").as_ptr()
941+
|| self.local_name().as_ptr() == local_name!("audio").as_ptr())
942+
}
935943
}
936944

937945
/// Convert slow selector flags from the raw `NodeSelectorFlags`.
@@ -2080,6 +2088,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
20802088
| NonTSPseudoClass::MozSuppressForPrintSelection => {
20812089
self.state().intersects(pseudo_class.state_flag())
20822090
},
2091+
NonTSPseudoClass::Paused => self.is_html_media_element() && self.state().intersects(ElementState::PAUSED),
2092+
NonTSPseudoClass::Playing => self.is_html_media_element() && !self.state().intersects(ElementState::PAUSED),
20832093
NonTSPseudoClass::Dir(ref dir) => self.state().intersects(dir.element_state()),
20842094
NonTSPseudoClass::ActiveViewTransitionType(ref types) => {
20852095
self.state().intersects(pseudo_class.state_flag())
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
[pseudo-classes-after-part.html]
2+
prefs: [dom.media.pseudo-classes.enabled:true]
23
["::part(mypart):future" should be a valid selector]
34
expected: FAIL
45

56
["::part(mypart):past" should be a valid selector]
67
expected: FAIL
78

8-
["::part(mypart):paused" should be a valid selector]
9-
expected: FAIL
10-
119
["::part(mypart):picture-in-picture" should be a valid selector]
1210
expected: FAIL
1311

14-
["::part(mypart):playing" should be a valid selector]
15-
expected: FAIL
16-
1712
["::part(mypart):xr-overlay" should be a valid selector]
1813
expected: FAIL
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
[media-pseudo-classes-in-has.html]
2+
prefs: [dom.media.pseudo-classes.enabled:true]
23
[Test :seeking pseudo-class]
34
expected: FAIL
45

56
[Test :muted pseudo-class]
67
expected: FAIL
7-
8-
[Test :playing pseudo-class]
9-
expected: FAIL
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
prefs: [dom.media.pseudo-classes.enabled:true]

0 commit comments

Comments
 (0)