Page MenuHomePhabricator

Bug 1707584 - part1 : implement :playing and :paused pseudo classes.
ClosedPublic

Authored by alwu on Jan 29 2026, 7:08 PM.
Referenced Files
Unknown Object (File)
Thu, Apr 16, 10:49 PM
Unknown Object (File)
Wed, Apr 15, 8:15 AM
Unknown Object (File)
Sat, Apr 11, 10:54 AM
Unknown Object (File)
Sat, Apr 11, 10:37 AM
Unknown Object (File)
Thu, Apr 9, 11:40 PM
Unknown Object (File)
Thu, Apr 9, 4:15 PM
Unknown Object (File)
Mon, Apr 6, 10:21 AM
Unknown Object (File)
Sun, Apr 5, 9:14 AM

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
alwu planned changes to this revision.Jan 29 2026, 7:42 PM
alwu updated this revision to Diff 1193788.
alwu edited the summary of this revision. (Show Details)
alwu requested review of this revision.Jan 29 2026, 11:25 PM
alwu updated this revision to Diff 1194082.
alwu retitled this revision from WIP: Bug 1707584 - part1 : implement :playing and :paused pseudo classes. to Bug 1707584 - part1 : implement :playing and :paused pseudo classes..
emilio requested changes to this revision.Jan 30 2026, 7:31 PM
emilio added a subscriber: emilio.

Seems this could be much simpler and avoid using extra bits, which we're a bit short of.

dom/base/rust/lib.rs
150

You only need one bit for these, right? They seem exclusive.

dom/media/mediaelement/HTMLMediaElement.cpp
5109

I think you should remove mPaused, and just have a PAUSED bit. Then just set that bit around.

E.g., instead of mPaused = true; you'd do AddStates(ElementState::PAUSED), etc.

servo/components/style/gecko/wrapper.rs
2092

Here you'd implement the matching like:

Playing => self.is_html_media_element() && !self.is_paused(),
Paused => self.is_html_media_element() && self.is_paused(),

With self.is_paused being self.state().intersects(ElementState::PAUSED)

This revision now requires changes to proceed.Jan 30 2026, 7:31 PM
alwu marked 2 inline comments as done.Jan 31 2026, 1:19 AM
alwu added inline comments.
dom/media/mediaelement/HTMLMediaElement.cpp
5109

I'd prefer to keep mPaused, because that is a watchable class which we can do something like this.

Considering UpdatePlaybackPseudoClasses() will do more just PLAYING and PAUSED states, it's fine to keep mPaused and set ElementState::PAUSED.

alwu marked an inline comment as done.Jan 31 2026, 1:41 AM
alwu added inline comments.
dom/base/rust/lib.rs
150

Hmm if I don't create a bit here (only declare :playing in non_ts_pseudo_class_list.rs) then I got this error, which seems the state bit is must to have?

error[E0599]: no associated item named `PLAYING` found for struct `dom::ElementState` in the current scope
alwu marked an inline comment as done.Jan 31 2026, 1:51 AM
alwu added inline comments.
dom/base/rust/lib.rs
150

As the build error, we can't remove the bit even if it's exclusive or not use (eg. :volume-locked in D281101).

dom/base/rust/lib.rs
150

As the build error, we can't remove the bit even if it's exclusive or not use (eg. :volume-locked in D281101).

We definitely have pseudo classes without state bits tho, see MozWindowInactive etc

dom/base/rust/lib.rs
150

(But also, you could just use the same state bits, it would be fine invalidation-wise)

dom/media/mediaelement/HTMLMediaElement.cpp
5109

I'd prefer to keep mPaused, because that is a watchable class which we can do something like this.

Ah, I missed the Watchable stuff, sorry! Seems fine then.

alwu requested review of this revision.Feb 2 2026, 7:41 PM
alwu updated this revision to Diff 1195918.
alwu marked 2 inline comments as done.Feb 2 2026, 7:44 PM
alwu added inline comments.
servo/components/style/gecko/non_ts_pseudo_class_list.rs
97

(But also, you could just use the same state bits, it would be fine invalidation-wise)

@emilio for playing, should I use PAUSED bit, or just keep it empty? Thanks!

emilio requested changes to this revision.Feb 3 2026, 1:15 PM

Add a test for playing and paused not matching on arbitrary elements please?

servo/components/style/gecko/non_ts_pseudo_class_list.rs
97

Probably PAUSED makes it easier for invalidation.

servo/components/style/gecko/wrapper.rs
2092

This is wrong, you also need to add the is_html_media_element() check, otherwise random <div> elements also match :playing, right?

This revision now requires changes to proceed.Feb 3 2026, 1:15 PM
alwu requested review of this revision.Feb 3 2026, 6:23 PM
alwu updated this revision to Diff 1196829.
alwu marked 3 inline comments as done.
dholbert added inline comments.
modules/libpref/init/StaticPrefList.yaml
3630

Perhaps it'd be good to add a patch at the end of this patch stack (or in a followup bug) that sets this pref to @IS_NIGHTLY_BUILD@?

(assuming any missing/known-broken bits are harmless enough for this to still feel safe to enable in Nightly at least)

modules/libpref/init/StaticPrefList.yaml
3630

Yes, that is exact what my plan is. I will file another bug to turn on the pref for Nightly, and then consider to enable it on all channels after fixing the problem for :stalled.

r=me with that fixed, but please add a test for the non-media element case matching in a followup please?

servo/components/selectors/tree.rs
133 ↗(On Diff #1197868)

No need to be in selectors, just in wrapper.rs

r=me with that fixed, but please add a test for the non-media element case matching in a followup please?

Could you point me out where I should add a test? do you have any reference? Thanks!

servo/components/selectors/tree.rs
133 ↗(On Diff #1197868)

@emilio If I don't add this, I will get the compile error like below. Do I miss something?

0:12.64 W error[E0407]: method `is_html_media_element` is not a member of trait `Element`
0:12.64 W    --> servo/components/style/invalidation/element/element_wrapper.rs:320:5
0:12.64 W     |
0:12.64 W 320 |       fn is_html_media_element(&self) -> bool {
0:12.64 W     |       ^  --------------------- help: there is an associated function with a similar name: `is_html_slot_element`
0:12.64 W     |  _____|
0:12.64 W     | |
0:12.64 W 321 | |         self.element.is_html_media_element()
0:12.64 W 322 | |     }
0:12.64 W     | |_____^ not a member of trait `Element`
0:12.98 W error[E0407]: method `is_html_media_element` is not a member of trait `selectors::Element`
0:12.98 W     --> servo/components/style/gecko/wrapper.rs:2265:5
0:12.98 W      |
0:12.98 W 2265 |       fn is_html_media_element(&self) -> bool {
0:12.98 W      |       ^  --------------------- help: there is an associated function with a similar name: `is_html_slot_element`
0:12.98 W      |  _____|
0:12.98 W      | |
0:12.98 W 2266 | |         self.is_html_element() && (self.local_name().as_ptr() == local_name!("video").as_ptr() || self.local_name().as_ptr() == local_name!("audio").as_ptr())
0:12.98 W 2267 | |     }
0:12.98 W      | |_____^ not a member of trait `selectors::Element`
alwu marked an inline comment as done.Feb 4 2026, 11:08 PM
alwu added inline comments.
servo/components/selectors/tree.rs
133 ↗(On Diff #1197868)

ah nvm, I know what I did wrong. Will update.

@emilio I’ve added the test case to media-playback-state.html and also removed the declaration from tree.js. Could you help take another look and let me know if everything looks correct? Thanks!

alwu marked an inline comment as done.Feb 4 2026, 11:16 PM
alwu added inline comments.
modules/libpref/init/StaticPrefList.yaml
3630

Will be done in D281809.

emilio added a project: testing-approved.

Looks great! Maybe we should have that for all the pseudo-classes? So something like:

for (let pseudoClass of [":playing", ":paused", ":seeking", ...]) {
  assert_true(CSS.supports(`selector(${pseudoClass})`));
  assert_false(div.matches(pseudoClass));
  assert_true(div.matches(`:not(${pseudoClass})`));
}

Or so, with nicer test messages and so on.

testing/web-platform/tests/css/selectors/media/media-playback-state.html
85

Would be simpler to do: assert_false(div.matches(":playing"), ...); etc.

alwu marked an inline comment as done.Feb 5 2026, 6:24 PM

Looks great! Maybe we should have that for all the pseudo-classes? So something like:

for (let pseudoClass of [":playing", ":paused", ":seeking", ...]) {
  assert_true(CSS.supports(`selector(${pseudoClass})`));
  assert_false(div.matches(pseudoClass));
  assert_true(div.matches(`:not(${pseudoClass})`));
}

Or so, with nicer test messages and so on.

I added :seeking in D281038, but considering media-playback-state.html is only about these three classes, I didn't add other classes into this test. Maybe we could have another separate test in the follow up to test all classes.

testing/web-platform/tests/css/selectors/media/media-playback-state.html
85

using assert_false results in an error assert_false: div should not match :playing expected false got null, so I think we should keep using `assert_equals.

chunmin added a subscriber: chunmin.
chunmin added inline comments.
dom/media/mediaelement/HTMLMediaElement.h
920

I guess mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdatePlaybackPseudoClasses) doesn't work?

This revision is now accepted and ready to land.Feb 5 2026, 6:32 PM
alwu marked 2 inline comments as done.Feb 5 2026, 6:54 PM
alwu added inline comments.
dom/media/mediaelement/HTMLMediaElement.h
920

Yes, we need to update the pseudo-class synchronously; using the watch manager would be too late.

alwu marked an inline comment as done.Feb 5 2026, 6:56 PM
testing/web-platform/tests/css/selectors/media/media-playback-state.html
85

using assert_false results in an error assert_false: div should not match :playing expected false got null, so I think we should keep using `assert_equals.

That doesn't make sense, you must have kept querySelector. matches() returns a boolean. Please double check?

testing/web-platform/tests/css/selectors/media/media-playback-state.html
85

My bad, you're right. I will have a follow up to fix this.

This revision is now accepted and ready to land.Feb 5 2026, 11:08 PM
alwu marked an inline comment as done.