bug 674373 pt 6 - support HiDPI display in Cocoa widget code. r=roc,smichaud
authorJonathan Kew <jkew@mozilla.com>
Sat, 29 Sep 2012 12:36:09 +0100
changeset 108627 1d3de8da2508e3acdaa3ddf1e0769472d3704f0b
parent 108626 550641381dfaf0e8144e4aec9e458dfd8c125aeb
child 108628 879cce846c1e5f6d81f5889cfedbd39c2f143372
child 108632 86acf4b667c822c8f14be779577b921a37a1c987
push id23568
push userryanvm@gmail.com
push dateSat, 29 Sep 2012 16:32:00 +0000
treeherdermozilla-central@879cce846c1e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, smichaud
bugs674373
milestone18.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 674373 pt 6 - support HiDPI display in Cocoa widget code. r=roc,smichaud
browser/base/content/tabbrowser.xml
layout/style/test/test_media_queries.html
layout/xul/base/src/nsMenuPopupFrame.h
layout/xul/base/src/nsXULPopupManager.cpp
modules/libpref/src/init/all.js
widget/cocoa/nsChildView.h
widget/cocoa/nsChildView.mm
widget/cocoa/nsCocoaUtils.h
widget/cocoa/nsCocoaUtils.mm
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
widget/cocoa/nsDragService.mm
widget/cocoa/nsScreenCocoa.h
widget/cocoa/nsScreenCocoa.mm
widget/nsIWidget.h
widget/xpwidgets/nsBaseWidget.cpp
widget/xpwidgets/nsBaseWidget.h
xpfe/appshell/src/nsXULWindow.cpp
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3572,22 +3572,27 @@
         // otherwise trying to deatch the tab by dropping it on the desktop
         // may result in an "internet shortcut"
         dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
 
         // Set the cursor to an arrow during tab drags.
         dt.mozCursor = "default";
 
         // Create a canvas to which we capture the current tab.
+        // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
+        // canvas size (in CSS pixels) to the window's backing resolution in order
+        // to get a full-resolution drag image for use on HiDPI displays.
+        let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+        let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
         let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
         canvas.mozOpaque = true;
-        canvas.width = 160;
-        canvas.height = 90;
+        canvas.width = 160 * scale;
+        canvas.height = 90 * scale;
         PageThumbs.captureToCanvas(browser.contentWindow, canvas);
-        dt.setDragImage(canvas, -16, -16);
+        dt.setDragImage(canvas, -16 * scale, -16 * scale);
 
         // _dragData.offsetX/Y give the coordinates that the mouse should be
         // positioned relative to the corner of the new window created upon
         // dragend such that the mouse appears to have the same position
         // relative to the corner of the dragged tab.
         function clientX(ele) ele.getBoundingClientRect().left;
         let tabOffsetX = clientX(tab) - clientX(this);
         tab._dragData = {
--- a/layout/style/test/test_media_queries.html
+++ b/layout/style/test/test_media_queries.html
@@ -20,17 +20,17 @@ 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() {
+function getScreenPixelsPerCSSPixel() {
   return SpecialPowers.DOMWindowUtils.screenPixelsPerCSSPixel;
 }
 
 function run() {
   iframe = document.getElementById("subdoc");
   var subdoc = iframe.contentDocument;
   var subwin = iframe.contentWindow;
   var style = subdoc.getElementById("style");
@@ -352,19 +352,19 @@ 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();
+  var real_dpr = 1.0 * getScreenPixelsPerCSSPixel();
+  var high_dpr = 1.1 * getScreenPixelsPerCSSPixel();
+  var low_dpr = 0.9 * getScreenPixelsPerCSSPixel();
   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 + ")");
--- a/layout/xul/base/src/nsMenuPopupFrame.h
+++ b/layout/xul/base/src/nsMenuPopupFrame.h
@@ -259,19 +259,19 @@ public:
   NS_IMETHOD GetFrameName(nsAString& aResult) const MOZ_OVERRIDE
   {
       return MakeFrameName(NS_LITERAL_STRING("MenuPopup"), aResult);
   }
 #endif
 
   void EnsureMenuItemIsVisible(nsMenuFrame* aMenuFrame);
 
-  // Move the popup to the screen coordinate (aLeft, aTop). If aUpdateAttrs
-  // is true, and the popup already has left or top attributes, then those
-  // attributes are updated to the new location.
+  // Move the popup to the screen coordinate (aLeft, aTop) in CSS pixels.
+  // If aUpdateAttrs is true, and the popup already has left or top attributes,
+  // then those attributes are updated to the new location.
   // The frame may be destroyed by this method.
   void MoveTo(int32_t aLeft, int32_t aTop, bool aUpdateAttrs);
 
   bool GetAutoPosition();
   void SetAutoPosition(bool aShouldAutoPosition);
   void SetConsumeRollupEvent(uint32_t aConsumeMode);
 
   nsIScrollableFrame* GetScrollFrame(nsIFrame* aStart);
@@ -297,16 +297,17 @@ public:
 
   // Return true if the popup is positioned relative to an anchor.
   bool IsAnchored() const { return mScreenXPos == -1 && mScreenYPos == -1; }
 
   // Return the anchor if there is one.
   nsIContent* GetAnchor() const { return mAnchorContent; }
 
   // Return the screen coordinates of the popup, or (-1, -1) if anchored.
+  // This position is in CSS pixels.
   nsIntPoint ScreenPosition() const { return nsIntPoint(mScreenXPos, mScreenYPos); }
 
   NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                               const nsRect&           aDirtyRect,
                               const nsDisplayListSet& aLists) MOZ_OVERRIDE;
 
   nsIntPoint GetLastClientOffset() const { return mLastClientOffset; }
 
@@ -382,18 +383,19 @@ protected:
   nsMenuFrame* mCurrentMenu; // The current menu that is active.
 
   // A popup's preferred size may be different than its actual size stored in
   // mRect in the case where the popup was resized because it was too large
   // for the screen. The preferred size mPrefSize holds the full size the popup
   // would be before resizing. Computations are performed using this size.
   nsSize mPrefSize;
 
-  // the position of the popup. The screen coordinates, if set to values other
-  // than -1, override mXPos and mYPos.
+  // The position of the popup, in CSS pixels.
+  // The screen coordinates, if set to values other than -1,
+  // override mXPos and mYPos.
   int32_t mXPos;
   int32_t mYPos;
   int32_t mScreenXPos;
   int32_t mScreenYPos;
   // The value of the client offset of our widget the last time we positioned
   // ourselves. We store this so that we can detect when it changes but the
   // position of our widget didn't change.
   nsIntPoint mLastClientOffset;
--- a/layout/xul/base/src/nsXULPopupManager.cpp
+++ b/layout/xul/base/src/nsXULPopupManager.cpp
@@ -311,16 +311,21 @@ nsMenuPopupFrame* GetPopupToMoveOrResize
 
 void
 nsXULPopupManager::PopupMoved(nsIFrame* aFrame, nsIntPoint aPnt)
 {
   nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
   if (!menuPopupFrame)
     return;
 
+  // Convert desired point to CSS pixels for comparison
+  nsPresContext* presContext = menuPopupFrame->PresContext();
+  aPnt.x = presContext->DevPixelsToIntCSSPixels(aPnt.x);
+  aPnt.y = presContext->DevPixelsToIntCSSPixels(aPnt.y);
+
   // Don't do anything if the popup is already at the specified location. This
   // prevents recursive calls when a popup is positioned.
   nsIntPoint currentPnt = menuPopupFrame->ScreenPosition();
   nsIWidget* widget = menuPopupFrame->GetWidget();
   if ((aPnt.x != currentPnt.x || aPnt.y != currentPnt.y) || (widget &&
       widget->GetClientOffset() != menuPopupFrame->GetLastClientOffset())) {
     // Update the popup's position using SetPopupPosition if the popup is
     // anchored and at the parent level as these maintain their position
@@ -341,23 +346,30 @@ nsXULPopupManager::PopupResized(nsIFrame
 {
   nsMenuPopupFrame* menuPopupFrame = GetPopupToMoveOrResize(aFrame);
   if (!menuPopupFrame)
     return;
 
   nsPresContext* presContext = menuPopupFrame->PresContext();
 
   nsSize currentSize = menuPopupFrame->GetSize();
-  if (aSize.width != presContext->AppUnitsToDevPixels(currentSize.width) ||
-      aSize.height != presContext->AppUnitsToDevPixels(currentSize.height)) {
+
+  // convert both current and new sizes to integer CSS pixels for comparison;
+  // we won't set attributes if there is only a sub-CSS-pixel discrepancy
+  nsIntSize currCSS(nsPresContext::AppUnitsToIntCSSPixels(currentSize.width),
+                    nsPresContext::AppUnitsToIntCSSPixels(currentSize.height));
+  nsIntSize newCSS(presContext->DevPixelsToIntCSSPixels(aSize.width),
+                   presContext->DevPixelsToIntCSSPixels(aSize.height));
+
+  if (newCSS.width != currCSS.width || newCSS.height != currCSS.height) {
     // for resizes, we just set the width and height attributes
     nsIContent* popup = menuPopupFrame->GetContent();
     nsAutoString width, height;
-    width.AppendInt(aSize.width);
-    height.AppendInt(aSize.height);
+    width.AppendInt(newCSS.width);
+    height.AppendInt(newCSS.height);
     popup->SetAttr(kNameSpaceID_None, nsGkAtoms::width, width, false);
     popup->SetAttr(kNameSpaceID_None, nsGkAtoms::height, height, true);
   }
 }
 
 nsMenuPopupFrame*
 nsXULPopupManager::GetPopupFrameForContent(nsIContent* aContent, bool aShouldFlush)
 {
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -181,16 +181,24 @@ pref("media.webaudio.enabled", false);
 
 // Whether to autostart a media element with an |autoplay| attribute
 pref("media.autoplay.enabled", true);
 
 // The default number of decoded video frames that are enqueued in
 // nsBuiltinDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 10);
 
+#ifdef XP_MACOSX
+// Whether to run in native HiDPI mode on machines with "Retina"/HiDPI display;
+//   <= 0 : hidpi mode disabled, display will just use pixel-based upscaling
+//   == 1 : hidpi supported if all screens share the same backingScaleFactor
+//   >= 2 : hidpi supported even with mixed backingScaleFactors (currently broken)
+pref("gfx.hidpi.enabled", 1);
+#endif
+
 // 0 = Off, 1 = Full, 2 = Tagged Images Only. 
 // See eCMSMode in gfx/thebes/gfxPlatform.h
 pref("gfx.color_management.mode", 2);
 pref("gfx.color_management.display_profile", "");
 pref("gfx.color_management.rendering_intent", 0);
 pref("gfx.color_management.enablev4", false);
 
 pref("gfx.downloadable_fonts.enabled", true);
@@ -1583,19 +1591,17 @@ pref("layout.css.visited_links_enabled",
 // This sets the physical size of a device pixel and thus controls the
 // interpretation of physical units such as "pt".
 pref("layout.css.dpi", -1);
 
 // Set the number of device pixels per CSS pixel. A value <= 0 means choose
 // automatically based on user settings for the platform (e.g., "UI scale factor"
 // on Mac). A positive value is used as-is. This effectively controls the size
 // of a CSS "px". This is only used for windows on the screen, not for printing.
-// XXX the default value here should be 0, but before we can set it to 0,
-// we have to get this feature working on all platforms.
-pref("layout.css.devPixelsPerPx", "1.0");
+pref("layout.css.devPixelsPerPx", "-1.0");
 
 // Is support for the the @supports rule enabled?
 pref("layout.css.supports-rule.enabled", true);
 
 // pref for which side vertical scrollbars should be on
 // 0 = end-side in UI direction
 // 1 = end-side in document/content direction
 // 2 = right
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -406,16 +406,26 @@ public:
   NS_IMETHOD              Resize(int32_t aWidth,int32_t aHeight, bool aRepaint);
   NS_IMETHOD              Resize(int32_t aX, int32_t aY,int32_t aWidth,int32_t aHeight, bool aRepaint);
 
   NS_IMETHOD              Enable(bool aState);
   virtual bool            IsEnabled() const;
   NS_IMETHOD              SetFocus(bool aRaise);
   NS_IMETHOD              GetBounds(nsIntRect &aRect);
 
+  // Returns the "backing scale factor" of the view's window, which is the
+  // ratio of pixels in the window's backing store to Cocoa points. Prior to
+  // HiDPI support in OS X 10.7, this was always 1.0, but in HiDPI mode it
+  // will be 2.0 (and might potentially other values as screen resolutions
+  // evolve). This gives the relationship between what Gecko calls "device
+  // pixels" and the Cocoa "points" coordinate system.
+  CGFloat                 BackingScaleFactor();
+
+  virtual double          GetDefaultScale();
+
   NS_IMETHOD              Invalidate(const nsIntRect &aRect);
 
   virtual void*           GetNativeData(uint32_t aDataType);
   virtual nsresult        ConfigureChildren(const nsTArray<Configuration>& aConfigurations);
   virtual nsIntPoint      WidgetToScreenOffset();
   virtual bool            ShowsResizeIndicator(nsIntRect* aResizerRect);
 
   static  bool            ConvertStatus(nsEventStatus aStatus)
@@ -511,16 +521,30 @@ public:
 
   NS_IMETHOD        ReparentNativeWidget(nsIWidget* aNewParent);
 
   mozilla::widget::TextInputHandler* GetTextInputHandler()
   {
     return mTextInputHandler;
   }
 
+  // unit conversion convenience functions
+  nsIntPoint        CocoaPointsToDevPixels(const NSPoint& aPt) {
+    return nsCocoaUtils::CocoaPointsToDevPixels(aPt, BackingScaleFactor());
+  }
+  nsIntRect         CocoaPointsToDevPixels(const NSRect& aRect) {
+    return nsCocoaUtils::CocoaPointsToDevPixels(aRect, BackingScaleFactor());
+  }
+  CGFloat           DevPixelsToCocoaPoints(int32_t aPixels) {
+    return nsCocoaUtils::DevPixelsToCocoaPoints(aPixels, BackingScaleFactor());
+  }
+  NSRect            DevPixelsToCocoaPoints(const nsIntRect& aRect) {
+    return nsCocoaUtils::DevPixelsToCocoaPoints(aRect, BackingScaleFactor());
+  }
+
 protected:
 
   void              ReportMoveEvent();
   void              ReportSizeEvent();
 
   // override to create different kinds of child views. Autoreleases, so
   // caller must retain.
   virtual NSView*   CreateCocoaView(NSRect inFrame);
@@ -547,16 +571,22 @@ protected:
   // weak ref to this childview's associated mozAccessible for speed reasons 
   // (we get queried for it *a lot* but don't want to own it)
   nsWeakPtr             mAccessible;
 #endif
 
   nsRefPtr<gfxASurface> mTempThebesSurface;
   nsRefPtr<mozilla::gl::TextureImage> mResizerImage;
 
+  // Cached value of [mView backingScaleFactor], to avoid sending two obj-c
+  // messages (respondsToSelector, backingScaleFactor) every time we need to
+  // use it.
+  // ** We'll need to reinitialize this if the backing resolution changes. **
+  CGFloat               mBackingScaleFactor;
+
   bool                  mVisible;
   bool                  mDrawing;
   bool                  mPluginDrawing;
   bool                  mIsDispatchPaint; // Is a paint event being dispatched
 
   NP_CGContext          mPluginCGContext;
   nsIPluginInstanceOwner* mPluginInstanceOwner; // [WEAK]
 
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -191,17 +191,17 @@ ConvertGeckoRectToMacRect(const nsIntRec
   outMacRect.right = aRect.x + aRect.width;
   outMacRect.bottom = aRect.y + aRect.height;
 }
 
 // Flips a screen coordinate from a point in the cocoa coordinate system (bottom-left rect) to a point
 // that is a "flipped" cocoa coordinate system (starts in the top-left).
 static inline void
 FlipCocoaScreenCoordinate(NSPoint &inPoint)
-{  
+{
   inPoint.y = nsCocoaUtils::FlippedScreenY(inPoint.y);
 }
 
 void EnsureLogInitialized()
 {
 #ifdef PR_LOGGING
   if (!sCocoaLog) {
     sCocoaLog = PR_NewLogModule("nsCocoaWidgets");
@@ -210,16 +210,17 @@ void EnsureLogInitialized()
 }
 
 #pragma mark -
 
 nsChildView::nsChildView() : nsBaseWidget()
 , mView(nullptr)
 , mParentView(nullptr)
 , mParentWidget(nullptr)
+, mBackingScaleFactor(0.0)
 , mVisible(false)
 , mDrawing(false)
 , mPluginDrawing(false)
 , mIsDispatchPaint(false)
 , mPluginInstanceOwner(nullptr)
 {
   EnsureLogInitialized();
 
@@ -314,20 +315,22 @@ nsresult nsChildView::Create(nsIWidget *
     // This is the normal case. When we're the root widget of the view hiararchy,
     // aNativeParent will be the contentView of our window, since that's what
     // nsCocoaWindow returns when asked for an NS_NATIVE_VIEW.
     mParentView = reinterpret_cast<NSView<mozView>*>(aNativeParent);
   }
   
   // create our parallel NSView and hook it up to our parent. Recall
   // that NS_NATIVE_WIDGET is the NSView.
-  NSRect r;
-  nsCocoaUtils::GeckoRectToNSRect(mBounds, r);
+  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mParentView);
+  NSRect r = nsCocoaUtils::DevPixelsToCocoaPoints(mBounds, scaleFactor);
   mView = [(NSView<mozView>*)CreateCocoaView(r) retain];
-  if (!mView) return NS_ERROR_FAILURE;
+  if (!mView) {
+    return NS_ERROR_FAILURE;
+  }
 
   [(ChildView*)mView setIsPluginView:(mWindowType == eWindowType_plugin)];
 
   // If this view was created in a Gecko view hierarchy, the initial state
   // is hidden.  If the view is attached only to a native NSView but has
   // no Gecko parent (as in embedding), the initial state is visible.
   if (mParentWidget)
     [mView setHidden:YES];
@@ -747,22 +750,40 @@ NS_IMETHODIMP nsChildView::SetCursor(img
 #pragma mark -
 
 // Get this component dimension
 NS_IMETHODIMP nsChildView::GetBounds(nsIntRect &aRect)
 {
   if (!mView) {
     aRect = mBounds;
   } else {
-    NSRect frame = [mView frame];
-    NSRectToGeckoRect(frame, aRect);
+    aRect = CocoaPointsToDevPixels([mView frame]);
   }
   return NS_OK;
 }
 
+double
+nsChildView::GetDefaultScale()
+{
+  return BackingScaleFactor();
+}
+
+CGFloat
+nsChildView::BackingScaleFactor()
+{
+  if (mBackingScaleFactor > 0.0) {
+    return mBackingScaleFactor;
+  }
+  if (!mView) {
+    return 1.0;
+  }
+  mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mView);
+  return mBackingScaleFactor;
+}
+
 NS_IMETHODIMP nsChildView::ConstrainPosition(bool aAllowSlop,
                                              int32_t *aX, int32_t *aY)
 {
   return NS_OK;
 }
 
 // Move this component, aX and aY are in the parent widget coordinate system
 NS_IMETHODIMP nsChildView::Move(int32_t aX, int32_t aY)
@@ -770,19 +791,17 @@ NS_IMETHODIMP nsChildView::Move(int32_t 
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (!mView || (mBounds.x == aX && mBounds.y == aY))
     return NS_OK;
 
   mBounds.x = aX;
   mBounds.y = aY;
 
-  NSRect r;
-  nsCocoaUtils::GeckoRectToNSRect(mBounds, r);
-  [mView setFrame:r];
+  [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
 
   if (mVisible)
     [mView setNeedsDisplay:YES];
 
   NotifyRollupGeometryChange(gRollupListener);
   ReportMoveEvent();
 
   return NS_OK;
@@ -795,19 +814,17 @@ NS_IMETHODIMP nsChildView::Resize(int32_
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (!mView || (mBounds.width == aWidth && mBounds.height == aHeight))
     return NS_OK;
 
   mBounds.width  = aWidth;
   mBounds.height = aHeight;
 
-  NSRect r;
-  nsCocoaUtils::GeckoRectToNSRect(mBounds, r);
-  [mView setFrame:r];
+  [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
 
   if (mVisible && aRepaint)
     [mView setNeedsDisplay:YES];
 
   NotifyRollupGeometryChange(gRollupListener);
   ReportSizeEvent();
 
   return NS_OK;
@@ -828,19 +845,17 @@ NS_IMETHODIMP nsChildView::Resize(int32_
     mBounds.x = aX;
     mBounds.y = aY;
   }
   if (isResizing) {
     mBounds.width  = aWidth;
     mBounds.height = aHeight;
   }
 
-  NSRect r;
-  nsCocoaUtils::GeckoRectToNSRect(mBounds, r);
-  [mView setFrame:r];
+  [mView setFrame:DevPixelsToCocoaPoints(mBounds)];
 
   if (mVisible && aRepaint)
     [mView setNeedsDisplay:YES];
 
   NotifyRollupGeometryChange(gRollupListener);
   if (isMoving) {
     ReportMoveEvent();
     if (mOnDestroyCalled)
@@ -1282,26 +1297,23 @@ static void blinkRgn(RgnHandle rgn)
 // Invalidate this component's visible area
 NS_IMETHODIMP nsChildView::Invalidate(const nsIntRect &aRect)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   if (!mView || !mVisible)
     return NS_OK;
 
-  NSRect r;
-  nsCocoaUtils::GeckoRectToNSRect(aRect, r);
-  
   if ([NSView focusView]) {
     // if a view is focussed (i.e. being drawn), then postpone the invalidate so that we
     // don't lose it.
-    [mView setNeedsPendingDisplayInRect:r];
+    [mView setNeedsPendingDisplayInRect:DevPixelsToCocoaPoints(aRect)];
   }
   else {
-    [mView setNeedsDisplayInRect:r];
+    [mView setNeedsDisplayInRect:DevPixelsToCocoaPoints(aRect)];
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 bool
@@ -1444,37 +1456,36 @@ void nsChildView::ReportSizeEvent()
 {
   if (mWidgetListener)
     mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
 }
 
 #pragma mark -
 
 //    Return the offset between this child view and the screen.
-//    @return       -- widget origin in screen coordinates
+//    @return       -- widget origin in device-pixel coords
 nsIntPoint nsChildView::WidgetToScreenOffset()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
-  NSPoint temp;
-  temp.x = 0;
-  temp.y = 0;
-  
-  // 1. First translate this point into window coords. The returned point is always in
-  //    bottom-left coordinates.
-  temp = [mView convertPoint:temp toView:nil];  
-  
+  NSPoint origin = NSMakePoint(0, 0);
+
+  // 1. First translate view origin point into window coords.
+  // The returned point is in bottom-left coordinates.
+  origin = [mView convertPoint:origin toView:nil];
+
   // 2. We turn the window-coord rect's origin into screen (still bottom-left) coords.
-  temp = [[mView window] convertBaseToScreen:temp];
-  
+  origin = [[mView window] convertBaseToScreen:origin];
+
   // 3. Since we're dealing in bottom-left coords, we need to make it top-left coords
   //    before we pass it back to Gecko.
-  FlipCocoaScreenCoordinate(temp);
-  
-  return nsIntPoint(NSToIntRound(temp.x), NSToIntRound(temp.y));
+  FlipCocoaScreenCoordinate(origin);
+
+  // convert to device pixels
+  return CocoaPointsToDevPixels(origin);
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0));
 }
 
 NS_IMETHODIMP nsChildView::CaptureRollupEvents(nsIRollupListener * aListener, 
                                                bool aDoCapture, 
                                                bool aConsumeRollupEvent)
 {
@@ -1800,17 +1811,18 @@ nsChildView::UpdateThemeGeometries(const
   for (uint32_t i = 0; i < aThemeGeometries.Length(); ++i) {
     const ThemeGeometry& g = aThemeGeometries[i];
     if ((g.mWidgetType == NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR ||
          g.mWidgetType == NS_THEME_TOOLBAR) &&
         g.mRect.Contains(topPixelStrip)) {
       unifiedToolbarHeight = g.mRect.YMost();
     }
   }
-  [(ToolbarWindow*)win setUnifiedToolbarHeight:unifiedToolbarHeight];
+  [(ToolbarWindow*)win
+    setUnifiedToolbarHeight:DevPixelsToCocoaPoints(unifiedToolbarHeight)];
 }
 
 NS_IMETHODIMP
 nsChildView::BeginSecureKeyboardInput()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   nsresult rv = nsBaseWidget::BeginSecureKeyboardInput();
@@ -2347,16 +2359,21 @@ NSEvent* gLastDragMouseDownEvent = nil;
   }
 }
 
 - (void) _surfaceNeedsUpdate:(NSNotification*)notification
 {
    [self update];
 }
 
+- (BOOL)wantsBestResolutionOpenGLSurface
+{
+  return nsCocoaUtils::HiDPIEnabled() ? YES : NO;
+}
+
 // The display system has told us that a portion of our view is dirty. Tell
 // gecko to paint it
 - (void)drawRect:(NSRect)aRect
 {
   CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
   [self drawRect:aRect inContext:cgContext];
 
   // If we're a transparent window and our contents have changed, we need
@@ -2396,26 +2413,25 @@ NSEvent* gLastDragMouseDownEvent = nil;
            aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height, aContext,
            geckoBounds.x, geckoBounds.y, geckoBounds.width, geckoBounds.height);
 
   CGAffineTransform xform = CGContextGetCTM(aContext);
   fprintf (stderr, "  xform in: [%f %f %f %f %f %f]\n", xform.a, xform.b, xform.c, xform.d, xform.tx, xform.ty);
 #endif
   nsIntRegion region;
 
-  nsIntRect boundingRect =
-    nsIntRect(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height);
+  nsIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect);
   const NSRect *rects;
   NSInteger count, i;
   [[NSView focusView] getRectsBeingDrawn:&rects count:&count];
   if (count < MAX_RECTS_IN_REGION) {
     for (i = 0; i < count; ++i) {
       // Add the rect to the region.
-      const NSRect& r = [self convertRect:rects[i] fromView:[NSView focusView]];
-      region.Or(region, nsIntRect(r.origin.x, r.origin.y, r.size.width, r.size.height));
+      NSRect r = [self convertRect:rects[i] fromView:[NSView focusView]];
+      region.Or(region, mGeckoChild->CocoaPointsToDevPixels(r));
     }
     region.And(region, boundingRect);
   } else {
     region = boundingRect;
   }
 
   LayerManager *layerManager = mGeckoChild->GetLayerManager(nullptr);
   if (layerManager->GetBackendType() == mozilla::layers::LAYERS_OPENGL) {
@@ -2447,16 +2463,24 @@ NSEvent* gLastDragMouseDownEvent = nil;
   // Create Cairo objects.
   NSSize bufferSize = [self bounds].size;
   nsRefPtr<gfxQuartzSurface> targetSurface =
     new gfxQuartzSurface(aContext, gfxSize(bufferSize.width, bufferSize.height));
   targetSurface->SetAllowUseAsSource(false);
 
   nsRefPtr<gfxContext> targetContext = new gfxContext(targetSurface);
 
+  // The CGContext that drawRect supplies us with comes with a transform that
+  // scales one user space unit to one Cocoa point, which can consist of
+  // multiple dev pixels. But Gecko expects its supplied context to be scaled
+  // to device pixels, so we need to reverse the scaling.
+  gfxContextMatrixAutoSaveRestore save(targetContext);
+  double scale = 1.0 / mGeckoChild->GetDefaultScale();
+  targetContext->Scale(scale, scale);
+
   // Set up the clip region.
   nsIntRegionRectIterator iter(region);
   targetContext->NewPath();
   for (;;) {
     const nsIntRect* r = iter.Next();
     if (!r)
       break;
     targetContext->Rectangle(gfxRect(r->x, r->y, r->width, r->height));
@@ -2480,38 +2504,36 @@ NSEvent* gLastDragMouseDownEvent = nil;
       mDidForceRefreshOpenGL = YES;
     }
   }
 
   if (!painted && [self isOpaque]) {
     // Gecko refused to draw, but we've claimed to be opaque, so we have to
     // draw something--fill with white.
     CGContextSetRGBFillColor(aContext, 1, 1, 1, 1);
-    CGContextFillRect(aContext, CGRectMake(aRect.origin.x, aRect.origin.y,
-                                           aRect.size.width, aRect.size.height));
+    CGContextFillRect(aContext, NSRectToCGRect(aRect));
   }
 
   // note that the cairo surface *MUST* be destroyed at this point,
   // or bad things will happen (since we can't keep the cgContext around
   // beyond this drawRect message handler)
 
 #ifdef DEBUG_UPDATE
   fprintf (stderr, "---- update done ----\n");
 
 #if 0
   CGContextSetRGBStrokeColor (aContext,
                             ((((unsigned long)self) & 0xff)) / 255.0,
                             ((((unsigned long)self) & 0xff00) >> 8) / 255.0,
                             ((((unsigned long)self) & 0xff0000) >> 16) / 255.0,
                             0.5);
-#endif 
+#endif
   CGContextSetRGBStrokeColor(aContext, 1, 0, 0, 0.8);
   CGContextSetLineWidth(aContext, 4.0);
-  CGContextStrokeRect(aContext,
-                      CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height));
+  CGContextStrokeRect(aContext, NSRectToCGRect(aRect));
 #endif
 }
 
 - (void)releaseWidgets:(NSArray*)aWidgetArray
 {
   if (!aWidgetArray) {
     return;
   }
@@ -3239,18 +3261,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
   if (!mGeckoChild)
     return;
 
   NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
   NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil];
 
   uint32_t msg = aEnter ? NS_MOUSE_ENTER : NS_MOUSE_EXIT;
   nsMouseEvent event(true, msg, mGeckoChild, nsMouseEvent::eReal);
-  event.refPoint.x = nscoord((int32_t)localEventLocation.x);
-  event.refPoint.y = nscoord((int32_t)localEventLocation.y);
+  event.refPoint = mGeckoChild->CocoaPointsToDevPixels(localEventLocation);
 
   // Create event for use by plugins.
   // This is going to our child view so we don't need to look up the destination
   // event type.
 #ifndef NP_NO_CARBON  
   EventRecord carbonEvent;
 #endif
   NPCocoaEvent cocoaEvent;
@@ -3601,22 +3622,23 @@ static int32_t RoundUp(double aDouble)
   wheelEvent.lineOrPageDeltaX = RoundUp(-[theEvent deltaX]);
   wheelEvent.lineOrPageDeltaY = RoundUp(-[theEvent deltaY]);
 
   if (wheelEvent.deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
     // Some scrolling devices supports pixel scrolling, e.g. a Macbook
     // touchpad or a Mighty Mouse. On those devices, [theEvent deviceDeltaX/Y]
     // contains the amount of pixels to scroll. Since Lion this has changed 
     // to [theEvent scrollingDeltaX/Y].
+    double scale = mGeckoChild->BackingScaleFactor();
     if ([theEvent respondsToSelector:@selector(scrollingDeltaX)]) {
-      wheelEvent.deltaX = -[theEvent scrollingDeltaX];
-      wheelEvent.deltaY = -[theEvent scrollingDeltaY];
+      wheelEvent.deltaX = -[theEvent scrollingDeltaX] * scale;
+      wheelEvent.deltaY = -[theEvent scrollingDeltaY] * scale;
     } else {
-      wheelEvent.deltaX = -[theEvent deviceDeltaX];
-      wheelEvent.deltaY = -[theEvent deviceDeltaY];
+      wheelEvent.deltaX = -[theEvent deviceDeltaX] * scale;
+      wheelEvent.deltaY = -[theEvent deviceDeltaY] * scale;
     }
   } else {
     wheelEvent.deltaX = -[theEvent deltaX];
     wheelEvent.deltaY = -[theEvent deltaY];
   }
 
   // TODO: We should not set deltaZ for now because we're not sure if we should
   //       revert the sign.
@@ -3783,18 +3805,18 @@ static int32_t RoundUp(double aDouble)
   if (!outGeckoEvent)
     return;
 
   nsCocoaUtils::InitInputEvent(*outGeckoEvent, aMouseEvent);
 
   // convert point to view coordinate system
   NSPoint locationInWindow = nsCocoaUtils::EventLocationForWindow(aMouseEvent, [self window]);
   NSPoint localPoint = [self convertPoint:locationInWindow fromView:nil];
-  outGeckoEvent->refPoint.x = static_cast<nscoord>(localPoint.x);
-  outGeckoEvent->refPoint.y = static_cast<nscoord>(localPoint.y);
+
+  outGeckoEvent->refPoint = mGeckoChild->CocoaPointsToDevPixels(localPoint);
 
   nsMouseEvent_base* mouseEvent =
     static_cast<nsMouseEvent_base*>(outGeckoEvent);
   mouseEvent->buttons = 0;
   NSUInteger mouseButtons =
     nsCocoaFeatures::OnSnowLeopardOrLater() ? [NSEvent pressedMouseButtons] : 0;
 
   if (mouseButtons & 0x01) {
@@ -4297,19 +4319,20 @@ static int32_t RoundUp(double aDouble)
   }
 
   // set up gecko event
   nsDragEvent geckoEvent(true, aMessage, mGeckoChild);
   nsCocoaUtils::InitInputEvent(geckoEvent, [NSApp currentEvent]);
 
   // Use our own coordinates in the gecko event.
   // Convert event from gecko global coords to gecko view coords.
-  NSPoint localPoint = [self convertPoint:[aSender draggingLocation] fromView:nil];
-  geckoEvent.refPoint.x = static_cast<nscoord>(localPoint.x);
-  geckoEvent.refPoint.y = static_cast<nscoord>(localPoint.y);
+  NSPoint draggingLoc = [aSender draggingLocation];
+  NSPoint localPoint = [self convertPoint:draggingLoc fromView:nil];
+
+  geckoEvent.refPoint = mGeckoChild->CocoaPointsToDevPixels(localPoint);
 
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
   mGeckoChild->DispatchWindowEvent(geckoEvent);
   if (!mGeckoChild)
     return NSDragOperationNone;
 
   if (dragSession) {
     switch (aMessage) {
--- a/widget/cocoa/nsCocoaUtils.h
+++ b/widget/cocoa/nsCocoaUtils.h
@@ -9,16 +9,22 @@
 #import <Cocoa/Cocoa.h>
 
 #include "nsRect.h"
 #include "nsObjCExceptions.h"
 #include "imgIContainer.h"
 #include "nsEvent.h"
 #include "npapi.h"
 
+// Declare the backingScaleFactor method that we want to call
+// on NSView/Window/Screen objects, if they recognize it.
+@interface NSObject (BackingScaleFactorCategory)
+- (CGFloat)backingScaleFactor;
+@end
+
 class nsIWidget;
 
 // Used to retain a Cocoa object for the remainder of a method's execution.
 class nsAutoRetainCocoaObject {
 public:
 nsAutoRetainCocoaObject(id anObject)
 {
   mObject = NS_OBJC_TRY_EXPR_ABORT([anObject retain]);
@@ -68,34 +74,104 @@ private:
 // and below (or using the 10.5 SDK and below).
 - (void)setHelpMenu:(NSMenu *)helpMenu;
 
 @end
 
 class nsCocoaUtils
 {
   public:
-  // Returns the height of the primary screen (the one with the menu bar, which
-  // is documented to be the first in the |screens| array).
-  static float MenuBarScreenHeight();
+
+  // Get the backing scale factor from an object that supports this selector
+  // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported
+  static CGFloat
+  GetBackingScaleFactor(id aObject)
+  {
+    if (HiDPIEnabled() &&
+        [aObject respondsToSelector:@selector(backingScaleFactor)]) {
+      return [aObject backingScaleFactor];
+    }
+    return 1.0;
+  }
+
+  // Conversions between Cocoa points and device pixels, given the backing
+  // scale factor from a view/window/screen.
+  static int32_t
+  CocoaPointsToDevPixels(CGFloat aPts, CGFloat aBackingScale)
+  {
+    return NSToIntRound(aPts * aBackingScale);
+  }
+
+  static nsIntPoint
+  CocoaPointsToDevPixels(const NSPoint& aPt, CGFloat aBackingScale)
+  {
+    return nsIntPoint(NSToIntRound(aPt.x * aBackingScale),
+                      NSToIntRound(aPt.y * aBackingScale));
+  }
+
+  static nsIntRect
+  CocoaPointsToDevPixels(const NSRect& aRect, CGFloat aBackingScale)
+  {
+    return nsIntRect(NSToIntRound(aRect.origin.x * aBackingScale),
+                     NSToIntRound(aRect.origin.y * aBackingScale),
+                     NSToIntRound(aRect.size.width * aBackingScale),
+                     NSToIntRound(aRect.size.height * aBackingScale));
+  }
+
+  static CGFloat
+  DevPixelsToCocoaPoints(int32_t aPixels, CGFloat aBackingScale)
+  {
+    return (CGFloat)aPixels / aBackingScale;
+  }
+
+  static NSPoint
+  DevPixelsToCocoaPoints(const nsIntPoint& aPt, CGFloat aBackingScale)
+  {
+    return NSMakePoint((CGFloat)aPt.x / aBackingScale,
+                       (CGFloat)aPt.y / aBackingScale);
+  }
+
+  static NSRect
+  DevPixelsToCocoaPoints(const nsIntRect& aRect, CGFloat aBackingScale)
+  {
+    return NSMakeRect((CGFloat)aRect.x / aBackingScale,
+                      (CGFloat)aRect.y / aBackingScale,
+                      (CGFloat)aRect.width / aBackingScale,
+                      (CGFloat)aRect.height / aBackingScale);
+  }
 
   // Returns the given y coordinate, which must be in screen coordinates,
   // flipped from Gecko to Cocoa or Cocoa to Gecko.
   static float FlippedScreenY(float y);
-  
+
+  // The following functions come in "DevPix" variants that work with
+  // backing-store (device pixel) coordinates, as well as the original
+  // versions that expect coordinates in Cocoa points/CSS pixels.
+  // The difference becomes important in HiDPI display modes, where Cocoa
+  // points and backing-store pixels are no longer 1:1.
+
   // Gecko rects (nsRect) contain an origin (x,y) in a coordinate
   // system with (0,0) in the top-left of the primary screen. Cocoa rects
   // (NSRect) contain an origin (x,y) in a coordinate system with (0,0)
   // in the bottom-left of the primary screen. Both nsRect and NSRect
   // contain width/height info, with no difference in their use.
+  // This function does no scaling, so the Gecko coordinates are
+  // expected to be CSS pixels, which we treat as equal to Cocoa points.
   static NSRect GeckoRectToCocoaRect(const nsIntRect &geckoRect);
-  
+
+  // Converts aGeckoRect in dev pixels to points in Cocoa coordinates
+  static NSRect GeckoRectToCocoaRectDevPix(const nsIntRect &aGeckoRect,
+                                           CGFloat aBackingScale);
+
   // See explanation for geckoRectToCocoaRect, guess what this does...
   static nsIntRect CocoaRectToGeckoRect(const NSRect &cocoaRect);
-  
+
+  static nsIntRect CocoaRectToGeckoRectDevPix(const NSRect &aCocoaRect,
+                                              CGFloat aBackingScale);
+
   // Gives the location for the event in screen coordinates. Do not call this
   // unless the window the event was originally targeted at is still alive!
   // anEvent may be nil -- in that case the current mouse location is returned.
   static NSPoint ScreenLocationForEvent(NSEvent* anEvent);
   
   // Determines if an event happened over a window, whether or not the event
   // is for the window. Does not take window z-order into account.
   static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow);
@@ -153,19 +229,29 @@ class nsCocoaUtils
 
   /**
    * Makes NSString instance for aString.
    */
   static NSString* ToNSString(const nsAString& aString);
 
   /**
    * Returns NSRect for aGeckoRect.
+   * Just copies values between the two types; it does no coordinate-system
+   * conversion, so both rects must have the same coordinate origin/direction.
    */
   static void GeckoRectToNSRect(const nsIntRect& aGeckoRect,
-                                       NSRect& aOutCocoaRect);
+                                NSRect& aOutCocoaRect);
+
+  /**
+   * Returns Gecko rect for aCocoaRect.
+   * Just copies values between the two types; it does no coordinate-system
+   * conversion, so both rects must have the same coordinate origin/direction.
+   */
+  static void NSRectToGeckoRect(const NSRect& aCocoaRect,
+                                nsIntRect& aOutGeckoRect);
 
   /**
    * Makes NSEvent instance for aEventTytpe and aEvent.
    */
   static NSEvent* MakeNewCocoaEventWithType(NSEventType aEventType,
                                             NSEvent *aEvent);
 
   /**
@@ -185,11 +271,17 @@ class nsCocoaUtils
                              NSEvent* aNativeEvent);
   static void InitInputEvent(nsInputEvent &aInputEvent,
                              NSUInteger aModifiers);
 
   /**
    * GetCurrentModifiers() returns Cocoa modifier flags for current state.
    */
   static NSUInteger GetCurrentModifiers();
+
+  /**
+   * Whether to support HiDPI rendering. For testing purposes, to be removed
+   * once we're comfortable with the HiDPI behavior.
+   */
+  static bool HiDPIEnabled();
 };
 
 #endif // nsCocoaUtils_h_
--- a/widget/cocoa/nsCocoaUtils.mm
+++ b/widget/cocoa/nsCocoaUtils.mm
@@ -12,64 +12,85 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIAppShellService.h"
 #include "nsIXULWindow.h"
 #include "nsIBaseWindow.h"
 #include "nsIServiceManager.h"
 #include "nsMenuUtilsX.h"
 #include "nsToolkit.h"
 #include "nsGUIEvent.h"
+#include "mozilla/Preferences.h"
 
+using namespace mozilla;
 using namespace mozilla::widget;
 
-float nsCocoaUtils::MenuBarScreenHeight()
+static float
+MenuBarScreenHeight()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   NSArray* allScreens = [NSScreen screens];
-  if ([allScreens count])
+  if ([allScreens count]) {
     return [[allScreens objectAtIndex:0] frame].size.height;
-  else
-    return 0.0; // If there are no screens, there's not much we can say.
+  }
+
+  return 0.0;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0.0);
 }
 
-float nsCocoaUtils::FlippedScreenY(float y)
+float
+nsCocoaUtils::FlippedScreenY(float y)
 {
   return MenuBarScreenHeight() - y;
 }
 
 NSRect nsCocoaUtils::GeckoRectToCocoaRect(const nsIntRect &geckoRect)
 {
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
-
   // We only need to change the Y coordinate by starting with the primary screen
-  // height, subtracting the gecko Y coordinate, and subtracting the height.
+  // height and subtracting the gecko Y coordinate of the bottom of the rect.
   return NSMakeRect(geckoRect.x,
-                    MenuBarScreenHeight() - (geckoRect.y + geckoRect.height),
+                    MenuBarScreenHeight() - geckoRect.YMost(),
                     geckoRect.width,
                     geckoRect.height);
+}
 
-  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakeRect(0.0, 0.0, 0.0, 0.0));
+NSRect nsCocoaUtils::GeckoRectToCocoaRectDevPix(const nsIntRect &aGeckoRect,
+                                                CGFloat aBackingScale)
+{
+  return NSMakeRect(aGeckoRect.x / aBackingScale,
+                    MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale,
+                    aGeckoRect.width / aBackingScale,
+                    aGeckoRect.height / aBackingScale);
 }
 
 nsIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect &cocoaRect)
 {
   // We only need to change the Y coordinate by starting with the primary screen
   // height and subtracting both the cocoa y origin and the height of the
   // cocoa rect.
   nsIntRect rect;
   rect.x = NSToIntRound(cocoaRect.origin.x);
   rect.y = NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height));
   rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x;
   rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y;
   return rect;
 }
 
+nsIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(const NSRect &aCocoaRect,
+                                                   CGFloat aBackingScale)
+{
+  nsIntRect rect;
+  rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale);
+  rect.y = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) * aBackingScale);
+  rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) * aBackingScale) - rect.x;
+  rect.height = NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) - rect.y;
+  return rect;
+}
+
 NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   // Don't trust mouse locations of mouse move events, see bug 443178.
   if (!anEvent || [anEvent type] == NSMouseMoved)
     return [NSEvent mouseLocation];
 
@@ -347,16 +368,27 @@ nsCocoaUtils::GeckoRectToNSRect(const ns
 {
   aOutCocoaRect.origin.x = aGeckoRect.x;
   aOutCocoaRect.origin.y = aGeckoRect.y;
   aOutCocoaRect.size.width = aGeckoRect.width;
   aOutCocoaRect.size.height = aGeckoRect.height;
 }
 
 // static
+void
+nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect,
+                                nsIntRect& aOutGeckoRect)
+{
+  aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x);
+  aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y);
+  aOutGeckoRect.width = NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) - aOutGeckoRect.x;
+  aOutGeckoRect.height = NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) - aOutGeckoRect.y;
+}
+
+// static
 NSEvent*
 nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent *aEvent)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   NSEvent* newEvent =
     [NSEvent     keyEventWithType:aEventType
                          location:[aEvent locationInWindow] 
@@ -483,8 +515,123 @@ nsCocoaUtils::GetCurrentModifiers()
     cocoaModifiers |= NSShiftKeyMask;
   }
   if (carbonModifiers & cmdKey) {
     cocoaModifiers |= NSCommandKeyMask;
   }
 
   return cocoaModifiers;
 }
+
+// While HiDPI support is not 100% complete and tested, we'll have a pref
+// to allow it to be turned off in case of problems (or for testing purposes).
+
+// gfx.hidpi.enabled is an integer with the meaning:
+//    <= 0 : HiDPI support is disabled
+//       1 : HiDPI enabled provided all screens have the same backing resolution
+//     > 1 : HiDPI enabled even if there are a mixture of screen modes
+
+// All the following code is to be removed once HiDPI work is more complete.
+
+static bool sHiDPIEnabled = false;
+static bool sHiDPIPrefInitialized = false;
+
+@interface ScreenParamChangeWatcher : NSObject
+- (id)init;
+@end
+
+@implementation ScreenParamChangeWatcher
+- (id)init
+{
+  [super init];
+  [[NSNotificationCenter defaultCenter]
+    addObserver:self
+       selector:@selector(applicationDidChangeScreenParameters:)
+           name:NSApplicationDidChangeScreenParametersNotification
+         object:NSApp];
+  return self;
+}
+
+- (void)applicationDidChangeScreenParameters:(NSNotification *)notification
+{
+  // reset flags so that the next call to HiDPIEnabled() will re-evaluate
+  sHiDPIEnabled = false;
+  sHiDPIPrefInitialized = false;
+}
+@end
+
+class HiDPIPrefObserver MOZ_FINAL : public nsIObserver {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+};
+
+NS_IMPL_ISUPPORTS1(HiDPIPrefObserver, nsIObserver)
+
+NS_IMETHODIMP
+HiDPIPrefObserver::Observe(nsISupports* aSubject, const char* aTopic,
+                           const PRUnichar* aData)
+{
+  // reset flags so that the next call to HiDPIEnabled() will re-evaluate
+  sHiDPIEnabled = false;
+  sHiDPIPrefInitialized = false;
+  return NS_OK;
+}
+
+// static
+bool
+nsCocoaUtils::HiDPIEnabled()
+{
+  static ScreenParamChangeWatcher* sChangeWatcher = nil;
+
+  if (!sHiDPIPrefInitialized) {
+    sHiDPIPrefInitialized = true;
+
+    if (!sChangeWatcher) {
+      // Create an object to watch for changes in screen configuration.
+      // Note that we'll leak this object at shutdown;
+      // this is all a temporary hack until we have multi-screen HiDPI working
+      // properly and can dispense with this code.
+      sChangeWatcher = [[ScreenParamChangeWatcher alloc] init];
+
+      // And create an observer for changes to the preference.
+      nsCOMPtr<nsIObserver> obs(new HiDPIPrefObserver());
+      Preferences::AddStrongObserver(obs, "gfx.hidpi.enabled");
+    }
+
+    int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
+    if (prefSetting <= 0) {
+      return false;
+    }
+
+    // prefSetting is at least 1, need to check attached screens...
+
+    int scaleFactors = 0; // used as a bitset to track the screen types found
+    NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+    while (NSScreen *screen = [screenEnum nextObject]) {
+      NSDictionary *desc = [screen deviceDescription];
+      if ([desc objectForKey:NSDeviceIsScreen] == nil) {
+        continue;
+      }
+      CGFloat scale =
+        [screen respondsToSelector:@selector(backingScaleFactor)] ?
+          [screen backingScaleFactor] : 1.0;
+      // Currently, we only care about differentiating "1.0" and "2.0",
+      // so we set one of the two low bits to record which.
+      if (scale > 1.0) {
+        scaleFactors |= 2;
+      } else {
+        scaleFactors |= 1;
+      }
+    }
+
+    // Now scaleFactors will be:
+    //   0 if no screens (supporting backingScaleFactor) found
+    //   1 if only lo-DPI screens
+    //   2 if only hi-DPI screens
+    //   3 if both lo- and hi-DPI screens
+    // We'll enable HiDPI support if there's only a single screen type,
+    // OR if the pref setting is explicitly greater than 1.
+    sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
+  }
+
+  return sHiDPIEnabled;
+}
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -237,16 +237,19 @@ public:
     NS_IMETHOD              Resize(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, bool aRepaint);
     NS_IMETHOD              GetClientBounds(nsIntRect &aRect);
     NS_IMETHOD              GetScreenBounds(nsIntRect &aRect);
     void                    ReportMoveEvent();
     void                    ReportSizeEvent();
     NS_IMETHOD              SetCursor(nsCursor aCursor);
     NS_IMETHOD              SetCursor(imgIContainer* aCursor, uint32_t aHotspotX, uint32_t aHotspotY);
 
+    CGFloat                 BackingScaleFactor();
+    virtual double          GetDefaultScale();
+
     NS_IMETHOD              SetTitle(const nsAString& aTitle);
 
     NS_IMETHOD Invalidate(const nsIntRect &aRect);
     virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations);
     virtual LayerManager* GetLayerManager(PLayersChild* aShadowManager = nullptr,
                                           LayersBackend aBackendHint = mozilla::layers::LAYERS_NONE,
                                           LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                                           bool* aAllowRetaining = nullptr);
@@ -323,16 +326,18 @@ protected:
   BaseWindow*          mWindow;         // our cocoa window [STRONG]
   WindowDelegate*      mDelegate;       // our delegate for processing window msgs [STRONG]
   nsRefPtr<nsMenuBarX> mMenuBar;
   NSWindow*            mSheetWindowParent; // if this is a sheet, this is the NSWindow it's attached to
   nsChildView*         mPopupContentView; // if this is a popup, this is its content widget
   int32_t              mShadowStyle;
   NSUInteger           mWindowFilter;
 
+  CGFloat              mBackingScaleFactor;
+
   WindowAnimationType  mAnimationType;
 
   bool                 mWindowMadeHere; // true if we created the window, false for embedding
   bool                 mSheetNeedsShow; // if this is a sheet, are we waiting to be shown?
                                         // this is used for sibling sheet contention only
   bool                 mFullScreen;
   bool                 mInFullScreenTransition; // true from the request to enter/exit fullscreen
                                                 // (MakeFullScreen() call) to EnteredFullScreen()
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -99,16 +99,17 @@ static void RollUpPopups()
 nsCocoaWindow::nsCocoaWindow()
 : mParent(nullptr)
 , mWindow(nil)
 , mDelegate(nil)
 , mSheetWindowParent(nil)
 , mPopupContentView(nil)
 , mShadowStyle(NS_STYLE_WINDOW_SHADOW_DEFAULT)
 , mWindowFilter(0)
+, mBackingScaleFactor(0.0)
 , mAnimationType(nsIWidget::eGenericWindowAnimation)
 , mWindowMadeHere(false)
 , mSheetNeedsShow(false)
 , mFullScreen(false)
 , mInFullScreenTransition(false)
 , mModal(false)
 , mUsesNativeFullScreen(false)
 , mIsAnimationSuppressed(false)
@@ -170,23 +171,26 @@ nsCocoaWindow::~nsCocoaWindow()
   if (mModal) {
     --gXULModalLevel;
     NS_ASSERTION(gXULModalLevel >= 0, "Wierdness setting modality!");
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
-static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *screen)
+// fits the rect to the screen that contains the largest area of it
+// NB: this operates with aRect in global CSS pixels
+static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen)
 {
-  if (!screen)
+  if (!aScreen) {
     return;
-  
-  nsIntRect screenBounds(nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]));
-  
+  }
+
+  nsIntRect screenBounds(nsCocoaUtils::CocoaRectToGeckoRect([aScreen visibleFrame]));
+
   if (aRect.width > screenBounds.width) {
     aRect.width = screenBounds.width;
   }
   if (aRect.height > screenBounds.height) {
     aRect.height = screenBounds.height;
   }
   
   if (aRect.x - screenBounds.x + aRect.width > screenBounds.width) {
@@ -212,49 +216,44 @@ static bool UseNativePopupWindows()
 {
 #ifdef MOZ_USE_NATIVE_POPUP_WINDOWS
   return true;
 #else
   return false;
 #endif /* MOZ_USE_NATIVE_POPUP_WINDOWS */
 }
 
+// aRect here is specified in CSS pixels
 nsresult nsCocoaWindow::Create(nsIWidget *aParent,
                                nsNativeWidget aNativeParent,
                                const nsIntRect &aRect,
                                nsDeviceContext *aContext,
                                nsWidgetInitData *aInitData)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   // Because the hidden window is created outside of an event loop,
   // we have to provide an autorelease pool (see bug 559075).
   nsAutoreleasePool localPool;
 
   // Find the screen that overlaps aRect the most,
   // if none are found default to the mainScreen.
   NSScreen *targetScreen = [NSScreen mainScreen];
-  NSArray *screens = [NSScreen screens];
-  if (screens) {
-    int largestIntersectArea = 0;
-    int i = [screens count];
-    while (i--) {
-      NSScreen *screen = [screens objectAtIndex:i];
-      nsIntRect screenBounds(nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]));
-
-      nsIntRegion intersect;
-      intersect.And(screenBounds, aRect);
-      int area = intersect.GetBounds().width * intersect.GetBounds().height;
-
-      if (area > largestIntersectArea) {
-        largestIntersectArea = area;
-        targetScreen = screen;
-      }
+  NSEnumerator *screenEnum = [[NSScreen screens] objectEnumerator];
+  int largestIntersectArea = 0;
+  while (NSScreen *screen = [screenEnum nextObject]) {
+    nsIntRect screenRect(nsCocoaUtils::CocoaRectToGeckoRect([screen visibleFrame]));
+    screenRect = screenRect.Intersect(aRect);
+    int area = screenRect.width * screenRect.height;
+    if (area > largestIntersectArea) {
+      largestIntersectArea = area;
+      targetScreen = screen;
     }
   }
+
   nsIntRect newBounds = aRect;
   FitRectToVisibleAreaForScreen(newBounds, targetScreen);
 
   // Set defaults which can be overriden from aInitData in BaseCreate
   mWindowType = eWindowType_toplevel;
   mBorderStyle = eBorderStyle_default;
 
   // Ensure that the toolkit is created.
@@ -1073,41 +1072,57 @@ void nsCocoaWindow::SetSizeConstraints(c
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   // Popups can be smaller than (60, 60)
   NSRect rect =
     (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
   rect = [mWindow frameRectForContentRect:rect];
 
+  CGFloat scaleFactor = BackingScaleFactor();
+
   SizeConstraints c = aConstraints;
-  c.mMinSize.width = NS_MAX(int32_t(rect.size.width), c.mMinSize.width);
-  c.mMinSize.height = NS_MAX(int32_t(rect.size.height), c.mMinSize.height);
-
-  NSSize minSize = { static_cast<CGFloat>(c.mMinSize.width),
-                     static_cast<CGFloat>(c.mMinSize.height) };
+  c.mMinSize.width =
+    NS_MAX(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.width, scaleFactor),
+           c.mMinSize.width);
+  c.mMinSize.height =
+    NS_MAX(nsCocoaUtils::CocoaPointsToDevPixels(rect.size.height, scaleFactor),
+           c.mMinSize.height);
+
+  NSSize minSize = {
+    nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.width, scaleFactor),
+    nsCocoaUtils::DevPixelsToCocoaPoints(c.mMinSize.height, scaleFactor)
+  };
   [mWindow setMinSize:minSize];
 
-  NSSize maxSize = { c.mMaxSize.width == NS_MAXSIZE ? FLT_MAX : c.mMaxSize.width,
-                     c.mMaxSize.height == NS_MAXSIZE ? FLT_MAX : c.mMaxSize.height };
+  NSSize maxSize = {
+    c.mMaxSize.width == NS_MAXSIZE ?
+      FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.width, scaleFactor),
+    c.mMaxSize.height == NS_MAXSIZE ?
+      FLT_MAX : nsCocoaUtils::DevPixelsToCocoaPoints(c.mMaxSize.height, scaleFactor)
+  };
   [mWindow setMaxSize:maxSize];
 
   nsBaseWidget::SetSizeConstraints(c);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 NS_IMETHODIMP nsCocoaWindow::Move(int32_t aX, int32_t aY)
 {
   if (!mWindow || (mBounds.x == aX && mBounds.y == aY))
     return NS_OK;
 
   // The point we have is in Gecko coordinates (origin top-left). Convert
   // it to Cocoa ones (origin bottom-left).
-  NSPoint coord = {static_cast<CGFloat>(aX), nsCocoaUtils::FlippedScreenY(aY)};
+  CGFloat scaleFactor = BackingScaleFactor();
+  NSPoint coord = {
+    nsCocoaUtils::DevPixelsToCocoaPoints(aX, scaleFactor),
+    nsCocoaUtils::FlippedScreenY(nsCocoaUtils::DevPixelsToCocoaPoints(aY, scaleFactor))
+  };
   [mWindow setFrameTopLeftPoint:coord];
 
   return NS_OK;
 }
 
 // Position the window behind the given window
 NS_METHOD nsCocoaWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
                                      nsIWidget *aWidget, bool aActivate)
@@ -1263,33 +1278,43 @@ NS_METHOD nsCocoaWindow::MakeFullScreen(
     EnteredFullScreen(aFullScreen);
   }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
-
 NS_IMETHODIMP nsCocoaWindow::Resize(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, bool aRepaint)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
   ConstrainSize(&aWidth, &aHeight);
 
-  nsIntRect newBounds = nsIntRect(aX, aY, aWidth, aHeight);
+  nsIntRect newBounds(aX, aY, aWidth, aHeight);
+
+  // convert requested size into Cocoa points
+  CGFloat scaleFactor = BackingScaleFactor();
+  NSRect cocoaBounds = nsCocoaUtils::DevPixelsToCocoaPoints(newBounds, scaleFactor);
+
+  // constrain to the visible area of the window's current screen
+  nsCocoaUtils::NSRectToGeckoRect(cocoaBounds, newBounds);
   FitRectToVisibleAreaForScreen(newBounds, [mWindow screen]);
 
+  // then convert back to device pixels
+  nsCocoaUtils::GeckoRectToNSRect(newBounds, cocoaBounds);
+  newBounds = nsCocoaUtils::CocoaPointsToDevPixels(cocoaBounds, scaleFactor);
+
   BOOL isMoving = (mBounds.x != newBounds.x || mBounds.y != newBounds.y);
   BOOL isResizing = (mBounds.width != newBounds.width || mBounds.height != newBounds.height);
 
   if (!mWindow || (!isMoving && !isResizing))
     return NS_OK;
 
-  NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds);
+  NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRectDevPix(newBounds, scaleFactor);
 
   // We ignore aRepaint -- we have to call display:YES, otherwise the
   // title bar doesn't immediately get repainted and is displayed in
   // the wrong place, leading to a visual jump.
   [mWindow setFrame:newFrame display:YES];
 
   return NS_OK;
 
@@ -1304,56 +1329,81 @@ NS_IMETHODIMP nsCocoaWindow::Resize(int3
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP nsCocoaWindow::GetClientBounds(nsIntRect &aRect)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
+  CGFloat scaleFactor = BackingScaleFactor();
   if (!mWindow) {
-    aRect = nsCocoaUtils::CocoaRectToGeckoRect(NSZeroRect);
+    aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(NSZeroRect, scaleFactor);
     return NS_OK;
   }
 
+  NSRect r;
   if ([mWindow isKindOfClass:[ToolbarWindow class]] &&
       [(ToolbarWindow*)mWindow drawsContentsIntoWindowFrame]) {
-    aRect = nsCocoaUtils::CocoaRectToGeckoRect([mWindow frame]);
+    r = [mWindow frame];
   } else {
-    NSRect contentRect = [mWindow contentRectForFrameRect:[mWindow frame]];
-    aRect = nsCocoaUtils::CocoaRectToGeckoRect(contentRect);
+    r = [mWindow contentRectForFrameRect:[mWindow frame]];
   }
 
+  aRect = nsCocoaUtils::CocoaRectToGeckoRectDevPix(r, scaleFactor);
+
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 void
 nsCocoaWindow::UpdateBounds()
 {
   NSRect frame = NSZeroRect;
-  if (mWindow)
+  if (mWindow) {
     frame = [mWindow frame];
-  mBounds = nsCocoaUtils::CocoaRectToGeckoRect(frame);
+  }
+  mBounds = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
 }
 
 NS_IMETHODIMP nsCocoaWindow::GetScreenBounds(nsIntRect &aRect)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
-  NS_ASSERTION(mWindow && mBounds == nsCocoaUtils::CocoaRectToGeckoRect([mWindow frame]),
-               "mBounds out of sync!");
+#ifdef DEBUG
+  nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix([mWindow frame], BackingScaleFactor());
+  NS_ASSERTION(mWindow && mBounds == r, "mBounds out of sync!");
+#endif
 
   aRect = mBounds;
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
+double
+nsCocoaWindow::GetDefaultScale()
+{
+  return BackingScaleFactor();
+}
+
+CGFloat
+nsCocoaWindow::BackingScaleFactor()
+{
+  if (mBackingScaleFactor > 0.0) {
+    return mBackingScaleFactor;
+  }
+  if (!mWindow) {
+    return 1.0;
+  }
+  mBackingScaleFactor = nsCocoaUtils::GetBackingScaleFactor(mWindow);
+  return mBackingScaleFactor;
+}
+
 NS_IMETHODIMP nsCocoaWindow::SetCursor(nsCursor aCursor)
 {
   if (mPopupContentView)
     return mPopupContentView->SetCursor(aCursor);
 
   return NS_OK;
 }
 
@@ -1379,18 +1429,19 @@ NS_IMETHODIMP nsCocoaWindow::SetTitle(co
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP nsCocoaWindow::Invalidate(const nsIntRect & aRect)
 {
-  if (mPopupContentView)
+  if (mPopupContentView) {
     return mPopupContentView->Invalidate(aRect);
+  }
 
   return NS_OK;
 }
 
 // Pass notification of some drag event to Gecko
 //
 // The drag manager has let us know that something related to a drag has
 // occurred in this window. It could be any number of things, ranging from 
@@ -1589,19 +1640,21 @@ NS_IMETHODIMP nsCocoaWindow::SetFocus(bo
   return NS_OK;
 }
 
 nsIntPoint nsCocoaWindow::WidgetToScreenOffset()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   NSRect rect = NSZeroRect;
-  if (mWindow)
+  nsIntRect r;
+  if (mWindow) {
     rect = [mWindow contentRectForFrameRect:[mWindow frame]];
-  nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRect(rect);
+  }
+  r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(rect, BackingScaleFactor());
 
   return r.TopLeft();
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntPoint(0,0));
 }
 
 nsIntPoint nsCocoaWindow::GetClientOffset()
 {
@@ -1617,20 +1670,22 @@ nsIntPoint nsCocoaWindow::GetClientOffse
 
 nsIntSize nsCocoaWindow::ClientToWindowSize(const nsIntSize& aClientSize)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   if (!mWindow)
     return nsIntSize(0, 0);
 
-  NSRect rect(NSMakeRect(0.0, 0.0, aClientSize.width, aClientSize.height));
+  CGFloat backingScale = BackingScaleFactor();
+  nsIntRect r(0, 0, aClientSize.width, aClientSize.height);
+  NSRect rect = nsCocoaUtils::DevPixelsToCocoaPoints(r, backingScale);
 
   NSRect inflatedRect = [mWindow frameRectForContentRect:rect];
-  return nsCocoaUtils::CocoaRectToGeckoRect(inflatedRect).Size();
+  return nsCocoaUtils::CocoaRectToGeckoRectDevPix(inflatedRect, backingScale).Size();
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntSize(0,0));
 }
 
 nsMenuBarX* nsCocoaWindow::GetMenuBar()
 {
   return mMenuBar;
 }
@@ -2218,17 +2273,23 @@ GetDPI(NSWindow* aWindow)
     return 96.0f;
   }
 
   // Currently we don't do our own scaling to take account
   // of userSpaceScaleFactor, so every "pixel" we draw is actually
   // userSpaceScaleFactor screen pixels. So divide the screen height
   // by userSpaceScaleFactor to get the number of "device pixels"
   // available.
-  return (heightPx / scaleFactor) / (heightMM / MM_PER_INCH_FLOAT);
+  float dpi = (heightPx / scaleFactor) / (heightMM / MM_PER_INCH_FLOAT);
+
+  // Account for HiDPI mode where Cocoa's "points" do not correspond to real
+  // device pixels
+  CGFloat backingScale = nsCocoaUtils::GetBackingScaleFactor(aWindow);
+
+  return dpi * backingScale;
 }
 
 @interface BaseWindow(Private)
 - (void)removeTrackingArea;
 - (void)cursorUpdated:(NSEvent*)aEvent;
 @end
 
 @implementation BaseWindow
--- a/widget/cocoa/nsDragService.mm
+++ b/widget/cocoa/nsDragService.mm
@@ -25,18 +25,17 @@
 #include "nsICharsetConverterManager.h"
 #include "nsIIOService.h"
 #include "nsNetUtil.h"
 #include "nsIDocument.h"
 #include "nsIContent.h"
 #include "nsIView.h"
 #include "gfxASurface.h"
 #include "gfxContext.h"
-
-#import <Cocoa/Cocoa.h>
+#include "nsCocoaUtils.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* sCocoaLog;
 #endif
 
 extern void EnsureLogInitialized();
 
 extern NSPasteboard* globalDragPboard;
@@ -129,29 +128,36 @@ static nsresult SetUpDragClipboard(nsISu
 
 NSImage*
 nsDragService::ConstructDragImage(nsIDOMNode* aDOMNode,
                                   nsIntRect* aDragRect,
                                   nsIScriptableRegion* aRegion)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
-  NSPoint screenPoint = [[gLastDragView window] convertBaseToScreen:[gLastDragMouseDownEvent locationInWindow]];
+  NSPoint screenPoint =
+    [[gLastDragView window] convertBaseToScreen:
+      [gLastDragMouseDownEvent locationInWindow]];
   // Y coordinates are bottom to top, so reverse this
-  if ([[NSScreen screens] count] > 0)
-    screenPoint.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - screenPoint.y;
+  screenPoint.y = nsCocoaUtils::FlippedScreenY(screenPoint.y);
+
+  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
+  nsIntPoint pt =
+    nsCocoaUtils::CocoaPointsToDevPixels(NSMakePoint(screenPoint.x,
+                                                     screenPoint.y),
+                                         scaleFactor);
 
   nsRefPtr<gfxASurface> surface;
   nsPresContext* pc;
-  nsresult rv = DrawDrag(aDOMNode, aRegion,
-                         NSToIntRound(screenPoint.x), NSToIntRound(screenPoint.y),
+  nsresult rv = DrawDrag(aDOMNode, aRegion, pt.x, pt.y,
                          aDragRect, getter_AddRefs(surface), &pc);
   if (!aDragRect->width || !aDragRect->height) {
     // just use some suitable defaults
-    aDragRect->SetRect(NSToIntRound(screenPoint.x), NSToIntRound(screenPoint.y), 20, 20);
+    int32_t size = nsCocoaUtils::CocoaPointsToDevPixels(20, scaleFactor);
+    aDragRect->SetRect(pt.x, pt.y, size, size);
   }
 
   if (NS_FAILED(rv) || !surface)
     return nil;
 
   uint32_t width = aDragRect->width;
   uint32_t height = aDragRect->height;
 
@@ -166,26 +172,27 @@ nsDragService::ConstructDragImage(nsIDOM
 
   context->SetOperator(gfxContext::OPERATOR_SOURCE);
   context->SetSource(surface);
   context->Paint();
 
   uint32_t* imageData = (uint32_t*)imgSurface->Data();
   int32_t stride = imgSurface->Stride();
 
-  NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
-                                                                       pixelsWide:width
-                                                                       pixelsHigh:height
-                                                                    bitsPerSample:8
-                                                                  samplesPerPixel:4
-                                                                         hasAlpha:YES
-                                                                         isPlanar:NO
-                                                                   colorSpaceName:NSDeviceRGBColorSpace
-                                                                      bytesPerRow:width * 4
-                                                                     bitsPerPixel:32];
+  NSBitmapImageRep* imageRep =
+    [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
+                                            pixelsWide:width
+                                            pixelsHigh:height
+                                         bitsPerSample:8
+                                       samplesPerPixel:4
+                                              hasAlpha:YES
+                                              isPlanar:NO
+                                        colorSpaceName:NSDeviceRGBColorSpace
+                                           bytesPerRow:width * 4
+                                          bitsPerPixel:32];
 
   uint8_t* dest = [imageRep bitmapData];
   for (uint32_t i = 0; i < height; ++i) {
     uint8_t* src = (uint8_t *)imageData + i * stride;
     for (uint32_t j = 0; j < width; ++j) {
       // Reduce transparency overall by multipying by a factor. Remember, Alpha
       // is premultipled here. Also, Quartz likes RGBA, so do that translation as well.
 #ifdef IS_BIG_ENDIAN
@@ -199,17 +206,19 @@ nsDragService::ConstructDragImage(nsIDOM
       dest[2] = uint8_t(src[0] * DRAG_TRANSLUCENCY);
       dest[3] = uint8_t(src[3] * DRAG_TRANSLUCENCY);
 #endif
       src += 4;
       dest += 4;
     }
   }
 
-  NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize((float)width, (float)height)];
+  NSImage* image =
+    [[NSImage alloc] initWithSize:NSMakeSize(width / scaleFactor,
+                                             height / scaleFactor)];
   [image addRepresentation:imageRep];
   [imageRep release];
 
   return [image autorelease];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
@@ -249,22 +258,20 @@ nsDragService::InvokeDragSession(nsIDOMN
     [path lineToPoint:NSMakePoint(0, size.height)];
     [path lineToPoint:NSMakePoint(size.width, size.height)];
     [path lineToPoint:NSMakePoint(size.width, 0)];
     [path lineToPoint:NSMakePoint(0, 0)];
     [path stroke];
     [image unlockFocus];
   }
 
-  NSPoint point;
-  point.x = dragRect.x;
-  if ([[NSScreen screens] count] > 0)
-    point.y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - dragRect.YMost();
-  else
-    point.y = dragRect.y;
+  nsIntPoint pt(dragRect.x, dragRect.YMost());
+  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(gLastDragView);
+  NSPoint point = nsCocoaUtils::DevPixelsToCocoaPoints(pt, scaleFactor);
+  point.y = nsCocoaUtils::FlippedScreenY(point.y);
 
   point = [[gLastDragView window] convertScreenToBase: point];
   NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
  
   // Save the transferables away in case a promised file callback is invoked.
   gDraggedTransferables = aTransferableArray;
 
   nsBaseDragService::StartDragSession();
--- a/widget/cocoa/nsScreenCocoa.h
+++ b/widget/cocoa/nsScreenCocoa.h
@@ -19,12 +19,14 @@ public:
     NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
     NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
     NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
     NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
 
     NSScreen *CocoaScreen() { return mScreen; }
 
 private:
+    CGFloat BackingScaleFactor();
+
     NSScreen *mScreen;
 };
 
 #endif // nsScreenCocoa_h_
--- a/widget/cocoa/nsScreenCocoa.mm
+++ b/widget/cocoa/nsScreenCocoa.mm
@@ -23,30 +23,34 @@ nsScreenCocoa::~nsScreenCocoa ()
   [mScreen release];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 NS_IMETHODIMP
 nsScreenCocoa::GetRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
 {
-  nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRect([mScreen frame]);
+  NSRect frame = [mScreen frame];
+
+  nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
 
   *outX = r.x;
   *outY = r.y;
   *outWidth = r.width;
   *outHeight = r.height;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsScreenCocoa::GetAvailRect(int32_t *outX, int32_t *outY, int32_t *outWidth, int32_t *outHeight)
 {
-  nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRect([mScreen visibleFrame]);
+  NSRect frame = [mScreen visibleFrame];
+
+  nsIntRect r = nsCocoaUtils::CocoaRectToGeckoRectDevPix(frame, BackingScaleFactor());
 
   *outX = r.x;
   *outY = r.y;
   *outWidth = r.width;
   *outHeight = r.height;
 
   return NS_OK;
 }
@@ -73,8 +77,14 @@ nsScreenCocoa::GetColorDepth(int32_t *aC
   NSWindowDepth depth = [mScreen depth];
   int bpp = NSBitsPerPixelFromDepth (depth);
 
   *aColorDepth = bpp;
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
+
+CGFloat
+nsScreenCocoa::BackingScaleFactor()
+{
+  return nsCocoaUtils::GetBackingScaleFactor(mScreen);
+}
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -424,16 +424,20 @@ class nsIWidget : public nsISupports {
      * automatically clear the window to the background color. The 
      * calling code must handle paint messages and clear the background 
      * itself. 
      *
      * In practice at least one of aParent and aNativeParent will be null. If
      * both are null the widget isn't parented (e.g. context menus or
      * independent top level windows).
      *
+     * The dimensions given in aRect are specified in the parent's
+     * coordinate system, or for parentless widgets such as top-level
+     * windows, in global CSS pixels.
+     *
      * @param     aParent       parent nsIWidget
      * @param     aNativeParent native parent widget
      * @param     aRect         the widget dimension
      * @param     aContext
      * @param     aInitData     data that is used for widget initialization
      *
      */
     NS_IMETHOD Create(nsIWidget        *aParent,
--- a/widget/xpwidgets/nsBaseWidget.cpp
+++ b/widget/xpwidgets/nsBaseWidget.cpp
@@ -90,16 +90,17 @@ nsBaseWidget::nsBaseWidget()
 , mCursor(eCursor_standard)
 , mWindowType(eWindowType_child)
 , mBorderStyle(eBorderStyle_none)
 , mOnDestroyCalled(false)
 , mUseAcceleratedRendering(false)
 , mForceLayersAcceleration(false)
 , mTemporarilyUseBasicLayerManager(false)
 , mUseAttachedEvents(false)
+, mContextInitialized(false)
 , mBounds(0,0,0,0)
 , mOriginalBounds(nullptr)
 , mClipRectCount(0)
 , mZIndex(0)
 , mSizeMode(nsSizeMode_Normal)
 , mPopupLevel(ePopupLevelTop)
 , mPopupType(ePopupTypeAny)
 {
@@ -923,16 +924,20 @@ BasicLayerManager* nsBaseWidget::CreateB
 
 //-------------------------------------------------------------------------
 //
 // Return the used device context
 //
 //-------------------------------------------------------------------------
 nsDeviceContext* nsBaseWidget::GetDeviceContext() 
 {
+  if (!mContextInitialized) {
+    mContext->Init(this);
+    mContextInitialized = true;
+  }
   return mContext; 
 }
 
 //-------------------------------------------------------------------------
 //
 // Get the thebes surface
 //
 //-------------------------------------------------------------------------
--- a/widget/xpwidgets/nsBaseWidget.h
+++ b/widget/xpwidgets/nsBaseWidget.h
@@ -339,16 +339,17 @@ protected:
   nsCursor          mCursor;
   nsWindowType      mWindowType;
   nsBorderStyle     mBorderStyle;
   bool              mOnDestroyCalled;
   bool              mUseAcceleratedRendering;
   bool              mForceLayersAcceleration;
   bool              mTemporarilyUseBasicLayerManager;
   bool              mUseAttachedEvents;
+  bool              mContextInitialized;
   nsIntRect         mBounds;
   nsIntRect*        mOriginalBounds;
   // When this pointer is null, the widget is not clipped
   nsAutoArrayPtr<nsIntRect> mClipRects;
   uint32_t          mClipRectCount;
   int32_t           mZIndex;
   nsSizeMode        mSizeMode;
   nsPopupLevel      mPopupLevel;
--- a/xpfe/appshell/src/nsXULWindow.cpp
+++ b/xpfe/appshell/src/nsXULWindow.cpp
@@ -1224,18 +1224,20 @@ bool nsXULWindow::LoadMiscPersistentAttr
 /* Stagger windows of the same type so they don't appear on top of each other.
    This code does have a scary double loop -- it'll keep passing through
    the entire list of open windows until it finds a non-collision. Doesn't
    seem to be a problem, but it deserves watching.
 */
 void nsXULWindow::StaggerPosition(int32_t &aRequestedX, int32_t &aRequestedY,
                                   int32_t aSpecWidth, int32_t aSpecHeight)
 {
-  const int32_t kOffset = 22;
-  const int32_t kSlop = 4;
+  const int32_t appPerDev = AppUnitsPerDevPixel();
+  const int32_t kOffset = CSSToDevPixels(22, appPerDev);
+  const int32_t kSlop   = CSSToDevPixels(4, appPerDev);
+
   nsresult rv;
   bool     keepTrying;
   int      bouncedX = 0, // bounced off vertical edge of screen
            bouncedY = 0; // bounced off horizontal edge
 
   // look for any other windows of this type
   nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID));
   if (!wm)