Bug 1494034 - Add support for CSS prefers-color-scheme media feature. r=emilio
authorJonathan Kingston <jkt@mozilla.com>
Fri, 15 Feb 2019 21:40:35 +0100
changeset 459565 4739353088fc
parent 459564 eb06f238c16a
child 459566 55806c7e28b7
push id111970
push usermpalmgren@mozilla.com
push dateFri, 15 Feb 2019 20:40:49 +0000
treeherdermozilla-inbound@4739353088fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1494034
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1494034 - Add support for CSS prefers-color-scheme media feature. r=emilio
layout/reftests/css-mediaqueries/greenbox-print.html
layout/reftests/css-mediaqueries/mq_prefers_color_scheme.html
layout/reftests/css-mediaqueries/mq_prefers_color_scheme_dark.html
layout/reftests/css-mediaqueries/mq_prefers_color_scheme_light.html
layout/reftests/css-mediaqueries/mq_prefers_color_scheme_no_preference.html
layout/reftests/css-mediaqueries/mq_prefers_color_scheme_print.html
layout/reftests/css-mediaqueries/reftest.list
layout/style/GeckoBindings.h
layout/style/ServoBindings.toml
layout/style/nsMediaFeatures.cpp
servo/components/style/cbindgen.toml
servo/components/style/gecko/media_features.rs
testing/profiles/reftest/user.js
testing/web-platform/meta/css/mediaqueries/prefers-color-scheme.html.ini
xpcom/ds/StaticAtoms.py
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-mediaqueries/greenbox-print.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset="utf-8">
+<html class="reftest-paged">
+<title>A reference of green box</title>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+  background-color: green;
+}
+</style>
+<div></div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-mediaqueries/mq_prefers_color_scheme.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<title>prefers-color-scheme is supported</title>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+}
+
+@media (prefers-color-scheme) {
+  div { background-color: green; }
+}
+</style>
+<div></div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-mediaqueries/mq_prefers_color_scheme_dark.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<title>prefers-color-scheme: dark</title>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+}
+
+@media (prefers-color-scheme: dark) {
+  div { background-color: green; }
+}
+@media (prefers-color-scheme: light) {
+  div { background-color: red; }
+}
+@media (prefers-color-scheme: no-preference) {
+  div { background-color: red; }
+}
+</style>
+<div></div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-mediaqueries/mq_prefers_color_scheme_light.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<title>prefers-color-scheme: light</title>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+}
+
+@media (prefers-color-scheme: light) {
+  div { background-color: green; }
+}
+@media (prefers-color-scheme: dark) {
+  div { background-color: red; }
+}
+@media (prefers-color-scheme: no-preference) {
+  div { background-color: red; }
+}
+</style>
+<div></div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-mediaqueries/mq_prefers_color_scheme_no_preference.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<html>
+<title>prefers-color-scheme: no-preference</title>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+}
+
+@media (prefers-color-scheme: no-preference) {
+  div { background-color: green; }
+}
+@media (prefers-color-scheme: light) {
+  div { background-color: red; }
+}
+@media (prefers-color-scheme: dark) {
+  div { background-color: red; }
+}
+</style>
+<div></div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-mediaqueries/mq_prefers_color_scheme_print.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<meta charset="utf-8">
+<html class="reftest-paged">
+<title>prefers-color-scheme: light in print mode</title>
+<style>
+div {
+  width: 100px;
+  height: 100px;
+}
+
+@media (prefers-color-scheme: light) {
+  div { background-color: green; }
+}
+@media (prefers-color-scheme: dark) {
+  div { background-color: red; }
+}
+@media (prefers-color-scheme: no-preference) {
+  div { background-color: red; }
+}
+</style>
+<div></div>
+</html>
--- a/layout/reftests/css-mediaqueries/reftest.list
+++ b/layout/reftests/css-mediaqueries/reftest.list
@@ -22,8 +22,14 @@ fuzzy-if(Android,0-8,0-454) == mq_print_
 test-pref(ui.prefersReducedMotion,0) == mq_prefers_reduced_motion.html about:blank
 test-pref(ui.prefersReducedMotion,1) == mq_prefers_reduced_motion.html greenbox.html
 test-pref(ui.prefersReducedMotion,0) == mq_prefers_reduced_motion_no_preference.html greenbox.html
 test-pref(ui.prefersReducedMotion,1) == mq_prefers_reduced_motion_no_preference.html about:blank
 test-pref(ui.prefersReducedMotion,0) == mq_prefers_reduced_motion_reduce.html about:blank
 test-pref(ui.prefersReducedMotion,1) == mq_prefers_reduced_motion_reduce.html greenbox.html
 test-pref(privacy.resistFingerprinting,true) test-pref(ui.prefersReducedMotion,1) == mq_prefers_reduced_motion_reduce.html about:blank
 test-pref(privacy.resistFingerprinting,true) == mq_prefers_reduced_motion_both.html greenbox.html
+test-pref(ui.systemUsesDarkTheme,1) == mq_prefers_color_scheme.html greenbox.html
+test-pref(privacy.resistFingerprinting,true) == mq_prefers_color_scheme_light.html greenbox.html
+== mq_prefers_color_scheme_print.html greenbox-print.html
+test-pref(ui.systemUsesDarkTheme,0) == mq_prefers_color_scheme_light.html greenbox.html
+test-pref(ui.systemUsesDarkTheme,1) == mq_prefers_color_scheme_dark.html greenbox.html
+test-pref(ui.systemUsesDarkTheme,2) == mq_prefers_color_scheme_no_preference.html greenbox.html
--- a/layout/style/GeckoBindings.h
+++ b/layout/style/GeckoBindings.h
@@ -794,16 +794,18 @@ mozilla::StyleDisplayMode Gecko_MediaFea
 
 uint32_t Gecko_MediaFeatures_GetColorDepth(mozilla::dom::Document*);
 
 void Gecko_MediaFeatures_GetDeviceSize(mozilla::dom::Document*, nscoord* width,
                                        nscoord* height);
 
 float Gecko_MediaFeatures_GetResolution(mozilla::dom::Document*);
 bool Gecko_MediaFeatures_PrefersReducedMotion(mozilla::dom::Document*);
+mozilla::StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(
+    mozilla::dom::Document*);
 
 mozilla::PointerCapabilities Gecko_MediaFeatures_PrimaryPointerCapabilities(
     mozilla::dom::Document*);
 
 mozilla::PointerCapabilities Gecko_MediaFeatures_AllPointerCapabilities(
     mozilla::dom::Document*);
 
 float Gecko_MediaFeatures_GetDevicePixelRatio(mozilla::dom::Document*);
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -385,16 +385,17 @@ cbindgen-types = [
     { gecko = "StyleAppearance", servo = "values::specified::Appearance" },
     { gecko = "StyleComputedFontStretchRange", servo = "font_face::ComputedFontStretchRange" },
     { gecko = "StyleComputedFontStyleDescriptor", servo = "font_face::ComputedFontStyleDescriptor" },
     { gecko = "StyleComputedFontWeightRange", servo = "font_face::ComputedFontWeightRange" },
     { gecko = "StyleComputedTimingFunction", servo = "values::computed::easing::TimingFunction" },
     { gecko = "StyleCursorKind", servo = "values::computed::ui::CursorKind" },
     { gecko = "StyleDisplay", servo = "values::specified::Display" },
     { gecko = "StyleDisplayMode", servo = "gecko::media_features::DisplayMode" },
+    { gecko = "StylePrefersColorScheme", servo = "gecko::media_features::PrefersColorScheme" },
     { gecko = "StyleExtremumLength", servo = "values::computed::length::ExtremumLength" },
     { gecko = "StyleFillRule", servo = "values::generics::basic_shape::FillRule" },
     { gecko = "StyleFontDisplay", servo = "font_face::FontDisplay" },
     { gecko = "StyleFontFaceSourceListComponent", servo = "font_face::FontFaceSourceListComponent" },
     { gecko = "StyleFontLanguageOverride", servo = "values::computed::font::FontLanguageOverride" },
     { gecko = "StylePathCommand", servo = "values::specified::svg_path::PathCommand" },
     { gecko = "StyleUnicodeRange", servo = "cssparser::UnicodeRange" },
     { gecko = "StyleOverflowWrap", servo = "values::computed::OverflowWrap" },
@@ -480,16 +481,17 @@ structs-types = [
     "mozilla::FontStretch",
     "mozilla::FontSlantStyle",
     "mozilla::FontWeight",
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
     "mozilla::StyleMotion",
     "mozilla::UniquePtr",
     "mozilla::StyleDisplayMode",
+    "mozilla::StylePrefersColorScheme",
     "mozilla::StyleIntersectionObserverRootMargin",
     "mozilla::StyleComputedFontStretchRange",
     "mozilla::StyleComputedFontStyleDescriptor",
     "mozilla::StyleComputedFontWeightRange",
     "mozilla::StyleFontDisplay",
     "mozilla::StyleUnicodeRange",
     "mozilla::StyleFontLanguageOverride",
     "mozilla::StyleFontFaceSourceListComponent",
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -233,16 +233,36 @@ nsAtom* Gecko_MediaFeatures_GetOperating
 
 bool Gecko_MediaFeatures_PrefersReducedMotion(Document* aDocument) {
   if (nsContentUtils::ShouldResistFingerprinting(aDocument)) {
     return false;
   }
   return LookAndFeel::GetInt(LookAndFeel::eIntID_PrefersReducedMotion, 0) == 1;
 }
 
+StylePrefersColorScheme Gecko_MediaFeatures_PrefersColorScheme(Document* aDocument) {
+  if (nsContentUtils::ShouldResistFingerprinting(aDocument)) {
+    return StylePrefersColorScheme::Light;
+  }
+  if (nsPresContext* pc = aDocument->GetPresContext()) {
+    if (pc->IsPrintingOrPrintPreview()) {
+      return StylePrefersColorScheme::Light;
+    }
+  }
+  // If LookAndFeel::eIntID_SystemUsesDarkTheme fails then return 2 (no-preference)
+  switch (LookAndFeel::GetInt(LookAndFeel::eIntID_SystemUsesDarkTheme, 2)) {
+    case 0: return StylePrefersColorScheme::Light;
+    case 1: return StylePrefersColorScheme::Dark;
+    case 2: return StylePrefersColorScheme::NoPreference;
+    default:
+      // This only occurs if the user has set the ui.systemUsesDarkTheme pref to an invalid value.
+      return StylePrefersColorScheme::Light;
+  }
+}
+
 static PointerCapabilities GetPointerCapabilities(Document* aDocument,
                                                   LookAndFeel::IntID aID) {
   MOZ_ASSERT(aID == LookAndFeel::eIntID_PrimaryPointerCapabilities ||
              aID == LookAndFeel::eIntID_AllPointerCapabilities);
   MOZ_ASSERT(aDocument);
 
   if (nsIDocShell* docShell = aDocument->GetDocShell()) {
     // The touch-events-override happens only for the Responsive Design Mode so
--- a/servo/components/style/cbindgen.toml
+++ b/servo/components/style/cbindgen.toml
@@ -52,16 +52,17 @@ include = [
   "OutlineStyle",
   "ComputedFontStretchRange",
   "ComputedFontStyleDescriptor",
   "ComputedFontWeightRange",
   "ComputedTimingFunction",
   "CursorKind",
   "Display",
   "DisplayMode",
+  "PrefersColorScheme",
   "ExtremumLength",
   "FillRule",
   "FontDisplay",
   "FontFaceSourceListComponent",
   "FontLanguageOverride",
   "OverflowWrap",
   "TimingFunction",
   "PathCommand",
--- a/servo/components/style/gecko/media_features.rs
+++ b/servo/components/style/gecko/media_features.rs
@@ -275,16 +275,26 @@ fn eval_resolution(
 
 #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
 #[repr(u8)]
 enum PrefersReducedMotion {
     NoPreference,
     Reduce,
 }
 
+/// Values for the prefers-color-scheme media feature.
+#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
+#[repr(u8)]
+#[allow(missing_docs)]
+pub enum PrefersColorScheme {
+    Light,
+    Dark,
+    NoPreference,
+}
+
 /// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
 fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
     let prefers_reduced =
         unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
     let query_value = match query_value {
         Some(v) => v,
         None => return prefers_reduced,
     };
@@ -343,16 +353,26 @@ fn eval_overflow_inline(device: &Device,
     };
 
     match query_value {
         OverflowInline::None => !scrolling,
         OverflowInline::Scroll => scrolling,
     }
 }
 
+/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
+fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
+    let prefers_color_scheme =
+        unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document()) };
+    match query_value {
+        Some(v) => prefers_color_scheme == v,
+        None => prefers_color_scheme != PrefersColorScheme::NoPreference,
+    }
+}
+
 /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
 bitflags! {
     struct PointerCapabilities: u8 {
         const COARSE = structs::PointerCapabilities_Coarse;
         const FINE = structs::PointerCapabilities_Fine;
         const HOVER = structs::PointerCapabilities_Hover;
     }
 }
@@ -521,17 +541,17 @@ macro_rules! system_metric_feature {
 }
 
 lazy_static! {
     /// Adding new media features requires (1) adding the new feature to this
     /// array, with appropriate entries (and potentially any new code needed
     /// to support new types in these entries and (2) ensuring that either
     /// nsPresContext::MediaFeatureValuesChanged is called when the value that
     /// would be returned by the evaluator function could change.
-    pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 52] = [
+    pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 53] = [
         feature!(
             atom!("width"),
             AllowsRanges::Yes,
             Evaluator::Length(eval_width),
             ParsingRequirements::empty(),
         ),
         feature!(
             atom!("height"),
@@ -653,16 +673,22 @@ lazy_static! {
         ),
         feature!(
             atom!("overflow-inline"),
             AllowsRanges::No,
             keyword_evaluator!(eval_overflow_inline, OverflowInline),
             ParsingRequirements::empty(),
         ),
         feature!(
+            atom!("prefers-color-scheme"),
+            AllowsRanges::No,
+            keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
+            ParsingRequirements::empty(),
+        ),
+        feature!(
             atom!("pointer"),
             AllowsRanges::No,
             keyword_evaluator!(eval_pointer, Pointer),
             ParsingRequirements::empty(),
         ),
         feature!(
             atom!("any-pointer"),
             AllowsRanges::No,
--- a/testing/profiles/reftest/user.js
+++ b/testing/profiles/reftest/user.js
@@ -94,12 +94,13 @@ user_pref("startup.homepage_welcome_url.
 user_pref("testing.supports.moz-bool-pref", false);
 // Ensure that telemetry is disabled, so we don't connect to the telemetry
 // server in the middle of the tests.
 user_pref("toolkit.telemetry.enabled", false);
 user_pref("toolkit.telemetry.server", "https://%(server)s/telemetry-dummy/");
 user_pref("ui.caretBlinkTime", -1);
 user_pref("ui.caretWidth", 1);
 user_pref("ui.prefersReducedMotion", 0);
+user_pref("ui.systemUsesDarkTheme", 0);
 // Turn off the Push service.
 user_pref("dom.push.serverURL", "");
 // Disable intermittent telemetry collection
 user_pref("toolkit.telemetry.initDelay", 99999999);
deleted file mode 100644
--- a/testing/web-platform/meta/css/mediaqueries/prefers-color-scheme.html.ini
+++ /dev/null
@@ -1,40 +0,0 @@
-[prefers-color-scheme.html]
-  [Should be parseable: '(prefers-color-scheme)']
-    expected: FAIL
-
-  [Check that no-preference evaluates to false in the boolean context]
-    expected: FAIL
-
-  [Should be parseable: '(prefers-color-scheme: dark)']
-    expected: FAIL
-
-  [Should be parseable: '(prefers-color-scheme: light)']
-    expected: FAIL
-
-  [Should be parseable: '(prefers-color-scheme: no-preference)']
-    expected: FAIL
-
-  [Should be parseable in JS: '(prefers-color-scheme: dark)']
-    expected: FAIL
-
-  [Should be parseable in a CSS stylesheet: '(prefers-color-scheme: light)']
-    expected: FAIL
-
-  [Should be parseable in a CSS stylesheet: '(prefers-color-scheme)']
-    expected: FAIL
-
-  [Should be parseable in JS: '(prefers-color-scheme: no-preference)']
-    expected: FAIL
-
-  [Should be parseable in a CSS stylesheet: '(prefers-color-scheme: dark)']
-    expected: FAIL
-
-  [Should be parseable in JS: '(prefers-color-scheme)']
-    expected: FAIL
-
-  [Should be parseable in a CSS stylesheet: '(prefers-color-scheme: no-preference)']
-    expected: FAIL
-
-  [Should be parseable in JS: '(prefers-color-scheme: light)']
-    expected: FAIL
-
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -962,16 +962,17 @@ STATIC_ATOMS = [
     Atom("popupset", "popupset"),
     Atom("popupsinherittooltip", "popupsinherittooltip"),
     Atom("position", "position"),
     Atom("poster", "poster"),
     Atom("pre", "pre"),
     Atom("preceding", "preceding"),
     Atom("precedingSibling", "preceding-sibling"),
     Atom("prefersReducedMotion", "prefers-reduced-motion"),
+    Atom("prefersColorScheme", "prefers-color-scheme"),
     Atom("prefix", "prefix"),
     Atom("preload", "preload"),
     Atom("mozpresentation", "mozpresentation"),
     Atom("preserve", "preserve"),
     Atom("preserveSpace", "preserve-space"),
     Atom("preventdefault", "preventdefault"),
     Atom("previewDiv", "preview-div"),
     Atom("primary", "primary"),