b=418497, optimize mac native theme ; r=josh,sr=rc
authorvladimir@pobox.com
Tue, 04 Mar 2008 12:51:54 -0800
changeset 12566 a888eabc4c7f7effc1e05b1dc77ee85420f6f22b
parent 12565 2a37d418d34d875edc398a36b8c94dbd194ffd42
child 12567 d00c0a8839f9ee3a42fa2f7c8588eb4cdad594ba
push idunknown
push userunknown
push dateunknown
reviewersjosh, rc
bugs418497
milestone1.9b5pre
b=418497, optimize mac native theme ; r=josh,sr=rc
widget/src/cocoa/nsNativeThemeCocoa.h
widget/src/cocoa/nsNativeThemeCocoa.mm
--- a/widget/src/cocoa/nsNativeThemeCocoa.h
+++ b/widget/src/cocoa/nsNativeThemeCocoa.h
@@ -43,16 +43,18 @@
 
 #include "nsITheme.h"
 #include "nsCOMPtr.h"
 #include "nsIAtom.h"
 #include "nsILookAndFeel.h"
 #include "nsIDeviceContext.h"
 #include "nsNativeTheme.h"
 
+#include "gfxASurface.h"
+
 class nsNativeThemeCocoa : private nsNativeTheme,
                            public nsITheme
 {
 public:
   nsNativeThemeCocoa();
   virtual ~nsNativeThemeCocoa();
 
   NS_DECL_ISUPPORTS
@@ -126,14 +128,23 @@ protected:
                     PRBool inDisabled, PRInt32 inState);
   // Scrollbars
   void DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame);
   void GetScrollbarPressStates (nsIFrame *aFrame, PRInt32 aButtonStates[]);
   void GetScrollbarDrawInfo (HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame, 
                              const HIRect& aRect, PRBool aShouldGetButtonStates);
   nsIFrame* GetParentScrollbarFrame(nsIFrame *aFrame);
 
+  void DrawCellWithScaling(NSCell *cell,
+                           CGContextRef cgContext,
+                           const HIRect& destRect,
+                           NSControlSize controlSize,
+                           float naturalWidth, float naturalHeight,
+                           float minWidth, float minHeight,
+                           const float marginSet[][3][4],
+                           PRBool doSaveCTM);
+
 private:
   NSButtonCell* mPushButtonCell;
   NSButtonCell* mRadioButtonCell;
 };
 
 #endif // nsNativeThemeCocoa_h_
--- a/widget/src/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/src/cocoa/nsNativeThemeCocoa.mm
@@ -141,42 +141,16 @@ nsNativeThemeCocoa::~nsNativeThemeCocoa(
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [mPushButtonCell release];
   [mRadioButtonCell release];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
-
-static PRBool
-IsTransformOnlyTranslateOrFlip(CGAffineTransform aTransform)
-{
-  return (aTransform.a == 1.0f && aTransform.b == 0.0f && 
-          aTransform.c == 0.0f && (aTransform.d == 1.0f || aTransform.d == -1.0f));
-}
-
-
-// We separate this into its own function because after an @try, all local
-// variables within that function get marked as volatile, and our C++ type
-// system doesn't like volatile things.
-static PRBool
-LockFocusOnImage(NSImage* aImage)
-{
-  @try {
-    [aImage lockFocus];
-  } @catch (NSException* e) {
-    NS_WARNING(nsPrintfCString(256, "Exception raised while drawing to offscreen buffer: \"%s - %s\"", 
-                               [[e name] UTF8String], [[e reason] UTF8String]).get());
-    return PR_FALSE;
-  }
-  return PR_TRUE;
-}
-
-
 void
 nsNativeThemeCocoa::DrawCheckbox(CGContextRef cgContext, ThemeButtonKind inKind,
                                  const HIRect& inBoxRect, PRBool inChecked,
                                  PRBool inDisabled, PRInt32 inState)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   HIThemeButtonDrawInfo bdi;
@@ -228,109 +202,128 @@ static const float radioButtonMargins[2]
   },
   { // Leopard
     {0, 4, 0, -4}, // mini
     {0, 3, 0, -3}, // small
     {0, 3, 0, -3}  // regular
   }
 };
 
+/*
+ * Draw the given NSCell into the given cgContext.
+ *
+ * destRect - the size and position of the resulting control rectangle
+ * controlSize - the NSControlSize which will be given to the NSCell before
+ *  asking it to render
+ * naturalWidth, naturalHeight - The natural dimensions of this control.
+ *  If the control rect size is not equal to either of these, a scale
+ *  will be applied to the context so that rendering the control at the
+ *  natural size will result in it filling the destRect space.
+ *  If a control has no natural dimensions in either/both axes, pass 0.0f.
+ * minWidth, minHeight - The minimum dimensions of this control.
+ *  If the control rect size is less than the minimum for a given axis,
+ *  a scale will be applied to the context so that the minimum is used
+ *  for drawing.  If a control has no minimum dimensions in either/both
+ *  axes, pass 0.0f.
+ * marginSet - an array of margins; a multidimensional array of [2][3][4],
+ *  with the first two array elements being margins for Tiger or Leopard,
+ *  the next three being control size (mini, small, regular), and the 4
+ *  being the 4 margin values.
+ * doSaveCTM - whether this routine should bother to save the CTM before
+ *  manipuating; if the caller has already done this, pass PR_FALSE.
+ */
+void
+nsNativeThemeCocoa::DrawCellWithScaling(NSCell *cell,
+                                        CGContextRef cgContext,
+                                        const HIRect& destRect,
+                                        NSControlSize controlSize,
+                                        float naturalWidth, float naturalHeight,
+                                        float minWidth, float minHeight,
+                                        const float marginSet[][3][4],
+                                        PRBool doSaveCTM)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+  NSRect drawRect = NSRectFromCGRect(destRect);
+
+  CGAffineTransform savedCTM;
+
+  if (doSaveCTM)
+    savedCTM = CGContextGetCTM(cgContext);
+
+  // Set up the graphics context we've been asked to draw to.
+  NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+  [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+
+  float xscale = 1.0f, yscale = 1.0f;
+
+  if (naturalWidth != 0.0f) {
+    xscale = drawRect.size.width / naturalWidth;
+    drawRect.size.width = naturalWidth;
+  }
+  else if (minWidth != 0.0f &&
+           drawRect.size.width < minWidth)
+  {
+    xscale = drawRect.size.width / minWidth;
+    drawRect.size.width = minWidth;
+  }
+
+  if (naturalHeight != 0.0f) {
+    yscale = drawRect.size.height / naturalHeight;
+    drawRect.size.height = naturalHeight;
+  }
+  else if (minHeight != 0.0f &&
+           drawRect.size.height < minHeight)
+  {
+    yscale = drawRect.size.height / minHeight;
+    drawRect.size.height = minHeight;
+  }
+
+  CGContextScaleCTM (cgContext, xscale, yscale);
+
+  // Inflate the rect Gecko gave us by the margin for the control.
+  InflateControlRect(&drawRect, controlSize, marginSet);
+  [cell drawWithFrame:drawRect inView:[NSView focusView]];
+
+  [NSGraphicsContext setCurrentContext:savedContext];
+
+  if (doSaveCTM)
+    CGContextSetCTM(cgContext, savedCTM);
+
+#if DRAW_IN_FRAME_DEBUG
+  CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.8);
+  CGContextFillRect(cgContext, destRect);
+#endif
+
+  NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+                                        
 void
 nsNativeThemeCocoa::DrawRadioButton(CGContextRef cgContext, const HIRect& inBoxRect, PRBool inSelected,
                                     PRBool inDisabled, PRInt32 inState)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   NSRect drawRect = NSMakeRect(inBoxRect.origin.x, inBoxRect.origin.y, inBoxRect.size.width, inBoxRect.size.height);
 
   [mRadioButtonCell setEnabled:!inDisabled];
   [mRadioButtonCell setShowsFirstResponder:(inState & NS_EVENT_STATE_FOCUS)];
   [mRadioButtonCell setState:(inSelected ? NSOnState : NSOffState)];
   [mRadioButtonCell setHighlighted:((inState & NS_EVENT_STATE_ACTIVE) && (inState & NS_EVENT_STATE_HOVER))];
 
-  // Set up the graphics context we've been asked to draw to.
-  NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
-  [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
-  [NSGraphicsContext saveGraphicsState];
-
   // Always use a regular size control because for some reason NSCell doesn't respect other
   // size choices here. Maybe because of a rendering context/ctm setup it doesn't like?
   NSControlSize controlSize = NSRegularControlSize;
-  float naturalHeight = NATURAL_REGULAR_RADIO_BUTTON_HEIGHT;
-  float naturalWidth = NATURAL_REGULAR_RADIO_BUTTON_WIDTH;
   [mRadioButtonCell setControlSize:controlSize];
 
-  // Render, by scaling if the target height is not the natural height of the control we're drawing.
-  if (drawRect.size.height == naturalHeight &&
-      drawRect.size.width ==  naturalWidth) {
-    // Just inflate the rect Gecko gave us by the margin for the control.
-    NSRect cellRenderRect = drawRect;
-    InflateControlRect(&cellRenderRect, controlSize, radioButtonMargins);
-    [mRadioButtonCell drawWithFrame:cellRenderRect inView:[NSView focusView]];
-  }
-  else {
-    // We need to calculate three things here - the size of our offscreen buffer, the rect we'll
-    // use to draw into it, and the rect that we'll copy the buffer to.
-
-    // This is the rect we'll render the control into our buffer with. We need to inset our control to leave room for a
-    // focus ring to be rendered.
-    NSRect bufferRenderRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, naturalWidth, naturalHeight);
-
-    // At this point the size of our render rect reflects the size of the control we actually
-    // want to draw into the buffer. Grab it for our initial buffer size and then expand the
-    // buffer size to allow for a focus ring to render.
-    NSSize initialBufferSize = bufferRenderRect.size;
-    initialBufferSize.width += (MAX_FOCUS_RING_WIDTH * 2);
-    initialBufferSize.height += (MAX_FOCUS_RING_WIDTH * 2);
-
-    // Now we need to inflate our rendering rect to account for the quirks of NSCell rendering,
-    // which is what this rect will actually be used for. The rect we pass to NSCell for
-    // rendering is not necessarily the same size as the control that gets drawn.
-    InflateControlRect(&bufferRenderRect, controlSize, radioButtonMargins);
-
-    // This is the rect in our destination that we'll copy our buffer to.
-    NSRect finalCopyRect = NSMakeRect(drawRect.origin.x - MAX_FOCUS_RING_WIDTH,
-                                      drawRect.origin.y - MAX_FOCUS_RING_WIDTH,
-                                      drawRect.size.width + (MAX_FOCUS_RING_WIDTH * 2),
-                                      drawRect.size.height + (MAX_FOCUS_RING_WIDTH * 2));
+  DrawCellWithScaling(mRadioButtonCell, cgContext, inBoxRect, controlSize,
+                      NATURAL_REGULAR_RADIO_BUTTON_WIDTH, NATURAL_REGULAR_RADIO_BUTTON_HEIGHT,
+                      0.0f, 0.0f,
+                      radioButtonMargins, PR_TRUE);
 
-    // This flips the image in place and is necessary to work around a bug in the way
-    // NSButtonCell draws buttons.
-    CGContextScaleCTM(cgContext, 1.0f, -1.0f);
-    CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * drawRect.origin.y + drawRect.size.height));
-
-    // Now actually do all the drawing/scaling with the rects we have set up.
-    NSImage *buffer = [[NSImage alloc] initWithSize:initialBufferSize];
-    if (!LockFocusOnImage(buffer)) {
-      [buffer release];
-      [NSGraphicsContext restoreGraphicsState];
-      [NSGraphicsContext setCurrentContext:savedContext];
-      return;
-    }
-
-    [mRadioButtonCell drawWithFrame:bufferRenderRect inView:[NSView focusView]];
-
-    [buffer setScalesWhenResized:YES];
-    [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
-    [buffer setSize:finalCopyRect.size];
-
-    [buffer unlockFocus];
-
-    [buffer drawInRect:finalCopyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
-
-    [buffer release];
-  }
-
-  [NSGraphicsContext restoreGraphicsState];
-  [NSGraphicsContext setCurrentContext:savedContext];
-
-#if DRAW_IN_FRAME_DEBUG
-  CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.8);
-  CGContextFillRect(cgContext, inBoxRect);
-#endif
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 
 // These are the sizes that Gecko needs to request to draw if it wants
 // to get a standard-sized Aqua rounded bevel button drawn. Note that
 // the rects that draw these are actually a little bigger.
@@ -351,45 +344,53 @@ static const float pushButtonMargins[2][
   },
   { // Leopard
     {0, 0, 0, 0}, // mini
     {4, 0, 4, 1}, // small
     {5, 0, 5, 2}  // regular
   }
 };
 
+// The height at which we start doing square buttons instead of rounded buttons
+// Rounded buttons look bad if drawn at a height greater than 26, so at that point
+// we switch over to doing square buttons which looks fine at any size.
+#define DO_SQUARE_BUTTON_HEIGHT 26
+
 void
 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect, PRBool inIsDefault,
                                    PRBool inDisabled, PRInt32 inState)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   NSRect drawRect = NSMakeRect(inBoxRect.origin.x, inBoxRect.origin.y, inBoxRect.size.width, inBoxRect.size.height);
 
   [mPushButtonCell setEnabled:!inDisabled];
   [mPushButtonCell setHighlighted:((inState & NS_EVENT_STATE_ACTIVE) && (inState & NS_EVENT_STATE_HOVER) || (inIsDefault && !inDisabled))];
   [mPushButtonCell setShowsFirstResponder:(inState & NS_EVENT_STATE_FOCUS)];
 
-  // Set up the graphics context we've been asked to draw to.
-  NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
-  [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
-  [NSGraphicsContext saveGraphicsState];
+  CGAffineTransform savedCTM = CGContextGetCTM(cgContext);
 
   // This flips the image in place and is necessary to work around a bug in the way
   // NSButtonCell draws buttons.
   CGContextScaleCTM(cgContext, 1.0f, -1.0f);
   CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * drawRect.origin.y + drawRect.size.height));
 
+  // Set up the graphics context we've been asked to draw to.
+
   // If the button is tall enough, draw the square button style so that buttons with
   // non-standard content look good. Otherwise draw normal rounded aqua buttons.
-  if (drawRect.size.height > 26) {
+  if (drawRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
+    NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+
     [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
     [mPushButtonCell drawWithFrame:drawRect inView:[NSView focusView]];
-  }
-  else {
+
+    [NSGraphicsContext setCurrentContext:savedContext];
+  } else {
     [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
 
     // Figure out what size cell control we're going to draw and grab its
     // natural height and min width.
     NSControlSize controlSize = NSRegularControlSize;
     float naturalHeight = NATURAL_REGULAR_ROUNDED_BUTTON_HEIGHT;
     float minWidth = NATURAL_REGULAR_ROUNDED_BUTTON_MIN_WIDTH;
     if (drawRect.size.height <= NATURAL_MINI_ROUNDED_BUTTON_HEIGHT &&
@@ -401,81 +402,23 @@ nsNativeThemeCocoa::DrawPushButton(CGCon
     else if (drawRect.size.height <= NATURAL_SMALL_ROUNDED_BUTTON_HEIGHT &&
              drawRect.size.width >= NATURAL_SMALL_ROUNDED_BUTTON_MIN_WIDTH) {
       controlSize = NSSmallControlSize;
       naturalHeight = NATURAL_SMALL_ROUNDED_BUTTON_HEIGHT;
       minWidth = NATURAL_SMALL_ROUNDED_BUTTON_MIN_WIDTH;
     }
     [mPushButtonCell setControlSize:controlSize];
 
-    // Render, by scaling if the target height is not the natural height of the control we're drawing.
-    if (drawRect.size.height == naturalHeight) {
-      // Just inflate the rect Gecko gave us by the margin for the control.
-      NSRect cellRenderRect = drawRect;
-      InflateControlRect(&cellRenderRect, controlSize, pushButtonMargins);
-
-      [mPushButtonCell drawWithFrame:cellRenderRect inView:[NSView focusView]];
-    }
-    else {
-      // We need to calculate three things here - the size of our offscreen buffer, the rect we'll
-      // use to draw into it, and the rect that we'll copy the buffer to.
-
-      // This is the rect we'll render the control into our buffer with. We need to inset our control to leave room for a
-      // focus ring to be rendered. Also, we start with the min width for the control or the desired width, whichever is
-      // larger.
-      NSRect bufferRenderRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, PR_MAX(minWidth, drawRect.size.width), naturalHeight);
-
-      // Adjust the size of our control to avoid distortion when scaling.
-      float scaleFactor = drawRect.size.height / naturalHeight;
-      if (scaleFactor != 0.0)
-        bufferRenderRect.size.width = bufferRenderRect.size.width / scaleFactor;
-
-      // At this point the size of our render rect reflects the size of the control we actually
-      // want to draw into the buffer. Grab it for our initial buffer size and then expand the
-      // buffer size to allow for a focus ring to render.
-      NSSize initialBufferSize = bufferRenderRect.size;
-      initialBufferSize.width += (MAX_FOCUS_RING_WIDTH * 2);
-      initialBufferSize.height += (MAX_FOCUS_RING_WIDTH * 2);
-
-      // Now we need to inflate our rendering rect to account for the quirks of NSCell rendering,
-      // which is what this rect will actually be used for. The rect we pass to NSCell for
-      // rendering is not necessarily the same size as the control that gets drawn.
-      InflateControlRect(&bufferRenderRect, controlSize, pushButtonMargins);
-
-      // This is the rect in our destination that we'll copy our buffer to.
-      NSRect finalCopyRect = NSMakeRect(drawRect.origin.x - MAX_FOCUS_RING_WIDTH,
-                                        drawRect.origin.y - MAX_FOCUS_RING_WIDTH,
-                                        drawRect.size.width + (MAX_FOCUS_RING_WIDTH * 2),
-                                        drawRect.size.height + (MAX_FOCUS_RING_WIDTH * 2));
-
-      // Now actually do all the drawing/scaling with the rects we have set up.
-      NSImage *buffer = [[NSImage alloc] initWithSize:initialBufferSize];
-      if (!LockFocusOnImage(buffer)) {
-        [buffer release];
-        [NSGraphicsContext restoreGraphicsState];
-        [NSGraphicsContext setCurrentContext:savedContext];
-        return;
-      }
-
-      [mPushButtonCell drawWithFrame:bufferRenderRect inView:[NSView focusView]];
-
-      [buffer setScalesWhenResized:YES];
-      [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
-      [buffer setSize:finalCopyRect.size];
-
-      [buffer unlockFocus];
-
-      [buffer drawInRect:finalCopyRect fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
-
-      [buffer release];
-    }
+    DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, controlSize,
+                        0.0f, naturalHeight,
+                        minWidth, 0.0f,
+                        pushButtonMargins, PR_FALSE);
   }
 
-  [NSGraphicsContext restoreGraphicsState];
-  [NSGraphicsContext setCurrentContext:savedContext];
+  CGContextSetCTM (cgContext, savedCTM);
 
 #if DRAW_IN_FRAME_DEBUG
   CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.8);
   CGContextFillRect(cgContext, inBoxRect);
 #endif
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
@@ -710,17 +653,17 @@ nsNativeThemeCocoa::DrawTab(CGContextRef
   tdi.adornment = kThemeAdornmentNone;
 
   HIThemeDrawTab(&inBoxRect, &tdi, cgContext, HITHEME_ORIENTATION, NULL);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 
-static UInt8
+static inline UInt8
 ConvertToPressState(PRInt32 aButtonState, UInt8 aPressState)
 {
   // If the button is pressed, return the press state passed in. Otherwise, return 0.
   return ((aButtonState & NS_EVENT_STATE_ACTIVE) && (aButtonState & NS_EVENT_STATE_HOVER)) ? aPressState : 0;
 }
 
 
 void 
@@ -784,25 +727,22 @@ nsNativeThemeCocoa::GetScrollbarDrawInfo
   aTdi.attributes = 0;
   aTdi.enableState = kThemeTrackActive;
   if (isHorizontal)
     aTdi.attributes |= kThemeTrackHorizontal;
 
   PRInt32 longSideLength = (PRInt32)(isHorizontal ? (aRect.size.width) : (aRect.size.height));
   aTdi.trackInfo.scrollbar.viewsize = (SInt32)longSideLength;
 
-  // Only display the thumb if we have room for it to display. Note that this doesn't 
-  // affect the actual tracking rects Gecko maintains -- this is a purely cosmetic
-  // change. See bmo bug 380185 for more info.
+  /* Only display features if we have enough room for them.
+   * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
+   */
   if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
     aTdi.attributes |= kThemeTrackShowThumb;
   }
-  // If we don't have enough room to display *any* features, we're done creating 
-  // this tdi, so return early. Again, this doesn't affect the tracking rects Gecko 
-  // maintains. See bmo bug 380185 for more info.
   else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
     aTdi.enableState = kThemeTrackNothingToScroll;
     return;
   }
 
   // Only go get these scrollbar button states if we need it. For example, there's no reaon to look up scrollbar button 
   // states when we're only creating a TrackDrawInfo to determine the size of the thumb.
   if (aShouldGetButtonStates) {
@@ -829,49 +769,71 @@ nsNativeThemeCocoa::GetScrollbarDrawInfo
 }
 
 
 void
 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
-  // If we're drawing offscreen, make the origin (0, 0), since that's where in
-  // the offscreen buffer we'll be drawing.
-  PRBool drawOnScreen = IsTransformOnlyTranslateOrFlip(CGContextGetCTM(aCGContext));
-  HIRect drawRect = drawOnScreen ? aBoxRect : CGRectMake(0, 0, aBoxRect.size.width, aBoxRect.size.height);
+  // HIThemeDrawTrack is buggy with rotations and scaling
+  CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
+  PRBool drawDirect;
+  HIRect drawRect = aBoxRect;
+
+  if (savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
+      savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f))
+  {
+    drawDirect = TRUE;
+  } else {
+    drawRect.origin.x = drawRect.origin.y = 0.0f;
+    drawDirect = FALSE;
+  }
+
   HIThemeTrackDrawInfo tdi;
   GetScrollbarDrawInfo(tdi, aFrame, drawRect, PR_TRUE); //True means we want the press states
 
-  if (drawOnScreen)
+  if (drawDirect) {
     ::HIThemeDrawTrack(&tdi, NULL, aCGContext, HITHEME_ORIENTATION);
-  else {
-    NSImage *buffer = [[NSImage alloc] initWithSize:NSMakeSize(aBoxRect.size.width, aBoxRect.size.height)];
-    if (!LockFocusOnImage(buffer)) {
-      [buffer release];
-      return;
-    }
-    ::HIThemeDrawTrack(&tdi, NULL, (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort],
-                       kHIThemeOrientationInverted);
-    [buffer unlockFocus];
-    
-    [NSGraphicsContext saveGraphicsState];
-    [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aCGContext flipped:YES]];
+  } else {
+    // Note that NSScroller can draw transformed just fine, but HITheme can't.
+    // However, we can't make NSScroller's parts light up easily (depressed buttons, etc.)
+    // This is very frustrating.
+
+    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+    CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
+                                                   (size_t) ceil(drawRect.size.width),
+                                                   (size_t) ceil(drawRect.size.height),
+                                                   8,
+                                                   (size_t) ceil(drawRect.size.width) * 4,
+                                                   colorSpace,
+                                                   kCGImageAlphaPremultipliedFirst);
+    CGColorSpaceRelease(colorSpace);
 
-    // We need to flip the buffer when we draw it.
-    CGContextTranslateCTM(aCGContext, 0, aBoxRect.size.height + (2 * aBoxRect.origin.y));
+    // HITheme always wants to draw into a flipped context, or things
+    // get confused.
+    CGContextTranslateCTM(bitmapctx, 0.0f, aBoxRect.size.height);
+    CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
+
+    HIThemeDrawTrack(&tdi, NULL, bitmapctx, HITHEME_ORIENTATION);
+
+    CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
+
+    CGAffineTransform ctm = CGContextGetCTM(aCGContext);
+
+    // We need to unflip, so that we can do a DrawImage without getting a flipped image.
+    CGContextTranslateCTM(aCGContext, 0.0f, aBoxRect.size.height);
     CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
-    [buffer drawInRect:*(NSRect*)&aBoxRect
-              fromRect:NSZeroRect
-             operation:NSCompositeSourceOver
-              fraction:1.0];
+
+    CGContextDrawImage(aCGContext, aBoxRect, bitmap);
 
-    [NSGraphicsContext restoreGraphicsState];
+    CGContextSetCTM(aCGContext, ctm);
 
-    [buffer release];
+    CGImageRelease(bitmap);
+    CGContextRelease(bitmapctx);
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 
 nsIFrame*
 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)