Bug 474356 - Add -moz-device-pixel-ratio media query. r=dbaron a=approval2.0
☠☠ backed out by 4651348911d2 ☠ ☠
authorJosh Matthews <josh@joshmatthews.net>
Sat, 11 Sep 2010 19:21:48 -0400
changeset 53645 ec2ffd935bd973fc6e650515df6fd8db907d5c12
parent 53644 ac779114cfa60efff7753e389523b3de7a8a7525
child 53646 8a6a5cf00da73adce5261d20d3eea79d58fda6ee
child 53647 4651348911d213c4eef60f09f15c5cc85cc938c2
push idunknown
push userunknown
push dateunknown
reviewersdbaron, approval2
bugs474356
milestone2.0b6pre
Bug 474356 - Add -moz-device-pixel-ratio media query. r=dbaron a=approval2.0
content/base/src/nsGkAtomList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSStyleSheet.cpp
layout/style/nsMediaFeatures.cpp
layout/style/nsMediaFeatures.h
layout/style/test/Makefile.in
layout/style/test/test_media_queries.html
layout/style/test/test_moz_device_pixel_ratio.html
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1745,8 +1745,9 @@ GK_ATOM(_moz_images_in_menus, "-moz-imag
 GK_ATOM(_moz_images_in_buttons, "-moz-images-in-buttons")
 GK_ATOM(_moz_windows_default_theme, "-moz-windows-default-theme")
 GK_ATOM(_moz_mac_graphite_theme, "-moz-mac-graphite-theme")
 GK_ATOM(_moz_windows_compositor, "-moz-windows-compositor")
 GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
 GK_ATOM(_moz_maemo_classic, "-moz-maemo-classic")
 GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
+GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1786,16 +1786,19 @@ CSSParserImpl::ParseMediaQueryExpression
     case nsMediaFeature::eBoolInteger:
       rv = ParseNonNegativeVariant(expr->mValue, VARIANT_INTEGER, nsnull);
       // Enforce extra restrictions for eBoolInteger
       if (rv &&
           feature->mValueType == nsMediaFeature::eBoolInteger &&
           expr->mValue.GetIntValue() > 1)
         rv = PR_FALSE;
       break;
+    case nsMediaFeature::eFloat:
+      rv = ParseNonNegativeVariant(expr->mValue, VARIANT_NUMBER, nsnull);
+      break;
     case nsMediaFeature::eIntRatio:
       {
         // Two integers separated by '/', with optional whitespace on
         // either side of the '/'.
         nsRefPtr<nsCSSValue::Array> a = nsCSSValue::Array::Create(2);
         if (!a) {
           mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
           SkipUntil(')');
--- a/layout/style/nsCSSStyleSheet.cpp
+++ b/layout/style/nsCSSStyleSheet.cpp
@@ -244,16 +244,25 @@ nsMediaExpression::Matches(nsPresContext
                      actual.GetIntValue() == 0 || actual.GetIntValue() == 1,
                      "bad actual bool integer value");
         NS_ASSERTION(mFeature->mValueType != nsMediaFeature::eBoolInteger ||
                      required.GetIntValue() == 0 || required.GetIntValue() == 1,
                      "bad required bool integer value");
         cmp = DoCompare(actual.GetIntValue(), required.GetIntValue());
       }
       break;
+    case nsMediaFeature::eFloat:
+      {
+        NS_ASSERTION(actual.GetUnit() == eCSSUnit_Number,
+                     "bad actual value");
+        NS_ASSERTION(required.GetUnit() == eCSSUnit_Number,
+                     "bad required value");
+        cmp = DoCompare(actual.GetFloatValue(), required.GetFloatValue());
+      }
+      break;
     case nsMediaFeature::eIntRatio:
       {
         NS_ASSERTION(actual.GetUnit() == eCSSUnit_Array &&
                      actual.GetArrayValue()->Count() == 2 &&
                      actual.GetArrayValue()->Item(0).GetUnit() ==
                        eCSSUnit_Integer &&
                      actual.GetArrayValue()->Item(1).GetUnit() ==
                        eCSSUnit_Integer,
@@ -417,16 +426,25 @@ nsMediaQuery::AppendToString(nsAString& 
         case nsMediaFeature::eInteger:
         case nsMediaFeature::eBoolInteger:
           NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Integer,
                        "bad unit");
           // Use 'z-index' as a property that takes integer values
           // written without anything extra.
           expr.mValue.AppendToString(eCSSProperty_z_index, aString);
           break;
+        case nsMediaFeature::eFloat:
+          {
+            NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Number,
+                         "bad unit");
+            // Use 'line-height' as a property that takes float values
+            // written in the normal way.
+            expr.mValue.AppendToString(eCSSProperty_line_height, aString);
+          }
+          break;
         case nsMediaFeature::eIntRatio:
           {
             NS_ASSERTION(expr.mValue.GetUnit() == eCSSUnit_Array,
                          "bad unit");
             nsCSSValue::Array *array = expr.mValue.GetArrayValue();
             NS_ASSERTION(array->Count() == 2, "unexpected length");
             NS_ASSERTION(array->Item(0).GetUnit() == eCSSUnit_Integer,
                          "bad unit");
--- a/layout/style/nsMediaFeatures.cpp
+++ b/layout/style/nsMediaFeatures.cpp
@@ -180,17 +180,16 @@ GetAspectRatio(nsPresContext* aPresConte
 
 static nsresult
 GetDeviceAspectRatio(nsPresContext* aPresContext, const nsMediaFeature*,
                      nsCSSValue& aResult)
 {
     return MakeArray(GetDeviceSize(aPresContext), aResult);
 }
 
-
 static nsresult
 GetColor(nsPresContext* aPresContext, const nsMediaFeature*,
          nsCSSValue& aResult)
 {
     // FIXME:  This implementation is bogus.  nsThebesDeviceContext
     // doesn't provide reliable information (should be fixed in bug
     // 424386).
     // FIXME: On a monochrome device, return 0!
@@ -257,16 +256,25 @@ GetGrid(nsPresContext* aPresContext, con
 {
     // Gecko doesn't support grid devices (e.g., ttys), so the 'grid'
     // feature is always 0.
     aResult.SetIntValue(0, eCSSUnit_Integer);
     return NS_OK;
 }
 
 static nsresult
+GetDevicePixelRatio(nsPresContext* aPresContext, const nsMediaFeature*,
+                    nsCSSValue& aResult)
+{
+  float ratio = aPresContext->CSSPixelsToDevPixels(1.0f);
+  aResult.SetFloatValue(ratio, eCSSUnit_Number);
+  return NS_OK;
+}
+
+static nsresult
 GetSystemMetric(nsPresContext* aPresContext, const nsMediaFeature* aFeature,
                 nsCSSValue& aResult)
 {
     NS_ABORT_IF_FALSE(aFeature->mValueType == nsMediaFeature::eBoolInteger,
                       "unexpected type");
     nsIAtom *metricAtom = *aFeature->mData.mMetric;
     PRBool hasMetric = nsCSSRuleProcessor::HasSystemMetric(metricAtom);
     aResult.SetIntValue(hasMetric ? 1 : 0, eCSSUnit_Integer);
@@ -373,16 +381,23 @@ nsMediaFeatures::features[] = {
         nsMediaFeature::eMinMaxNotAllowed,
         nsMediaFeature::eBoolInteger,
         { nsnull },
         GetGrid
     },
 
     // Mozilla extensions
     {
+        &nsGkAtoms::_moz_device_pixel_ratio,
+        nsMediaFeature::eMinMaxAllowed,
+        nsMediaFeature::eFloat,
+        { nsnull },
+        GetDevicePixelRatio
+    },
+    {
         &nsGkAtoms::_moz_scrollbar_start_backward,
         nsMediaFeature::eMinMaxNotAllowed,
         nsMediaFeature::eBoolInteger,
         { &nsGkAtoms::scrollbar_start_backward },
         GetSystemMetric
     },
     {
         &nsGkAtoms::_moz_scrollbar_start_forward,
--- a/layout/style/nsMediaFeatures.h
+++ b/layout/style/nsMediaFeatures.h
@@ -58,16 +58,17 @@ struct nsMediaFeature {
     enum RangeType { eMinMaxAllowed, eMinMaxNotAllowed };
     RangeType mRangeType;
 
     enum ValueType {
         // All value types allow eCSSUnit_Null to indicate that no value
         // was given (in addition to the types listed below).
         eLength,     // values are such that nsCSSValue::IsLengthUnit() is true
         eInteger,    // values are eCSSUnit_Integer
+        eFloat,      // values are eCSSUnit_Number
         eBoolInteger,// values are eCSSUnit_Integer (0, -0, or 1 only)
         eIntRatio,   // values are eCSSUnit_Array of two eCSSUnit_Integer
         eResolution, // values are in eCSSUnit_Inch (for dpi) or
                      //   eCSSUnit_Centimeter (for dpcm)
         eEnumerated  // values are eCSSUnit_Enumerated (uses keyword table)
 
         // Note that a number of pieces of code (both for parsing and
         // for matching of valueless expressions) assume that all numeric
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -148,16 +148,17 @@ GARBAGE += css_properties.js
 		test_ident_escaping.html \
 		test_inherit_computation.html \
 		test_inherit_storage.html \
 		test_initial_computation.html \
 		test_initial_storage.html \
 		test_media_queries.html \
 		test_media_queries_dynamic.html \
 		test_media_queries_dynamic_xbl.html \
+		test_moz_device_pixel_ratio.html \
 		test_namespace_rule.html \
 		test_of_type_selectors.xhtml \
 		test_parse_rule.html \
 		test_parse_url.html \
 		test_pixel_lengths.html \
 		test_pointer-events.html \
 		test_property_database.html \
 		test_priority_preservation.html \
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -21,16 +21,23 @@ https://bugzilla.mozilla.org/show_bug.cg
 /** Test for Bug 156716 **/
 
 // Note that many other tests are in test_acid3_test46.html .
 
 SimpleTest.waitForExplicitFinish();
 
 var iframe;
 
+function getZoomRatio() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                    .getInterface(Components.interfaces.nsIDOMWindowUtils);
+  return utils.screenPixelsPerCSSPixel;
+}
+
 function run() {
   iframe = document.getElementById("subdoc");
   var subdoc = iframe.contentDocument;
   var subwin = iframe.contentWindow;
   var style = subdoc.getElementById("style");
   var iframe_style = iframe.style;
   var body_cs = subdoc.defaultView.getComputedStyle(subdoc.body, "");
 
@@ -324,16 +331,36 @@ function run() {
 
   should_apply("all and (max-device-aspect-ratio: " + real_dar + ")");
   should_apply("(max-device-aspect-ratio: " + high_dar_1 + ")");
   should_apply("(max-device-aspect-ratio: " + high_dar_2 + ")");
   should_not_apply("all and (max-device-aspect-ratio: " + low_dar_1 + ")");
   should_apply("not all and (max-device-aspect-ratio: " + low_dar_2 + ")");
   expression_should_not_be_parseable("max-device-aspect-ratio");
 
+  var real_dpr = 1.0 * getZoomRatio();
+  var high_dpr = 1.1 * getZoomRatio();
+  var low_dpr = 0.9 * getZoomRatio();
+  should_apply("all and (max--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_apply("all and (min--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_not_apply("not all and (max--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_not_apply("not all and (min--moz-device-pixel-ratio: " + real_dpr + ")");
+  should_apply("all and (min--moz-device-pixel-ratio: " + low_dpr + ")");
+  should_apply("all and (max--moz-device-pixel-ratio: " + high_dpr + ")");
+  should_not_apply("all and (max--moz-device-pixel-ratio: " + low_dpr + ")");
+  should_not_apply("all and (min--moz-device-pixel-ratio: " + high_dpr + ")");
+  should_apply("not all and (max--moz-device-pixel-ratio: " + low_dpr + ")");
+  should_apply("not all and (min--moz-device-pixel-ratio: " + high_dpr + ")");
+  should_apply("(-moz-device-pixel-ratio: " + real_dpr + ")");
+  should_not_apply("(-moz-device-pixel-ratio: " + high_dpr + ")");
+  should_not_apply("(-moz-device-pixel-ratio: " + low_dpr + ")");
+  should_apply("(-moz-device-pixel-ratio)");
+  expression_should_not_be_parseable("min--moz-device-pixel-ratio");
+  expression_should_not_be_parseable("max--moz-device-pixel-ratio");
+
   features = [ "max-aspect-ratio", "device-aspect-ratio" ];
   for (i in features) {
     feature = features[i];
     expression_should_be_parseable(feature + ": 1/1");
     expression_should_be_parseable(feature + ": 1  /1");
     expression_should_be_parseable(feature + ": 1  / \t\n1");
     expression_should_be_parseable(feature + ": 1/\r1");
     expression_should_not_be_parseable(feature + ": 1");
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_moz_device_pixel_ratio.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=474356
+-->
+<head>
+  <title>Test for Bug 474356</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <style>.zoom-test { visibility: hidden; }</style>
+  <style><!-- placeholder for dynamic additions --></style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474356">Mozilla Bug 474356</a>
+<div id="content" style="display: none">
+  
+</div>
+<script type="text/javascript">
+</script>
+<pre id="test">
+<div id="zoom1" class="zoom-test"></div>
+<div id="zoom2" class="zoom-test"></div>
+<div id="zoom3" class="zoom-test"></div>
+<script class="testbody" type="application/javascript">
+
+/** Test for Bug 474356 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+  viewer = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                 .getInterface(Components.interfaces.nsIWebNavigation)
+                 .QueryInterface(Components.interfaces.nsIDocShell)
+                 .contentViewer
+                 .QueryInterface(Components.interfaces.nsIMarkupDocumentViewer);
+
+
+  function zoom(factor) {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    viewer.fullZoom = factor;
+  }
+
+  function isVisible(divName) {
+    return window.getComputedStyle(document.getElementById(divName), null).visibility == "visible";
+  }
+
+  function getZoomRatio() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    return utils ? utils.screenPixelsPerCSSPixel : 1.0;
+  }
+
+  var screenPixelsPerCSSPixel = getZoomRatio();
+  var baseRatio = 1.0 * screenPixelsPerCSSPixel;
+  var doubleRatio = 2.0 * screenPixelsPerCSSPixel;
+  var halfRatio = 0.5 * screenPixelsPerCSSPixel;
+  var styleElem = document.getElementsByTagName("style")[1];
+  styleElem.textContent = 
+      ["@media all and (-moz-device-pixel-ratio: " + baseRatio + ") {",
+         "#zoom1 { visibility: visible; }",
+       "}",
+       "@media all and (-moz-device-pixel-ratio: " + doubleRatio + ") {",
+         "#zoom2 { visibility: visible; }",
+       "}",
+       "@media all and (-moz-device-pixel-ratio: " + halfRatio + ") {",
+         "#zoom3 { visibility: visible; }",
+       "}"
+      ].join("\n");
+
+  ok(isVisible("zoom1"), "Base ratio rule should apply at base zoom level");
+  ok(!isVisible("zoom2") && !isVisible("zoom3"), "no other rules should apply");
+  zoom(2 * screenPixelsPerCSSPixel);
+  ok(isVisible("zoom2"), "Double ratio rule should apply at double zoom level");
+  ok(!isVisible("zoom1") && !isVisible("zoom3"), "no other rules should apply");
+  zoom(0.5 * screenPixelsPerCSSPixel);
+  ok(isVisible("zoom3"), "Half ratio rule should apply at half zoom level");
+  ok(!isVisible("zoom1") && !isVisible("zoom2"), "no other rules should apply");
+
+  SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>