Bug 1483111 - Implement any-hover and any-pointer media queries features. r=emilio
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Fri, 17 Aug 2018 20:29:37 +0900
changeset 436183 2eee04d3b283c512520242f7af49657321137140
parent 436182 ff2c8e49bfb4d2344ad5b1333f2cce46f8f47701
child 436184 3059f63d7f56bc0d3d52e1e195628c2cb16a4d8e
push id34631
push usernerli@mozilla.com
push dateThu, 13 Sep 2018 22:02:04 +0000
treeherdermozilla-central@e923330d5bd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1483111
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 1483111 - Implement any-hover and any-pointer media queries features. r=emilio Differential Revision: https://phabricator.services.mozilla.com/D3609
layout/style/ServoBindings.h
layout/style/nsMediaFeatures.cpp
layout/style/test/mochitest.ini
layout/style/test/test_mq_any_hover_and_any_pointer.html
servo/components/style/gecko/media_features.rs
xpcom/ds/StaticAtoms.py
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -747,16 +747,17 @@ 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*);
+mozilla::PointerCapabilities Gecko_MediaFeatures_AllPointerCapabilities(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/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -257,38 +257,53 @@ 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)
+static PointerCapabilities
+GetPointerCapabilities(nsIDocument* aDocument, LookAndFeel::IntID aID)
 {
+  MOZ_ASSERT(aID == LookAndFeel::eIntID_PrimaryPointerCapabilities ||
+             aID == LookAndFeel::eIntID_AllPointerCapabilities);
+
   // 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);
+  nsresult rv = LookAndFeel::GetInt(aID, &intValue);
   if (NS_FAILED(rv)) {
     return kDefaultCapabilities;
   }
 
   return static_cast<PointerCapabilities>(intValue);
 }
 
+PointerCapabilities
+Gecko_MediaFeatures_PrimaryPointerCapabilities(nsIDocument* aDocument)
+{
+  return GetPointerCapabilities(aDocument,
+                                LookAndFeel::eIntID_PrimaryPointerCapabilities);
+}
+
+PointerCapabilities
+Gecko_MediaFeatures_AllPointerCapabilities(nsIDocument* aDocument)
+{
+  return GetPointerCapabilities(aDocument,
+                                LookAndFeel::eIntID_AllPointerCapabilities);
+}
+
 /* 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_any_hover_and_any_pointer.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]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_mq_any_hover_and_any_pointer.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1483111
+-->
+<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=1483111">Mozilla Bug 1483111</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("(any-pointer: none)").matches,
+                 "Doesn't match (any-pointer: none)");
+  ok(!matchMedia("(any-pointer: coarse)").matches,
+                 "Doesn't match (any-pointer: coarse)");
+  ok(matchMedia("(any-pointer: fine)").matches, "Matches (any-pointer: fine)");
+  ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+  ok(!matchMedia("(any-hover: none)").matches,
+                 "Doesn't match (any-hover: none)");
+  ok(matchMedia("(any-hover: hover)").matches,
+                "Matches (any-hover: hover)");
+  ok(matchMedia("(any-hover)").matches, "Matches (any-hover)");
+
+  await SpecialPowers.flushPrefEnv();
+});
+
+add_task(async () => {
+  // No pointer.
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['ui.allPointerCapabilities', NO_POINTER] ]
+  });
+
+  ok(matchMedia("(any-pointer: none)").matches, "Matches (any-pointer: none)");
+  ok(!matchMedia("(any-pointer: coarse)").matches,
+                 "Doesn't match (any-pointer: coarse)");
+  ok(!matchMedia("(any-pointer: fine)").matches,
+                 "Doesn't match (any-pointer: fine)");
+  ok(!matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+  ok(matchMedia("(any-hover: none)").matches, "Matches (any-hover: none)");
+  ok(!matchMedia("(any-hover: hover)").matches,
+                 "Doesn't match (any-hover: hover)");
+  ok(!matchMedia("(any-hover)").matches, "Doesn't match (any-hover)");
+});
+
+add_task(async () => {
+  // Mouse type pointer and touchscreen
+  await SpecialPowers.pushPrefEnv({
+    set: [ ['ui.allPointerCapabilities',
+            FINE_POINTER | COARSE_POINTER | HOVER_CAPABLE_POINTER] ]
+  });
+
+  ok(!matchMedia("(any-pointer: none)").matches,
+                 "Doesn't match (any-pointer: none)");
+  ok(matchMedia("(any-pointer: coarse)").matches,
+                 "Matches (any-pointer: coarse)");
+  ok(matchMedia("(any-pointer: fine)").matches, "Matches (any-pointer: fine)");
+  ok(matchMedia("(any-pointer)").matches, "Matches (any-pointer)");
+
+  ok(!matchMedia("(any-hover: none)").matches,
+                 "Doesn't match (any-hover: none)");
+  ok(matchMedia("(any-hover: hover)").matches,
+                "Matches (any-hover: hover)");
+  ok(matchMedia("(any-hover)").matches, "Matches (any-hover)");
+});
+
+</script>
+</body>
+</html>
--- a/servo/components/style/gecko/media_features.rs
+++ b/servo/components/style/gecko/media_features.rs
@@ -305,67 +305,95 @@ fn eval_prefers_reduced_motion(device: &
 bitflags! {
     struct PointerCapabilities: u8 {
         const COARSE = structs::PointerCapabilities_Coarse;
         const FINE = structs::PointerCapabilities_Fine;
         const HOVER = structs::PointerCapabilities_Hover;
     }
 }
 
+fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
+    PointerCapabilities::from_bits_truncate(
+        unsafe { bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document()) }
+    )
+}
+
+fn all_pointer_capabilities(device: &Device) -> PointerCapabilities {
+    PointerCapabilities::from_bits_truncate(
+        unsafe { bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document()) }
+    )
+}
+
 #[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);
+fn eval_pointer_capabilities(
+    query_value: Option<Pointer>,
+    pointer_capabilities: PointerCapabilities,
+) -> bool {
     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),
     }
 }
 
+/// https://drafts.csswg.org/mediaqueries-4/#pointer
+fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
+    eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
+fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
+    eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
+}
+
 #[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);
+fn eval_hover_capabilities(
+    query_value: Option<Hover>,
+    pointer_capabilities: PointerCapabilities,
+) -> bool {
     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,
     }
 }
 
+/// https://drafts.csswg.org/mediaqueries-4/#hover
+fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
+    eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
+}
+
+/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
+fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
+    eval_hover_capabilities(query_value, all_pointer_capabilities(device))
+}
+
 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)
 }
@@ -446,17 +474,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; 45] = [
+    pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 47] = [
         feature!(
             atom!("width"),
             AllowsRanges::Yes,
             Evaluator::Length(eval_width),
             ParsingRequirements::empty(),
         ),
         feature!(
             atom!("height"),
@@ -572,21 +600,33 @@ lazy_static! {
         ),
         feature!(
             atom!("pointer"),
             AllowsRanges::No,
             keyword_evaluator!(eval_pointer, Pointer),
             ParsingRequirements::empty(),
         ),
         feature!(
+            atom!("any-pointer"),
+            AllowsRanges::No,
+            keyword_evaluator!(eval_any_pointer, Pointer),
+            ParsingRequirements::empty(),
+        ),
+        feature!(
             atom!("hover"),
             AllowsRanges::No,
             keyword_evaluator!(eval_hover, Hover),
             ParsingRequirements::empty(),
         ),
+        feature!(
+            atom!("any-hover"),
+            AllowsRanges::No,
+            keyword_evaluator!(eval_any_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/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -84,16 +84,18 @@ STATIC_ATOMS = [
     Atom("ancestor", "ancestor"),
     Atom("ancestorOrSelf", "ancestor-or-self"),
     Atom("anchor", "anchor"),
     Atom("_and", "and"),
     Atom("animations", "animations"),
     Atom("anonid", "anonid"),
     Atom("anonlocation", "anonlocation"),
     Atom("any", "any"),
+    Atom("any_hover", "any-hover"),
+    Atom("any_pointer", "any-pointer"),
     Atom("applet", "applet"),
     Atom("applyImports", "apply-imports"),
     Atom("applyTemplates", "apply-templates"),
     Atom("archive", "archive"),
     Atom("area", "area"),
     Atom("aria_activedescendant", "aria-activedescendant"),
     Atom("aria_atomic", "aria-atomic"),
     Atom("aria_autocomplete", "aria-autocomplete"),