Bug 1035774 - Add media feature keys for hover and pointer. r=heycam,emilio
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 14 Aug 2018 17:13:04 +0900
changeset 491913 035003a0c43d2ed8d2af2f1d50386104070741f1
parent 491912 6096baf109f977a5d2e64dc6c8216df6ad13863d
child 491914 4124937f8ef9be745e80ba70a1a5553b2f553a60
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam, emilio
bugs1035774
milestone64.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 1035774 - Add media feature keys for hover and pointer. r=heycam,emilio Summary: https://drafts.csswg.org/mediaqueries-4/#hover https://drafts.csswg.org/mediaqueries-4/#pointer In this patch series, we don't introduce any-hover and any-pointer media features yet, but functionalities for them on each platform backends will be introduced in this patch series, so eIntID_AllPointerCapabilities and relevant stuff is added in this patch for the convenience that each platform backends can be reviewed at once. Differential Revision: https://phabricator.services.mozilla.com/D3296
layout/style/ServoBindings.h
layout/style/ServoBindings.toml
layout/style/ServoTypes.h
layout/style/nsMediaFeatures.cpp
layout/style/test/mochitest.ini
layout/style/test/test_mq_hover_and_pointer.html
servo/components/style/gecko/media_features.rs
widget/LookAndFeel.h
widget/nsXPLookAndFeel.cpp
xpcom/ds/StaticAtoms.py
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -51,16 +51,17 @@ namespace mozilla {
     enum class IterationCompositeOperation : uint8_t;
   };
   enum class UpdateAnimationsTasks : uint8_t;
   struct LangGroupFontPrefs;
   class SeenPtrs;
   class ComputedStyle;
   class StyleSheet;
   class ServoElementSnapshotTable;
+  enum class PointerCapabilities : uint8_t;
 }
 using mozilla::FontFamilyList;
 using mozilla::FontFamilyName;
 using mozilla::FontFamilyType;
 using mozilla::ServoElementSnapshot;
 using mozilla::SharedFontList;
 struct nsMediaFeature;
 class nsSimpleContentList;
@@ -745,16 +746,17 @@ bool Gecko_IsInServoTraversal();
 bool Gecko_IsMainThread();
 
 // Media feature helpers.
 mozilla::StyleDisplayMode Gecko_MediaFeatures_GetDisplayMode(nsIDocument*);
 uint32_t Gecko_MediaFeatures_GetColorDepth(nsIDocument*);
 void Gecko_MediaFeatures_GetDeviceSize(nsIDocument*, nscoord* width, nscoord* height);
 float Gecko_MediaFeatures_GetResolution(nsIDocument*);
 bool Gecko_MediaFeatures_PrefersReducedMotion(nsIDocument*);
+mozilla::PointerCapabilities Gecko_MediaFeatures_PrimaryPointerCapabilities(nsIDocument*);
 float Gecko_MediaFeatures_GetDevicePixelRatio(nsIDocument*);
 bool Gecko_MediaFeatures_HasSystemMetric(nsIDocument*,
                                          nsAtom* metric,
                                          bool is_accessible_from_content);
 bool Gecko_MediaFeatures_IsResourceDocument(nsIDocument*);
 nsAtom* Gecko_MediaFeatures_GetOperatingSystemVersion(nsIDocument*);
 
 } // extern "C"
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -252,16 +252,17 @@ whitelist-types = [
     "mozilla::MallocSizeOf",
     "mozilla::OriginFlags",
     "mozilla::PropertyStyleAnimationValuePair",
     "mozilla::ServoTraversalFlags",
     "mozilla::StaticPrefs",
     "mozilla::StyleShapeRadius",
     "mozilla::StyleGrid.*",
     "mozilla::UpdateAnimationsTasks",
+    "mozilla::PointerCapabilities",
     "mozilla::LookAndFeel",
     "mozilla::gfx::Float",
     "mozilla::gfx::FontVariation",
     "mozilla::StyleImageLayerAttachment",
     ".*ThreadSafe.*Holder",
     "AnonymousContent",
     "AudioContext",
     "CapturingContentInfo",
@@ -615,16 +616,17 @@ structs-types = [
     "LoaderReusableStyleSheets",
     "SheetLoadData",
     "SheetLoadDataHolder",
     "StyleSheet",
     "ServoComputedData",
     "ComputedStyleStrong",
     "EffectCompositor_CascadeLevel",
     "UpdateAnimationsTasks",
+    "PointerCapabilities",
     "ParsingMode",
     "InheritTarget",
     "DocumentMatchingFunction",
     "StyleAnimation",
     "StyleRuleInclusion",
     "nsStyleTransformMatrix::MatrixTransformOperator",
     "RawGeckoGfxMatrix4x4",
     "FontFamilyName",
--- a/layout/style/ServoTypes.h
+++ b/layout/style/ServoTypes.h
@@ -120,16 +120,27 @@ enum class InheritTarget {
   // We're requesting a text style.
   Text,
   // We're requesting a first-letter continuation frame style.
   FirstLetterContinuation,
   // We're requesting a style for a placeholder frame.
   PlaceholderFrame,
 };
 
+// Represents values for interaction media features.
+// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
+enum class PointerCapabilities : uint8_t {
+  None = 0,
+  Coarse = 1 << 0,
+  Fine = 1 << 1,
+  Hover = 1 << 2,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PointerCapabilities)
+
 // These measurements are obtained for both the UA cache and the Stylist, but
 // not all the fields are used in both cases.
 class ServoStyleSetSizes
 {
 public:
   size_t mRuleTree;                // Stylist-only
   size_t mPrecomputedPseudos;      // UA cache-only
   size_t mElementAndPseudosMaps;   // Used for both
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -257,16 +257,38 @@ bool
 Gecko_MediaFeatures_PrefersReducedMotion(nsIDocument* aDocument)
 {
   if (nsContentUtils::ShouldResistFingerprinting(aDocument)) {
     return false;
   }
   return LookAndFeel::GetInt(LookAndFeel::eIntID_PrefersReducedMotion, 0) == 1;
 }
 
+PointerCapabilities
+Gecko_MediaFeatures_PrimaryPointerCapabilities(nsIDocument* aDocument)
+{
+  // The default value is mouse-type pointer.
+  const PointerCapabilities kDefaultCapabilities =
+    PointerCapabilities::Fine | PointerCapabilities::Hover;
+
+  if (nsContentUtils::ShouldResistFingerprinting(aDocument)) {
+    return kDefaultCapabilities;
+  }
+
+  int32_t intValue;
+  nsresult rv =
+    LookAndFeel::GetInt(LookAndFeel::eIntID_PrimaryPointerCapabilities,
+                        &intValue);
+  if (NS_FAILED(rv)) {
+    return kDefaultCapabilities;
+  }
+
+  return static_cast<PointerCapabilities>(intValue);
+}
+
 /* static */ void
 nsMediaFeatures::InitSystemMetrics()
 {
   if (sSystemMetrics)
     return;
 
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -252,16 +252,17 @@ skip-if = toolkit == 'android'
 support-files = slow_broken_sheet.sjs slow_ok_sheet.sjs
 [test_logical_properties.html]
 [test_media_queries.html]
 skip-if = android_version == '18' #debug-only failure; timed out #Android 4.3 aws only; bug 1030419
 [test_media_queries_dynamic.html]
 [test_media_queries_dynamic_xbl.html]
 [test_media_query_list.html]
 [test_media_query_serialization.html]
+[test_mq_hover_and_pointer.html]
 [test_moz_device_pixel_ratio.html]
 [test_namespace_rule.html]
 [test_non_content_accessible_properties.html]
 [test_non_content_accessible_pseudos.html]
 [test_non_content_accessible_values.html]
 [test_of_type_selectors.xhtml]
 [test_overscroll_behavior_pref.html]
 [test_page_parser.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_mq_hover_and_pointer.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1035774
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1035774</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="/tests/SimpleTest/AddTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1035774">Mozilla Bug 1035774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+const NO_POINTER            = 0x00;
+const COARSE_POINTER        = 0x01;
+const FINE_POINTER          = 0x02;
+const HOVER_CAPABLE_POINTER = 0x04;
+
+add_task(async () => {
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['privacy.resistFingerprinting', true] ]
+  });
+
+  // When resistFingerprinting is enabled, we pretend that the system has a
+  // mouse pointer.
+  ok(!matchMedia("(pointer: none)").matches,
+                 "Doesn't match (pointer: none)");
+  ok(!matchMedia("(pointer: coarse)").matches,
+                 "Doesn't match (pointer: coarse)");
+  ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+  ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+  ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+  ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+  ok(matchMedia("(hover)").matches, "Matches (hover)");
+
+  await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+  // No pointer.
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['ui.primaryPointerCapabilities', NO_POINTER] ]
+  });
+
+  ok(matchMedia("(pointer: none)").matches, "Matches (pointer: none)");
+  ok(!matchMedia("(pointer: coarse)").matches,
+                 "Doesn't match (pointer: coarse)");
+  ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)");
+  ok(!matchMedia("(pointer)").matches, "Matches (pointer)");
+
+  ok(matchMedia("(hover: none)").matches, "Matches (hover: none)");
+  ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)");
+  ok(!matchMedia("(hover)").matches, "Doesn't match (hover)");
+});
+
+add_task(async () => {
+  // Mouse type pointer.
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['ui.primaryPointerCapabilities', FINE_POINTER | HOVER_CAPABLE_POINTER] ]
+  });
+
+  ok(!matchMedia("(pointer: none)").matches,
+                 "Doesn't match (pointer: none)");
+  ok(!matchMedia("(pointer: coarse)").matches,
+                 "Doesn't match (pointer: coarse)");
+  ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+  ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+  ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+  ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+  ok(matchMedia("(hover)").matches, "Matches (hover)");
+});
+
+add_task(async () => {
+  // Mouse type pointer.
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['ui.primaryPointerCapabilities',
+            FINE_POINTER |
+            HOVER_CAPABLE_POINTER] ]
+  });
+
+  ok(!matchMedia("(pointer: none)").matches,
+                 "Doesn't match (pointer: none)");
+  ok(!matchMedia("(pointer: coarse)").matches,
+                 "Doesn't match (pointer: coarse)");
+  ok(matchMedia("(pointer: fine)").matches, "Matches (pointer: fine)");
+  ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+  ok(!matchMedia("(hover: none)").matches, "Doesn't match (hover: none)");
+  ok(matchMedia("(hover: hover)").matches, "Matches (hover: hover)");
+  ok(matchMedia("(hover)").matches, "Matches (hover)");
+});
+
+add_task(async () => {
+  // Touch screen.
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['ui.primaryPointerCapabilities', COARSE_POINTER] ]
+  });
+
+  ok(!matchMedia("(pointer: none)").matches, "Doesn't match (pointer: none)");
+  ok(matchMedia("(pointer: coarse)").matches, "Matches (pointer: coarse)");
+  ok(!matchMedia("(pointer: fine)").matches, "Doesn't match (pointer: fine)");
+  ok(matchMedia("(pointer)").matches, "Matches (pointer)");
+
+  ok(matchMedia("(hover: none)").matches, "Match (hover: none)");
+  ok(!matchMedia("(hover: hover)").matches, "Doesn't match (hover: hover)");
+  ok(!matchMedia("(hover)").matches, "Doesn't match (hover)");
+});
+
+</script>
+</body>
+</html>
--- a/servo/components/style/gecko/media_features.rs
+++ b/servo/components/style/gecko/media_features.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Gecko's media feature list and evaluator.
 
 use Atom;
 use app_units::Au;
 use euclid::Size2D;
 use gecko_bindings::bindings;
+use gecko_bindings::structs;
 use media_queries::Device;
 use media_queries::media_feature::{AllowsRanges, ParsingRequirements};
 use media_queries::media_feature::{MediaFeatureDescription, Evaluator};
 use media_queries::media_feature_expression::{AspectRatio, RangeOrOperator};
 use values::computed::CSSPixelLength;
 use values::computed::Resolution;
 
 fn viewport_size(device: &Device) -> Size2D<Au> {
@@ -295,16 +296,76 @@ fn eval_prefers_reduced_motion(device: &
     };
 
     match query_value {
         PrefersReducedMotion::NoPreference => !prefers_reduced,
         PrefersReducedMotion::Reduce => prefers_reduced,
     }
 }
 
+/// 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;
+    }
+}
+
+#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)]
+#[repr(u8)]
+enum Pointer {
+    None,
+    Coarse,
+    Fine,
+}
+
+fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
+    PointerCapabilities::from_bits_truncate(
+        unsafe { bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document()) }
+    )
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#pointer
+fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
+    let pointer_capabilities = primary_pointer_capabilities(device);
+    let query_value = match query_value {
+        Some(v) => v,
+        None => return !pointer_capabilities.is_empty(),
+    };
+
+    match query_value {
+        Pointer::None => pointer_capabilities.is_empty(),
+        Pointer::Coarse => pointer_capabilities.intersects(PointerCapabilities::COARSE),
+        Pointer::Fine => pointer_capabilities.intersects(PointerCapabilities::FINE),
+    }
+}
+
+#[derive(Debug, Copy, Clone, FromPrimitive, ToCss, Parse)]
+#[repr(u8)]
+enum Hover {
+    None,
+    Hover,
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#hover
+fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
+    let pointer_capabilities = primary_pointer_capabilities(device);
+    let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER);
+    let query_value = match query_value {
+        Some(v) => v,
+        None => return can_hover,
+    };
+
+    match query_value {
+        Hover::None => !can_hover,
+        Hover::Hover => can_hover,
+    }
+}
+
 fn eval_moz_is_glyph(
     device: &Device,
     query_value: Option<bool>,
     _: Option<RangeOrOperator>,
 ) -> bool {
     let is_glyph = unsafe { (*device.document()).mIsSVGGlyphsDocument() };
     query_value.map_or(is_glyph, |v| v == is_glyph)
 }
@@ -385,17 +446,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; 43] = [
+    pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 45] = [
         feature!(
             atom!("width"),
             AllowsRanges::Yes,
             Evaluator::Length(eval_width),
             ParsingRequirements::empty(),
         ),
         feature!(
             atom!("height"),
@@ -504,16 +565,28 @@ lazy_static! {
             ParsingRequirements::empty(),
         ),
         feature!(
             atom!("prefers-reduced-motion"),
             AllowsRanges::No,
             keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
             ParsingRequirements::empty(),
         ),
+        feature!(
+            atom!("pointer"),
+            AllowsRanges::No,
+            keyword_evaluator!(eval_pointer, Pointer),
+            ParsingRequirements::empty(),
+        ),
+        feature!(
+            atom!("hover"),
+            AllowsRanges::No,
+            keyword_evaluator!(eval_hover, Hover),
+            ParsingRequirements::empty(),
+        ),
 
         // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
         // Internal because it is really only useful in the user agent anyway
         // and therefore not worth standardizing.
         feature!(
             atom!("-moz-is-glyph"),
             AllowsRanges::No,
             Evaluator::BoolInteger(eval_moz_is_glyph),
--- a/widget/LookAndFeel.h
+++ b/widget/LookAndFeel.h
@@ -442,17 +442,32 @@ public:
      eIntID_SystemUsesDarkTheme,
 
      /**
       * Corresponding to prefers-reduced-motion.
       * https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
       * 0: no-preference
       * 1: reduce
       */
+
      eIntID_PrefersReducedMotion,
+     /**
+      * Corresponding to PointerCapabilities in ServoTypes.h
+      * 0: None
+      * 1: Coarse
+      * 2: Fine
+      * 4: Hover
+      */
+     eIntID_PrimaryPointerCapabilities,
+     /**
+      * Corresponding to union of PointerCapabilities values in ServoTypes.h
+      * E.g. if there is a mouse and a digitizer, the value will be
+      * 'Coarse | Fine | Hover'.
+      */
+     eIntID_AllPointerCapabilities,
   };
 
   /**
    * Windows themes we currently detect.
    */
   enum WindowsTheme {
     eWindowsTheme_Generic = 0, // unrecognized theme
     eWindowsTheme_Classic,
--- a/widget/nsXPLookAndFeel.cpp
+++ b/widget/nsXPLookAndFeel.cpp
@@ -136,16 +136,22 @@ nsLookAndFeelIntPref nsXPLookAndFeel::sI
     eIntID_GTKCSDCloseButton,
     false, 0 },
   { "ui.systemUsesDarkTheme",
     eIntID_SystemUsesDarkTheme,
     false, 0 },
   { "ui.prefersReducedMotion",
     eIntID_PrefersReducedMotion,
     false, 0 },
+  { "ui.primaryPointerCapabilities",
+    eIntID_PrimaryPointerCapabilities,
+    false, 6 /* fine and hover-capable pointer, i.e. mouse-type */ },
+  { "ui.allPointerCapabilities",
+    eIntID_AllPointerCapabilities,
+    false, 6 /* fine and hover-capable pointer, i.e. mouse-type */ },
 };
 
 nsLookAndFeelFloatPref nsXPLookAndFeel::sFloatPrefs[] =
 {
   { "ui.IMEUnderlineRelativeSize",
     eFloatID_IMEUnderlineRelativeSize,
     false, 0 },
   { "ui.SpellCheckerUnderlineRelativeSize",
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -1378,16 +1378,17 @@ STATIC_ATOMS = [
     Atom("overridePreserveAspectRatio", "overridePreserveAspectRatio"),
     Atom("pad", "pad"),
     Atom("path", "path"),
     Atom("pathLength", "pathLength"),
     Atom("patternContentUnits", "patternContentUnits"),
     Atom("patternTransform", "patternTransform"),
     Atom("patternUnits", "patternUnits"),
     Atom("pc", "pc"),
+    Atom("pointer", "pointer"),
     Atom("pointer_events", "pointer-events"),
     Atom("points", "points"),
     Atom("pointsAtX", "pointsAtX"),
     Atom("pointsAtY", "pointsAtY"),
     Atom("pointsAtZ", "pointsAtZ"),
     Atom("polyline", "polyline"),
     Atom("preserveAlpha", "preserveAlpha"),
     Atom("preserveAspectRatio", "preserveAspectRatio"),