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 108736 1d3de8da2508e3acdaa3ddf1e0769472d3704f0b
parent 108735 550641381dfaf0e8144e4aec9e458dfd8c125aeb
child 108737 879cce846c1e5f6d81f5889cfedbd39c2f143372
child 108741 86acf4b667c822c8f14be779577b921a37a1c987
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersroc, smichaud
bugs674373
milestone18.0a1
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)