Skip to content

Commit

Permalink
Bug 1722299 - Initial support for the color-scheme CSS property. r=ms…
Browse files Browse the repository at this point in the history
…tange

Add initial support for the color-scheme CSS property, allowing pages to
choose between light and dark system colors per-element, and such.

Things that are left to do so that this can be enabled by default:

 * Dark system colors on Windows / Android / Standins.
 * Dark Canvas/CanvasText/Link visited colors (which right now are set
   via PreferenceSheet).
 * Dark form controls in nsNativeBasicTheme.
 * Processing the color-scheme meta tag to fill-in
   Document::mColorSchemeBits.

But this seems like enough progress to be landable on its own.

Differential Revision: https://phabricator.services.mozilla.com/D120843
  • Loading branch information
emilio committed Jul 27, 2021
1 parent 97ce07f commit 02bbf03
Show file tree
Hide file tree
Showing 25 changed files with 245 additions and 147 deletions.
1 change: 1 addition & 0 deletions devtools/server/actors/animation-type-longhand.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
"color-adjust",
"color-interpolation",
"color-interpolation-filters",
"color-scheme",
"column-fill",
"column-rule-style",
"column-span",
Expand Down
5 changes: 5 additions & 0 deletions devtools/shared/css/generated/properties-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -2971,6 +2971,7 @@ exports.CSS_PROPERTIES = {
"-moz-user-focus",
"caret-color",
"accent-color",
"color-scheme",
"scrollbar-color",
"list-style-position",
"list-style-type",
Expand Down Expand Up @@ -11024,6 +11025,10 @@ exports.PREFERENCES = [
"backdrop-filter",
"layout.css.backdrop-filter.enabled"
],
[
"color-scheme",
"layout.css.color-scheme.enabled"
],
[
"d",
"layout.css.d-property.enabled"
Expand Down
13 changes: 13 additions & 0 deletions dom/base/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,14 @@ class Document : public nsINode,
void GetXMLDeclaration(nsAString& aVersion, nsAString& aEncoding,
nsAString& Standalone);

/**
* Returns the bits for the color-scheme specified by the
* <meta name="color-scheme">.
*
* TODO(emilio): Actually process the meta tag.
*/
uint8_t GetColorSchemeBits() const { return mColorSchemeBits; }

/**
* Returns true if this is what HTML 5 calls an "HTML document" (for example
* regular HTML document with Content-Type "text/html", image documents and
Expand Down Expand Up @@ -4759,6 +4767,11 @@ class Document : public nsINode,

uint8_t mXMLDeclarationBits;

// NOTE(emilio): Technically, this should be a StyleColorSchemeFlags, but we
// use uint8_t to avoid having to include a bunch of style system headers
// everywhere.
uint8_t mColorSchemeBits = 0;

// Currently active onload blockers.
uint32_t mOnloadBlockCount;

Expand Down
5 changes: 3 additions & 2 deletions layout/style/GeckoBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,8 @@ bool Gecko_IsDocumentBody(const Element* aElement) {
}

nscolor Gecko_GetLookAndFeelSystemColor(int32_t aId, const Document* aDoc,
StyleSystemColorScheme aColorScheme) {
StyleSystemColorScheme aColorScheme,
const StyleColorScheme* aStyle) {
auto colorId = static_cast<LookAndFeel::ColorID>(aId);
auto useStandins = LookAndFeel::ShouldUseStandins(*aDoc, colorId);
auto colorScheme = [&] {
Expand All @@ -700,7 +701,7 @@ nscolor Gecko_GetLookAndFeelSystemColor(int32_t aId, const Document* aDoc,
case StyleSystemColorScheme::Dark:
return LookAndFeel::ColorScheme::Dark;
}
return LookAndFeel::ColorSchemeForDocument(*aDoc);
return LookAndFeel::ColorSchemeForStyle(*aDoc, aStyle->bits);
}();

AutoWriteLock guard(*sServoFFILock);
Expand Down
3 changes: 2 additions & 1 deletion layout/style/GeckoBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,8 @@ bool Gecko_IsDocumentBody(const mozilla::dom::Element* element);
// because forward-declaring a nested enum/struct is impossible
nscolor Gecko_GetLookAndFeelSystemColor(int32_t color_id,
const mozilla::dom::Document*,
mozilla::StyleSystemColorScheme);
mozilla::StyleSystemColorScheme,
const mozilla::StyleColorScheme*);

int32_t Gecko_GetLookAndFeelInt(int32_t int_id);

Expand Down
1 change: 1 addition & 0 deletions layout/style/ServoBindings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ cbindgen-types = [
{ gecko = "StyleRestyleHint", servo = "crate::invalidation::element::restyle_hints::RestyleHint" },
{ gecko = "StyleTouchAction", servo = "crate::values::computed::TouchAction" },
{ gecko = "StyleWillChange", servo = "crate::values::specified::box_::WillChange" },
{ gecko = "StyleColorScheme", servo = "crate::values::specified::color::ColorScheme" },
{ gecko = "StyleTextDecorationLine", servo = "crate::values::computed::TextDecorationLine" },
{ gecko = "StyleTextTransform", servo = "crate::values::computed::TextTransform" },
{ gecko = "StyleTextUnderlinePosition", servo = "crate::values::computed::TextUnderlinePosition" },
Expand Down
9 changes: 6 additions & 3 deletions layout/style/nsStyleStruct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3077,7 +3077,8 @@ nsStyleUI::nsStyleUI(const Document& aDocument)
mCursor{{}, StyleCursorKind::Auto},
mAccentColor(StyleColorOrAuto::Auto()),
mCaretColor(StyleColorOrAuto::Auto()),
mScrollbarColor(StyleScrollbarColor::Auto()) {
mScrollbarColor(StyleScrollbarColor::Auto()),
mColorScheme(StyleColorScheme{{}, {}}) {
MOZ_COUNT_CTOR(nsStyleUI);
}

Expand All @@ -3090,7 +3091,8 @@ nsStyleUI::nsStyleUI(const nsStyleUI& aSource)
mCursor(aSource.mCursor),
mAccentColor(aSource.mAccentColor),
mCaretColor(aSource.mCaretColor),
mScrollbarColor(aSource.mScrollbarColor) {
mScrollbarColor(aSource.mScrollbarColor),
mColorScheme(aSource.mColorScheme) {
MOZ_COUNT_CTOR(nsStyleUI);
}

Expand Down Expand Up @@ -3140,7 +3142,8 @@ nsChangeHint nsStyleUI::CalcDifference(const nsStyleUI& aNewData) const {

if (mCaretColor != aNewData.mCaretColor ||
mAccentColor != aNewData.mAccentColor ||
mScrollbarColor != aNewData.mScrollbarColor) {
mScrollbarColor != aNewData.mScrollbarColor ||
mColorScheme != aNewData.mColorScheme) {
hint |= nsChangeHint_RepaintFrame;
}

Expand Down
1 change: 1 addition & 0 deletions layout/style/nsStyleStruct.h
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,7 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleUI {
mozilla::StyleColorOrAuto mAccentColor;
mozilla::StyleCaretColor mCaretColor;
mozilla::StyleScrollbarColor mScrollbarColor;
mozilla::StyleColorScheme mColorScheme;

inline mozilla::StylePointerEvents GetEffectivePointerEvents(
nsIFrame* aFrame) const;
Expand Down
21 changes: 21 additions & 0 deletions layout/style/test/property_database.js
Original file line number Diff line number Diff line change
Expand Up @@ -13357,6 +13357,27 @@ if (IsCSSPropertyPrefEnabled("layout.css.color-mix.enabled")) {
);
}

if (IsCSSPropertyPrefEnabled("layout.css.color-scheme.enabled")) {
gCSSProperties["color-scheme"] = {
domProp: "colorScheme",
inherited: true,
type: CSS_TYPE_LONGHAND,
initial_values: ["normal"],
other_values: [
"light",
"dark",
"light dark",
"light dark purple",
"light light dark",
"only light",
"only light dark",
"only light dark purple",
"light only",
],
invalid_values: ["only normal", "normal only", "only light only"],
};
}

// Copy aliased properties' fields from their alias targets. Keep this logic
// at the bottom of this file to ensure all the aliased properties are
// processed.
Expand Down
7 changes: 7 additions & 0 deletions modules/libpref/init/StaticPrefList.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6812,6 +6812,13 @@
mirror: always
rust: true

# Whether the `color-scheme` css property and meta tags are enabled.
- name: layout.css.color-scheme.enabled
type: RelaxedAtomicBool
value: false
mirror: always
rust: true

# The minimum contrast ratio between the accent color foreground and background
# colors.
#
Expand Down
11 changes: 11 additions & 0 deletions servo/components/style/properties/longhands/inherited_ui.mako.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ ${helpers.predefined_type(
has_effect_on_gecko_scrollbars=False,
)}

${helpers.predefined_type(
"color-scheme",
"ColorScheme",
"specified::color::ColorScheme::normal()",
engines="gecko",
spec="https://drafts.csswg.org/css-color-adjust/#color-scheme-prop",
gecko_pref="layout.css.color-scheme.enabled",
animation_value_type="discrete",
has_effect_on_gecko_scrollbars=False,
)}

${helpers.predefined_type(
"scrollbar-color",
"ui::ScrollbarColor",
Expand Down
3 changes: 3 additions & 0 deletions servo/components/style/properties/properties.mako.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,9 @@ impl LonghandId {
LonghandId::FontStyle |
LonghandId::FontFamily |

// color-scheme affects how system colors resolve.
LonghandId::ColorScheme |

// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff.
LonghandId::WritingMode |
Expand Down
2 changes: 2 additions & 0 deletions servo/components/style/values/computed/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use cssparser::{Color as CSSParserColor, RGBA};
use std::fmt;
use style_traits::{CssWriter, ToCss};

pub use crate::values::specified::color::ColorScheme;

/// The computed value of the `color` property.
pub type ColorPropertyValue = RGBA;

Expand Down
2 changes: 1 addition & 1 deletion servo/components/style/values/computed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme};
pub use self::column::ColumnCount;
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset};
pub use self::easing::TimingFunction;
Expand Down
126 changes: 125 additions & 1 deletion servo/components/style/values/specified/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::color::{GenericColorOrAuto, GenericCaretColor};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::Percentage;
use crate::values::CustomIdent;
use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA};
use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind};
use itoa;
Expand Down Expand Up @@ -401,7 +402,10 @@ impl SystemColor {
use crate::gecko_bindings::bindings;

let colors = &cx.device().pref_sheet_prefs().mColors;
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();

// TODO: At least Canvas / CanvasText should be color-scheme aware
// (probably the link colors too).
convert_nscolor_to_computedcolor(match *self {
SystemColor::Canvastext => colors.mDefault,
SystemColor::Canvas => colors.mDefaultBackground,
Expand All @@ -411,7 +415,7 @@ impl SystemColor {

_ => {
let color = unsafe {
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme)
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme)
};
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor();
Expand Down Expand Up @@ -860,3 +864,123 @@ impl Parse for CaretColor {
ColorOrAuto::parse(context, input).map(GenericCaretColor)
}
}

bitflags! {
/// Various flags to represent the color-scheme property in an efficient
/// way.
#[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[repr(C)]
#[value_info(other_values = "light,dark,only")]
pub struct ColorSchemeFlags: u8 {
/// Whether the author specified `light`.
const LIGHT = 1 << 0;
/// Whether the author specified `dark`.
const DARK = 1 << 1;
/// Whether the author specified `only`.
const ONLY = 1 << 2;
}
}

/// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop>
#[derive(
Clone,
Debug,
Default,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
#[value_info(other_values = "normal")]
pub struct ColorScheme {
#[ignore_malloc_size_of = "Arc"]
idents: crate::ArcSlice<CustomIdent>,
bits: ColorSchemeFlags,
}

impl ColorScheme {
/// Returns the `normal` value.
pub fn normal() -> Self {
Self {
idents: Default::default(),
bits: ColorSchemeFlags::empty(),
}
}
}

impl Parse for ColorScheme {
fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
let mut idents = vec![];
let mut bits = ColorSchemeFlags::empty();

let mut location = input.current_source_location();
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
let mut is_only = false;
match_ignore_ascii_case! { &ident,
"normal" => {
if idents.is_empty() && bits.is_empty() {
return Ok(Self::normal());
}
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
},
"light" => bits.insert(ColorSchemeFlags::LIGHT),
"dark" => bits.insert(ColorSchemeFlags::DARK),
"only" => {
if bits.intersects(ColorSchemeFlags::ONLY) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
bits.insert(ColorSchemeFlags::ONLY);
is_only = true;
},
_ => {},
};

if is_only {
if !idents.is_empty() {
// Only is allowed either at the beginning or at the end,
// but not in the middle.
break;
}
} else {
idents.push(CustomIdent::from_ident(location, &ident, &[])?);
}
location = input.current_source_location();
}

if idents.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}

Ok(Self {
idents: crate::ArcSlice::from_iter(idents.into_iter()),
bits,
})
}
}

impl ToCss for ColorScheme {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.idents.is_empty() {
debug_assert!(self.bits.is_empty());
return dest.write_str("normal");
}
let mut first = true;
for ident in self.idents.iter() {
if !first {
dest.write_char(' ')?;
}
first = false;
ident.to_css(dest)?;
}
if self.bits.intersects(ColorSchemeFlags::ONLY) {
dest.write_str(" only")?;
}
Ok(())
}
}
2 changes: 1 addition & 1 deletion servo/components/style/values/specified/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme};
pub use self::column::ColumnCount;
pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset};
pub use self::easing::TimingFunction;
Expand Down
1 change: 1 addition & 0 deletions servo/ports/geckolib/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ include = [
"BorderImageSlice",
"BorderSpacing",
"BorderRadius",
"ColorScheme",
"NonNegativeLengthOrNumberRect",
"Perspective",
"ZIndex",
Expand Down
1 change: 1 addition & 0 deletions testing/web-platform/meta/css/css-color-adjust/__dir__.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
prefs: [layout.css.color-scheme.enabled:true]

This file was deleted.

Loading

0 comments on commit 02bbf03

Please sign in to comment.