Answer the user-preference media features from the render device - #235
Merged
Conversation
Adds IRenderDevicePreferences, a small interface with a single IReadOnlyDictionary<String, String> Preferences member that DefaultRenderDevice implements, so a host can say which user preferences its device carries without every existing IRenderDevice implementation having to change. A generic PreferenceFeatureValidator is registered for prefers-color-scheme, prefers-reduced-motion, prefers-reduced-transparency, prefers-contrast, prefers-reduced-data, forced-colors and display-mode, and hover/any-hover and pointer/any-pointer now read the dictionary when it carries them, while keeping their previous answer when it does not. A key that is not set leaves its feature unknown, i.e., the query does not match. Fixes AngleSharp#234 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NqCcJrL3MJecCPRBMQsZyC
FlorianRappl
reviewed
Sep 3, 2026
FlorianRappl
reviewed
Sep 3, 2026
FlorianRappl
left a comment
Contributor
There was a problem hiding this comment.
Looks good - I'd just make the RenderDeviceExtensions public as somebody might want to also have this convenience layer on top of the render device (in particular the DefaultRenderDevice).
Review feedback on AngleSharp#235: the convenience layer over IRenderDevice is useful to hosts that configure a DefaultRenderDevice, so the class is public and documented. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NqCcJrL3MJecCPRBMQsZyC
Contributor
Author
|
Done: |
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
IRenderDevicemodels the Media Queries Level 3/4 device features but none of the Level 5 user-preference features, so@media (prefers-color-scheme: dark)never applied in the cascade andmatchMedia("(prefers-color-scheme: dark)").matcheswas alwaysfalse— with no way for a host that has a preference (a headless browser emulating a dark theme, say) to say so.This is the non-breaking route picked in #234: a small
IRenderDevicePreferenceswith the singlePreferencesmember, already implemented byDefaultRenderDevice, which the validators probe withas. Nothing that implementsIRenderDevicetoday has to change.What is in it
IRenderDevicePreferencesbesideIRenderDevice, withIReadOnlyDictionary<String, String> Preferences.DefaultRenderDeviceimplements it with a settable property defaulting to an empty, case-insensitive dictionary; its existing members are untouched.PreferenceFeatureValidator, parameterized by the feature name, registered inDefaultFeatureValidatorFactoryfor every key below. It answersfalsewhen the device does not implement the interface or does not carry the key, and otherwise compares the queried keyword with the value case insensitively.hover/pointeralready had validators that read nothing from the device and assumed a headless browser ((hover: none)istrue). They now route through the dictionary when it carries the key and keep exactly that answer when it does not.any-hover/any-pointerwere unregistered — they are now registered and behave like their siblings, so(any-hover: none)answerstrueon a bare device where it previously answeredfalseas an unknown feature. That is the only behaviour change for a host that sets no preferences.FeatureNamesandCssKeywordsentries, and the key table inREADME.mdanddocs/general/05-Extensibility.md.prefers-color-schemelight,darkprefers-reduced-motionno-preference,reduceprefers-reduced-transparencyno-preference,reduceprefers-contrastno-preference,more,less,customprefers-reduced-datano-preference,reduceforced-colorsnone,activehover,any-hovernone,hoverpointer,any-pointernone,coarse,finedisplay-modefullscreen,standalone,minimal-ui,browserThe keyword is compared verbatim rather than against a hard-coded grammar per feature, so a value newer than this library (another
display-mode, anotherprefers-contrastlevel) works without a release — which is the point of the untyped dictionary. Used without a value the feature evaluates in a boolean context per MQ5 §3.1:(prefers-reduced-motion)istrueunless the value isno-preference,(forced-colors)unless it isnone, and(hover)istruewhen the value ishover.Tests
41 new tests in
src/AngleSharp.Css.Tests/Rules/CssMediaPreferenceFeatures.cs(validator level: each feature true / false / absent, boolean context, case insensitivity, a device that does not implement the interface, thehoverandpointerfallbacks) andsrc/AngleSharp.Css.Tests/Extensions/MediaPreferences.cs(matchMediaend to end beside the tests from #228, a third-party device implementing both interfaces, and@media (prefers-reduced-motion: reduce)applying a declaration underComputeCurrentStyle). Full suite: 2073 passing, 0 failing; solution builds with 0 warnings over all five target frameworks. Rebased ontodevelat 87a30c7, i.e., on top of the #230-#233 fixes.Run against the unfixed behaviour first, 31 of the 41 fail and 10 pass on both sides — the 10 are the ones asserting that an absent preference, or a device without the interface, changes nothing, plus the two pinning that
hoverandpointerkeep their current answer.Not in this PR
The four media-query evaluation defects filed alongside #234 (#230-#233) are untouched here - they are already fixed on
devel, and this branch is rebased on top of those fixes.Fixes #234
🤖 Generated with Claude Code
https://claude.ai/code/session_01NqCcJrL3MJecCPRBMQsZyC