Bug 1241867 - override the DPR without affecting the rendering; r=dbaron draft
authorMatteo Ferretti <mferretti@mozilla.com>
Thu, 25 Aug 2016 13:15:19 +0200
changeset 406726 9640d8d23363343022cf49755e9927427a97aac3
parent 406479 b02228e2a9eb38fa2a1a947f7943def10c5310be
child 529731 dfc285fb98d8d13c9463ecd80496580ce13f3d96
push id27811
push userbmo:zer0@mozilla.com
push dateMon, 29 Aug 2016 11:51:11 +0000
reviewersdbaron
bugs1241867
milestone51.0a1
Bug 1241867 - override the DPR without affecting the rendering; r=dbaron - added overrideDPPX to nsIContentViewer - made CSSStyleSheet and GlobalWindow using the overrideDPPX value - added unit test with frame check MozReview-Commit-ID: AOWpGs4vb9H
docshell/base/nsIContentViewer.idl
dom/base/nsDocument.cpp
dom/base/nsGlobalWindow.cpp
dom/tests/mochitest/general/mochitest.ini
dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html
layout/base/nsDocumentViewer.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/style/CSSStyleSheet.cpp
testing/specialpowers/content/specialpowersAPI.js
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -198,22 +198,29 @@ interface nsIContentViewer : nsISupports
   void scrollToNode(in nsIDOMNode node);
 
   /** The amount by which to scale all text. Default is 1.0. */
   attribute float textZoom;
 
   /** The amount by which to scale all lengths. Default is 1.0. */
   attribute float fullZoom;
 
+  /**
+   * The value used to override devicePixelRatio and media queries dppx.
+   * Default is 0.0, that means no overriding is done (only a positive value
+   * is applied).
+   */
+  attribute float overrideDPPX;
+
   /** Disable entire author style level (including HTML presentation hints) */
   attribute boolean authorStyleDisabled;
 
   /**
-   * XXX comm-central only: bug 829543. Not the Character Encoding menu in 
-     * browser!
+   * XXX comm-central only: bug 829543. Not the Character Encoding menu in
+   * browser!
    */
   attribute ACString forceCharacterSet;
 
   /**
    * XXX comm-central only: bug 829543.
    */
   attribute ACString hintCharacterSet;
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -988,16 +988,17 @@ TransferZoomLevels(nsIDocument* aFromDoc
 
   nsPresContext* toCtxt = toShell->GetPresContext();
   if (!toCtxt)
     return;
 
   toCtxt->SetFullZoom(fromCtxt->GetFullZoom());
   toCtxt->SetBaseMinFontSize(fromCtxt->BaseMinFontSize());
   toCtxt->SetTextZoom(fromCtxt->TextZoom());
+  toCtxt->SetOverrideDPPX(fromCtxt->GetOverrideDPPX());
 }
 
 void
 TransferShowingState(nsIDocument* aFromDoc, nsIDocument* aToDoc)
 {
   MOZ_ASSERT(aFromDoc && aToDoc,
              "transferring showing state from/to null doc");
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5453,16 +5453,22 @@ nsGlobalWindow::GetDevicePixelRatioOuter
   if (!presContext) {
     return 1.0;
   }
 
   if (nsContentUtils::ShouldResistFingerprinting(mDocShell)) {
     return 1.0;
   }
 
+  float overrideDPPX = presContext->GetOverrideDPPX();
+
+  if (overrideDPPX > 0) {
+    return overrideDPPX;
+  }
+
   return float(nsPresContext::AppUnitsPerCSSPixel())/
       presContext->AppUnitsPerDevPixel();
 }
 
 float
 nsGlobalWindow::GetDevicePixelRatio(ErrorResult& aError)
 {
   FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (), aError, 0.0);
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -66,16 +66,17 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug653364.html]
 [test_bug861217.html]
 [test_clientRects.html]
 [test_clipboard_disallowed.html]
 [test_clipboard_events.html]
 subsuite = clipboard
 skip-if = buildapp == 'b2g' # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined)
 [test_consoleAPI.html]
+[test_contentViewer_overrideDPPX.html]
 [test_DOMMatrix.html]
 [test_domWindowUtils.html]
 [test_domWindowUtils_scrollXY.html]
 [test_domWindowUtils_scrollbarSize.html]
 [test_donottrack.html]
 skip-if = buildapp == 'mulet'
 [test_focus_legend_noparent.html]
 [test_focusrings.xul]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_contentViewer_overrideDPPX.html
@@ -0,0 +1,314 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>nsIContentViewer::overrideDPPX test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+
+<body>
+
+<iframe></iframe>
+
+<script type="application/javascript;version=1.8">
+
+SimpleTest.waitForExplicitFinish();
+
+const frameWindow = document.querySelector("iframe").contentWindow;
+
+const originalDPR = window.devicePixelRatio;
+const originalZoom = SpecialPowers.getFullZoom(window);
+const dppx = originalDPR * 1.5;
+const zoom = originalZoom * 0.5;
+
+const setOverrideDPPX = (value) => {
+  if (value > 0) {
+    info(`override window's dppx to ${value}`);
+  } else {
+    info(`restore window's dppx to default value`);
+  }
+
+  SpecialPowers.setOverrideDPPX(window, value);
+}
+
+const setFullZoom = (value) => {
+  info(`set window's fullZoom to ${value}`);
+  SpecialPowers.setFullZoom(window, value);
+}
+
+const createFontStyleForDPPX = (doc, value, size) => {
+  info(`adding a stylesheet that set font size to ${size}px for ${value}dppx`);
+
+  let style = doc.createElement("style");
+
+  style.setAttribute("media", `(resolution: ${value}dppx)`);
+
+  doc.head.appendChild(style);
+
+  style.sheet.insertRule(`body {font-size: ${size}px}`, 0);
+
+  return style;
+}
+
+const getBodyFontSize = (w) => w.getComputedStyle(w.document.body).fontSize;
+
+const assertValuesAreInitial = () => {
+  is(window.devicePixelRatio, originalDPR,
+    "devicePixelRatio has the original value.");
+  is(SpecialPowers.getFullZoom(window), originalZoom,
+    "fullZoom has the original value.");
+
+  is(frameWindow.devicePixelRatio, originalDPR,
+    "devicePixelRatio has the original value.");
+  is(SpecialPowers.getFullZoom(frameWindow), originalZoom,
+    "fullZoom has the original value.");
+}
+
+const gTests = {
+  "test overrideDPPX with devicePixelRatio": (done) => {
+    assertValuesAreInitial();
+
+    setOverrideDPPX(dppx);
+
+    is(window.devicePixelRatio, dppx,
+      "devicePixelRatio overridden.");
+    is(frameWindow.devicePixelRatio, dppx,
+      "frame's devicePixelRatio overridden.");
+
+    setOverrideDPPX(0);
+
+    is(window.devicePixelRatio, originalDPR,
+      "devicePixelRatio back to default.");
+    is(frameWindow.devicePixelRatio, originalDPR,
+      "frame's devicePixelRatio back to default.");
+
+    done();
+  },
+  "test overrideDPPX with devicePixelRatio and fullZoom": (done) => {
+    assertValuesAreInitial();
+
+    setFullZoom(zoom);
+    setOverrideDPPX(dppx);
+
+    is(window.devicePixelRatio, dppx,
+      "devicePixelRatio overridden; fullZoom ignored");
+    is(frameWindow.devicePixelRatio, dppx,
+      "frame's devicePixelRatio overridden; fullZoom ignored");
+
+    setOverrideDPPX(0);
+
+    is(window.devicePixelRatio, originalDPR * zoom,
+      "devicePixelRatio now is affected by fullZoom");
+    is(frameWindow.devicePixelRatio, originalDPR * zoom,
+      "frame's devicePixelRatio now is affected by fullZoom");
+    isnot(dppx, originalDPR * zoom,
+          "test is no longer testing what it should be");
+
+    setFullZoom(originalZoom);
+
+    is(window.devicePixelRatio, originalDPR,
+      "devicePixelRatio back to default.");
+    is(frameWindow.devicePixelRatio, originalDPR,
+      "frame's devicePixelRatio back to default.");
+
+    done();
+
+  },
+  "test overrideDPPX with media queries": (done) => {
+    assertValuesAreInitial();
+
+    let frameDoc = frameWindow.document;
+
+    let originalFontSize = getBodyFontSize(window);
+    let frameOriginalFontSize = getBodyFontSize(frameWindow);
+
+    let style = createFontStyleForDPPX(document, dppx, "32");
+    let frameStyle = createFontStyleForDPPX(frameDoc, dppx, "32");
+
+    let currentFontSize = getBodyFontSize(window);
+    let frameCurrentFontSize = getBodyFontSize(frameWindow);
+
+    is(currentFontSize, originalFontSize,
+      "media queries are not applied yet");
+    is(frameCurrentFontSize, frameOriginalFontSize,
+      "frame's media queries are not applied yet");
+
+    setOverrideDPPX(dppx);
+
+    currentFontSize = getBodyFontSize(window);
+    frameCurrentFontSize = getBodyFontSize(frameWindow);
+
+    isnot(currentFontSize, originalFontSize,
+      "media queries are applied.");
+    isnot(frameCurrentFontSize, frameOriginalFontSize,
+      "frame's media queries are applied.");
+
+    is(currentFontSize, "32px",
+      "font size has the expected value.");
+    is(frameCurrentFontSize, "32px",
+      "frame's font size has the expected value.");
+
+    setOverrideDPPX(0);
+
+    currentFontSize = getBodyFontSize(window);
+    frameCurrentFontSize = getBodyFontSize(frameWindow);
+
+    is(currentFontSize, originalFontSize,
+      "media queries are not applied anymore.");
+    is(frameCurrentFontSize, frameOriginalFontSize,
+      "media queries are not applied anymore.");
+
+    style.remove();
+    frameStyle.remove();
+
+    done();
+  },
+  "test overrideDPPX with media queries and fullZoom": (done) => {
+    assertValuesAreInitial();
+
+    let frameDoc = frameWindow.document;
+
+    let styles = [
+      createFontStyleForDPPX(document, originalDPR, "23"),
+      createFontStyleForDPPX(document, dppx, "32"),
+      createFontStyleForDPPX(document, originalDPR * zoom, "48"),
+      createFontStyleForDPPX(frameDoc, originalDPR, "23"),
+      createFontStyleForDPPX(frameDoc, dppx, "32"),
+      createFontStyleForDPPX(frameDoc, originalDPR * zoom, "48")
+    ];
+
+    let currentFontSize = getBodyFontSize(window);
+    let frameCurrentFontSize = getBodyFontSize(frameWindow);
+    is(currentFontSize, "23px",
+      "media queries are not applied yet");
+    is(frameCurrentFontSize, "23px",
+      "frame's media queries are not applied yet");
+
+    setFullZoom(zoom);
+    setOverrideDPPX(dppx);
+
+    currentFontSize = getBodyFontSize(window);
+    frameCurrentFontSize = getBodyFontSize(frameWindow);
+    is(currentFontSize, "32px",
+      "media queries are applied for overridden DDPX; fullZoom ignored.");
+    is(frameCurrentFontSize, "32px",
+      "frame's media queries are applied for overridden DDPX; fullZoom ignored.");
+
+    setOverrideDPPX(0);
+
+    currentFontSize = getBodyFontSize(window);
+    frameCurrentFontSize = getBodyFontSize(frameWindow);
+    is(currentFontSize, "48px",
+      "media queries are applied for fullZoom.");
+    is(frameCurrentFontSize, "48px",
+      "frame's media queries are applied for fullZoom.");
+
+    setFullZoom(originalZoom);
+
+    currentFontSize = getBodyFontSize(window);
+    frameCurrentFontSize = getBodyFontSize(frameWindow);
+    is(currentFontSize, "23px",
+      "media queries are not applied anymore.");
+    is(frameCurrentFontSize, "23px",
+      "frame's media queries are not applied anymore.");
+
+    styles.forEach(style => style.remove());
+
+    done();
+  },
+  "test OverrideDPPX with MediaQueryList": (done) => {
+    assertValuesAreInitial();
+
+    let promises = [
+      new Promise(resolve => {
+        let mql = window.matchMedia(`(resolution: ${dppx}dppx)`);
+
+        mql.addListener(function listener() {
+          ok("MediaQueryList's listener invoked.")
+          mql.removeListener(listener);
+          resolve();
+        });
+      }),
+      new Promise(resolve => {
+        let mql = frameWindow.matchMedia(`(resolution: ${dppx}dppx)`);
+
+        mql.addListener(function listener() {
+          ok("frame's MediaQueryList's listener invoked.")
+          mql.removeListener(listener);
+          resolve();
+        });
+      })
+    ];
+
+    Promise.all(promises)
+      .then(() => setOverrideDPPX(0))
+      .then(done, e => {throw e});
+
+    setOverrideDPPX(dppx);
+  },
+  "test OverrideDPPX with MediaQueryList and fullZoom": (done) => {
+    assertValuesAreInitial();
+
+    let overridden = false;
+
+    let promises = [
+      new Promise(resolve => {
+        let mql = window.matchMedia(`(resolution: ${dppx}dppx)`);
+
+        mql.addListener(function listener() {
+          ok("MediaQueryList's listener for dppx invoked.");
+          mql.removeListener(listener);
+          overridden = true;
+          resolve();
+        });
+      }),
+      new Promise(resolve => {
+        let mql = window.matchMedia(`(resolution: ${originalDPR * zoom}dppx)`);
+
+        mql.addListener(function listener() {
+          ok(overridden,
+            "MediaQueryList's listener for zoom invoked in the right order");
+
+          mql.removeListener(listener);
+          resolve();
+        });
+      })
+    ];
+
+    promises[0]
+      .then(() => setOverrideDPPX(0))
+      .then(promises[1])
+      .then(() => setFullZoom(originalZoom))
+      .then(done, e => {throw e});
+
+    setOverrideDPPX(dppx);
+    setFullZoom(zoom);
+  }
+};
+
+function* runner(tests) {
+  for (let name of Object.keys(tests)) {
+    info(name);
+    tests[name](next);
+    yield undefined;
+  }
+};
+
+const gTestRunner = runner(gTests);
+
+function next() {
+  SimpleTest.executeSoon(function() {
+    if (gTestRunner.next().done) {
+      SimpleTest.finish();
+    }
+  });
+}
+
+// Run the tests
+addLoadEvent(next);
+
+</script>
+
+</body>
+</html>
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -358,16 +358,17 @@ protected:
   bool mAttachedToParent; // view is attached to the parent widget
 
   nsIntRect mBounds;
 
   // mTextZoom/mPageZoom record the textzoom/pagezoom of the first (galley)
   // presshell only.
   float mTextZoom;      // Text zoom, defaults to 1.0
   float mPageZoom;
+  float mOverrideDPPX;  // DPPX overrided, defaults to 0.0
   int mMinFontSize;
 
   int16_t mNumURLStarts;
   int16_t mDestroyRefCount;    // a second "refcount" for the document viewer's "destroy"
 
   unsigned      mStopped : 1;
   unsigned      mLoaded : 1;
   unsigned      mDeferredWindowClose : 1;
@@ -476,17 +477,17 @@ void nsDocumentViewer::PrepareToStartLoa
   mDebugFile = nullptr;
 #endif
 
 #endif // NS_PRINTING
 }
 
 // Note: operator new zeros our memory, so no need to init things to null.
 nsDocumentViewer::nsDocumentViewer()
-  : mTextZoom(1.0), mPageZoom(1.0), mMinFontSize(0),
+  : mTextZoom(1.0), mPageZoom(1.0), mOverrideDPPX(0.0), mMinFontSize(0),
     mIsSticky(true),
 #ifdef NS_PRINT_PREVIEW
     mPrintPreviewZoom(1.0),
 #endif
     mHintCharsetSource(kCharsetUninitialized),
     mInitializedForPrintPreview(false),
     mHidden(false)
 {
@@ -659,16 +660,17 @@ nsDocumentViewer::InitPresentationStuff(
   MOZ_ASSERT(p2a ==
              mPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
   nscoord width = p2a * mBounds.width;
   nscoord height = p2a * mBounds.height;
 
   mViewManager->SetWindowDimensions(width, height);
   mPresContext->SetTextZoom(mTextZoom);
   mPresContext->SetFullZoom(mPageZoom);
+  mPresContext->SetOverrideDPPX(mOverrideDPPX);
   mPresContext->SetBaseMinFontSize(mMinFontSize);
 
   p2a = mPresContext->AppUnitsPerDevPixel();  // zoom may have changed it
   width = p2a * mBounds.width;
   height = p2a * mBounds.height;
   if (aDoInitialReflow) {
     nsCOMPtr<nsIPresShell> shellGrip = mPresShell;
     // Initial reflow
@@ -2831,16 +2833,23 @@ SetChildMinFontSize(nsIContentViewer* aC
 
 static void
 SetChildFullZoom(nsIContentViewer* aChild, void* aClosure)
 {
   struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure;
   aChild->SetFullZoom(ZoomInfo->mZoom);
 }
 
+static void
+SetChildOverrideDPPX(nsIContentViewer* aChild, void* aClosure)
+{
+  struct ZoomInfo* ZoomInfo = (struct ZoomInfo*) aClosure;
+  aChild->SetOverrideDPPX(ZoomInfo->mZoom);
+}
+
 static bool
 SetExtResourceTextZoom(nsIDocument* aDocument, void* aClosure)
 {
   // Would it be better to enumerate external resource viewers instead?
   nsIPresShell* shell = aDocument->GetShell();
   if (shell) {
     nsPresContext* ctxt = shell->GetPresContext();
     if (ctxt) {
@@ -2877,16 +2886,31 @@ SetExtResourceFullZoom(nsIDocument* aDoc
       struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
       ctxt->SetFullZoom(ZoomInfo->mZoom);
     }
   }
 
   return true;
 }
 
+static bool
+SetExtResourceOverrideDPPX(nsIDocument* aDocument, void* aClosure)
+{
+  nsIPresShell* shell = aDocument->GetShell();
+  if (shell) {
+    nsPresContext* ctxt = shell->GetPresContext();
+    if (ctxt) {
+      struct ZoomInfo* ZoomInfo = static_cast<struct ZoomInfo*>(aClosure);
+      ctxt->SetOverrideDPPX(ZoomInfo->mZoom);
+    }
+  }
+
+  return true;
+}
+
 NS_IMETHODIMP
 nsDocumentViewer::SetTextZoom(float aTextZoom)
 {
   // If we don't have a document, then we need to bail.
   if (!mDocument) {
     return NS_ERROR_FAILURE;
   }
 
@@ -3042,16 +3066,50 @@ nsDocumentViewer::GetFullZoom(float* aFu
 #endif
   // Check the prescontext first because it might have a temporary
   // setting for print-preview
   nsPresContext* pc = GetPresContext();
   *aFullZoom = pc ? pc->GetFullZoom() : mPageZoom;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDocumentViewer::SetOverrideDPPX(float aDPPX)
+{
+  // If we don't have a document, then we need to bail.
+  if (!mDocument) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mOverrideDPPX = aDPPX;
+
+  struct ZoomInfo ZoomInfo = { aDPPX };
+  CallChildren(SetChildOverrideDPPX, &ZoomInfo);
+
+  nsPresContext* pc = GetPresContext();
+  if (pc) {
+    pc->SetOverrideDPPX(aDPPX);
+  }
+
+  // And do the external resources
+  mDocument->EnumerateExternalResources(SetExtResourceOverrideDPPX, &ZoomInfo);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetOverrideDPPX(float* aDPPX)
+{
+  NS_ENSURE_ARG_POINTER(aDPPX);
+
+  nsPresContext* pc = GetPresContext();
+  *aDPPX = pc ? pc->GetOverrideDPPX() : mOverrideDPPX;
+  return NS_OK;
+}
+
 static void
 SetChildAuthorStyleDisabled(nsIContentViewer* aChild, void* aClosure)
 {
   bool styleDisabled  = *static_cast<bool*>(aClosure);
   aChild->SetAuthorStyleDisabled(styleDisabled);
 }
 
 
@@ -4277,16 +4335,17 @@ nsDocumentViewer::ReturnToGalleyPresenta
   mPrintEngine->Destroy();
   mPrintEngine = nullptr;
 
   nsCOMPtr<nsIDocShell> docShell(mContainer);
   ResetFocusState(docShell);
 
   SetTextZoom(mTextZoom);
   SetFullZoom(mPageZoom);
+  SetOverrideDPPX(mOverrideDPPX);
   SetMinFontSize(mMinFontSize);
   Show();
 
 #endif // NS_PRINTING && NS_PRINT_PREVIEW
 }
 
 //------------------------------------------------------------
 // This called ONLY when printing has completed and the DV
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -200,17 +200,17 @@ IsVisualCharset(const nsCString& aCharse
   }
 }
 
   // NOTE! nsPresContext::operator new() zeroes out all members, so don't
   // bother initializing members to 0.
 
 nsPresContext::nsPresContext(nsIDocument* aDocument, nsPresContextType aType)
   : mType(aType), mDocument(aDocument), mBaseMinFontSize(0),
-    mTextZoom(1.0), mFullZoom(1.0),
+    mTextZoom(1.0), mFullZoom(1.0), mOverrideDPPX(0.0),
     mLastFontInflationScreenSize(gfxSize(-1.0, -1.0)),
     mPageSize(-1, -1), mPPScale(1.0f),
     mViewportStyleScrollbar(NS_STYLE_OVERFLOW_AUTO, NS_STYLE_OVERFLOW_AUTO),
     mImageAnimationModePref(imgIContainer::kNormalAnimMode),
     mAllInvalidated(false),
     mPaintFlashing(false), mPaintFlashingInitialized(false)
 {
   // NOTE! nsPresContext::operator new() zeroes out all members, so don't
@@ -1303,16 +1303,26 @@ nsPresContext::SetFullZoom(float aZoom)
     SetWindowDimensions(NSToCoordRound(oldWidthDevPixels * AppUnitsPerDevPixel()),
                         NSToCoordRound(oldHeightDevPixels * AppUnitsPerDevPixel()));
 
   AppUnitsPerDevPixelChanged();
 
   mSuppressResizeReflow = false;
 }
 
+void
+nsPresContext::SetOverrideDPPX(float aDPPX)
+{
+  mOverrideDPPX = aDPPX;
+
+  if (HasCachedStyleData()) {
+    MediaFeatureValuesChanged(nsRestyleHint(0), nsChangeHint(0));
+  }
+}
+
 gfxSize
 nsPresContext::ScreenSizeInchesForFontInflation(bool* aChanged)
 {
   if (aChanged) {
     *aChanged = false;
   }
 
   nsDeviceContext *dx = DeviceContext();
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -596,16 +596,19 @@ public:
       MediaFeatureValuesChanged(eRestyle_ForceDescendants,
                                 NS_STYLE_HINT_REFLOW);
     }
   }
 
   float GetFullZoom() { return mFullZoom; }
   void SetFullZoom(float aZoom);
 
+  float GetOverrideDPPX() { return mOverrideDPPX; }
+  void SetOverrideDPPX(float aDPPX);
+
   nscoord GetAutoQualityMinFontSize() {
     return DevPixelsToAppUnits(mAutoQualityMinFontSizePixelsPref);
   }
 
   /**
    * Return the device's screen size in inches, for font size
    * inflation.
    *
@@ -1249,17 +1252,17 @@ public:
 protected:
 
   mozilla::WeakPtr<nsDocShell>             mContainer;
 
   // Base minimum font size, independent of the language-specific global preference. Defaults to 0
   int32_t               mBaseMinFontSize;
   float                 mTextZoom;      // Text zoom, defaults to 1.0
   float                 mFullZoom;      // Page zoom, defaults to 1.0
-
+  float                 mOverrideDPPX;   // DPPX overrided, defaults to 0.0
   gfxSize               mLastFontInflationScreenSize;
 
   int32_t               mCurAppUnitsPerDevPixel;
   int32_t               mAutoQualityMinFontSizePixelsPref;
 
   nsCOMPtr<nsITheme> mTheme;
   nsCOMPtr<nsILanguageAtomService> mLangService;
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
--- a/layout/style/CSSStyleSheet.cpp
+++ b/layout/style/CSSStyleSheet.cpp
@@ -228,17 +228,21 @@ nsMediaExpression::Matches(nsPresContext
                      actual.GetUnit() == eCSSUnit_Pixel ||
                      actual.GetUnit() == eCSSUnit_Centimeter,
                      "bad actual value");
         NS_ASSERTION(required.GetUnit() == eCSSUnit_Inch ||
                      required.GetUnit() == eCSSUnit_Pixel ||
                      required.GetUnit() == eCSSUnit_Centimeter,
                      "bad required value");
         float actualDPI = actual.GetFloatValue();
-        if (actual.GetUnit() == eCSSUnit_Centimeter) {
+        float overrideDPPX = aPresContext->GetOverrideDPPX();
+
+        if (overrideDPPX > 0) {
+          actualDPI = overrideDPPX * 96.0f;
+        } else if (actual.GetUnit() == eCSSUnit_Centimeter) {
           actualDPI = actualDPI * 2.54f;
         } else if (actual.GetUnit() == eCSSUnit_Pixel) {
           actualDPI = actualDPI * 96.0f;
         }
         float requiredDPI = required.GetFloatValue();
         if (required.GetUnit() == eCSSUnit_Centimeter) {
           requiredDPI = requiredDPI * 2.54f;
         } else if (required.GetUnit() == eCSSUnit_Pixel) {
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -1424,16 +1424,23 @@ SpecialPowersAPI.prototype = {
   },
   getTextZoom: function(window) {
     return this._getMUDV(window).textZoom;
   },
   setTextZoom: function(window, zoom) {
     this._getMUDV(window).textZoom = zoom;
   },
 
+  getOverrideDPPX: function(window) {
+    return this._getMUDV(window).overrideDPPX;
+  },
+  setOverrideDPPX: function(window, dppx) {
+    this._getMUDV(window).overrideDPPX = dppx;
+  },
+
   emulateMedium: function(window, mediaType) {
     this._getMUDV(window).emulateMedium(mediaType);
   },
   stopEmulatingMedium: function(window) {
     this._getMUDV(window).stopEmulatingMedium();
   },
 
   snapshotWindowWithOptions: function (win, rect, bgcolor, options) {