Bug 676241 part 6/9/11: Correct titlebar drawing. r=bgirard, r=smichaud, r=mattwoodrow
authorMarkus Stange <mstange@themasta.com>
Thu, 23 May 2013 16:49:17 +0200
changeset 169897 c895cfe0c09a8e791052d0dcdc25a6e0b90bc6d6
parent 169896 bc1f0124ee2f9ca7e791f2e4cc322ac6ea27c411
child 169898 99480bf38444fc6e08c3e6840d1de3b3cf73fbdc
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgirard, smichaud, mattwoodrow
bugs676241
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 676241 part 6/9/11: Correct titlebar drawing. r=bgirard, r=smichaud, r=mattwoodrow
widget/cocoa/nsChildView.h
widget/cocoa/nsChildView.mm
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -274,16 +274,20 @@ typedef NSInteger NSEventGestureAxis;
 #ifdef __LP64__
   // Support for fluid swipe tracking.
   BOOL* mCancelSwipeAnimation;
   PRUint32 mCurrentSwipeDir;
 #endif
 
   // Whether this uses off-main-thread compositing.
   BOOL mUsingOMTCompositor;
+
+  // The mask image that's used when painting into the titlebar using basic
+  // CGContext painting (i.e. non-accelerated).
+  CGImageRef mTopLeftCornerMask;
 }
 
 // class initialization
 + (void)initialize;
 
 + (void)registerViewForDraggedTypes:(NSView*)aView;
 
 // these are sent to the first responder when the window key status changes
@@ -311,16 +315,18 @@ typedef NSInteger NSEventGestureAxis;
 
 // Are we processing an NSLeftMouseDown event that will fail to click through?
 // If so, we shouldn't focus or unfocus a plugin.
 - (BOOL)isInFailingLeftClickThrough;
 
 - (void)setGLContext:(NSOpenGLContext *)aGLContext;
 - (void)preRender:(NSOpenGLContext *)aGLContext;
 
+- (BOOL)isCoveringTitlebar;
+
 // Simple gestures support
 //
 // XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
 // rotateWithEvent, and endGestureWithEvent methods are part of a
 // PRIVATE interface exported by nsResponder and reverse-engineering
 // was necessary to obtain the methods' prototypes. Thus, Apple may
 // change the interface in the future without notice.
 //
@@ -533,16 +539,18 @@ public:
 
   NS_IMETHOD        ReparentNativeWidget(nsIWidget* aNewParent);
 
   mozilla::widget::TextInputHandler* GetTextInputHandler()
   {
     return mTextInputHandler;
   }
 
+  void              NotifyDirtyRegion(const nsIntRegion& aDirtyRegion);
+
   // unit conversion convenience functions
   int32_t           CocoaPointsToDevPixels(CGFloat aPts) {
     return nsCocoaUtils::CocoaPointsToDevPixels(aPts, BackingScaleFactor());
   }
   nsIntPoint        CocoaPointsToDevPixels(const NSPoint& aPt) {
     return nsCocoaUtils::CocoaPointsToDevPixels(aPt, BackingScaleFactor());
   }
   nsIntRect         CocoaPointsToDevPixels(const NSRect& aRect) {
@@ -568,18 +576,30 @@ protected:
   virtual already_AddRefed<nsIWidget>
   AllocateChildPopupWidget()
   {
     static NS_DEFINE_IID(kCPopUpCID, NS_POPUP_CID);
     nsCOMPtr<nsIWidget> widget = do_CreateInstance(kCPopUpCID);
     return widget.forget();
   }
 
-  void MaybeDrawResizeIndicator(mozilla::layers::GLManager* aManager, nsIntRect aRect);
-  void MaybeDrawRoundedBottomCorners(mozilla::layers::GLManager* aManager, nsIntRect aRect);
+  // Overlay drawing functions for OpenGL drawing
+  void MaybeDrawResizeIndicator(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
+  void MaybeDrawRoundedCorners(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
+  void MaybeDrawTitlebar(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
+
+  // Redraw the contents of mTitlebarImageBuffer on the main thread, as
+  // determined by mDirtyTitlebarRegion.
+  void UpdateTitlebarImageBuffer();
+
+  // Upload the contents of mTitlebarImageBuffer to mTitlebarImage on the
+  // compositor thread, as determined by mUpdatedTitlebarRegion.
+  void UpdateTitlebarImage(mozilla::layers::GLManager* aManager, const nsIntRect& aRect);
+
+  nsIntRect RectContainingTitlebarControls();
 
   nsIWidget* GetWidgetForListenerEvents();
 
 protected:
 
   NSView<mozView>*      mView;      // my parallel cocoa view (ChildView or NativeScrollbarView), [STRONG]
   nsRefPtr<mozilla::widget::TextInputHandler> mTextInputHandler;
   InputContext          mInputContext;
@@ -599,22 +619,35 @@ protected:
   mozilla::Mutex mEffectsLock;
 
   // May be accessed from any thread, protected
   // by mEffectsLock.
   bool mShowsResizeIndicator;
   nsIntRect mResizeIndicatorRect;
   bool mHasRoundedBottomCorners;
   int mDevPixelCornerRadius;
+  bool mIsCoveringTitlebar;
+  nsIntRect mTitlebarRect;
+
+  // The area of mTitlebarImageBuffer that needs to be redrawn during the next
+  // transaction. Accessed from any thread, protected by mEffectsLock.
+  nsIntRegion mUpdatedTitlebarRegion;
+
+  nsRefPtr<gfxQuartzSurface> mTitlebarImageBuffer;
 
   // Compositor thread only
   bool                  mFailedResizerImage;
   bool                  mFailedCornerMaskImage;
   nsRefPtr<mozilla::gl::TextureImage> mResizerImage;
   nsRefPtr<mozilla::gl::TextureImage> mCornerMaskImage;
+  nsRefPtr<mozilla::gl::TextureImage> mTitlebarImage;
+
+  // The area of mTitlebarImageBuffer that has changed and needs to be
+  // uploaded to to mTitlebarImage. Main thread only.
+  nsIntRegion           mDirtyTitlebarRegion;
 
   // 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;
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -43,16 +43,17 @@
 #include "nsMenuUtilsX.h"
 #include "nsMenuBarX.h"
 #ifdef __LP64__
 #include "ComplexTextInputPanel.h"
 #endif
 
 #include "gfxContext.h"
 #include "gfxQuartzSurface.h"
+#include "gfxUtils.h"
 #include "nsRegion.h"
 #include "Layers.h"
 #include "LayerManagerOGL.h"
 #include "ClientLayerManager.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "GLTextureImage.h"
 #include "mozilla/layers/GLManager.h"
 #include "mozilla/layers/CompositorCocoaWidgetHelper.h"
@@ -141,18 +142,22 @@ uint32_t nsChildView::sLastInputEventCou
 - (void)drawRect:(NSRect)aRect inContext:(CGContextRef)aContext;
 
 - (BOOL)isUsingMainThreadOpenGL;
 - (BOOL)isUsingOpenGL;
 - (void)drawUsingOpenGL;
 - (void)drawUsingOpenGLCallback;
 
 - (BOOL)hasRoundedBottomCorners;
-- (CGFloat)bottomCornerRadius;
-- (void)maybeClearBottomCorners;
+- (CGFloat)cornerRadius;
+- (void)clearCorners;
+
+// Overlay drawing functions for traditional CGContext drawing
+- (void)drawTitlebarHighlight;
+- (void)maskTopCornersInContext:(CGContextRef)aContext;
 
 // Called using performSelector:withObject:afterDelay:0 to release
 // aWidgetArray (and its contents) the next time through the run loop.
 - (void)releaseWidgets:(NSArray*)aWidgetArray;
 
 #if USE_CLICK_HOLD_CONTEXTMENU
  // called on a timer two seconds after a mouse down to see if we should display
  // a context menu (click-hold)
@@ -241,16 +246,17 @@ void EnsureLogInitialized()
 
 nsChildView::nsChildView() : nsBaseWidget()
 , mView(nullptr)
 , mParentView(nullptr)
 , mParentWidget(nullptr)
 , mEffectsLock("WidgetEffects")
 , mShowsResizeIndicator(false)
 , mHasRoundedBottomCorners(false)
+, mIsCoveringTitlebar(false)
 , mFailedResizerImage(false)
 , mFailedCornerMaskImage(false)
 , mBackingScaleFactor(0.0)
 , mVisible(false)
 , mDrawing(false)
 , mPluginDrawing(false)
 , mIsDispatchPaint(false)
 , mPluginInstanceOwner(nullptr)
@@ -1811,31 +1817,62 @@ nsChildView::GetThebesSurface()
   if (!mTempThebesSurface) {
     mTempThebesSurface = new gfxQuartzSurface(gfxSize(1, 1), gfxASurface::ImageFormatARGB32);
   }
 
   return mTempThebesSurface;
 }
 
 void
+nsChildView::NotifyDirtyRegion(const nsIntRegion& aDirtyRegion)
+{
+  if ([(ChildView*)mView isCoveringTitlebar]) {
+    // We store the dirty region so that we know what to repaint in the titlebar.
+    mDirtyTitlebarRegion.Or(mDirtyTitlebarRegion, aDirtyRegion);
+    mDirtyTitlebarRegion.And(mDirtyTitlebarRegion, RectContainingTitlebarControls());
+  }
+}
+
+static const CGFloat kTitlebarHighlightHeight = 6;
+
+nsIntRect
+nsChildView::RectContainingTitlebarControls()
+{
+  // Start with a 6px high strip at the top of the window for the highlight line.
+  NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width,
+                           kTitlebarHighlightHeight);
+
+  // Add the rects of the titlebar controls.
+  for (id view in [(BaseWindow*)[mView window] titlebarControls]) {
+    rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]);
+  }
+  return CocoaPointsToDevPixels(rect);
+}
+
+void
 nsChildView::PrepareWindowEffects()
 {
   MutexAutoLock lock(mEffectsLock);
   mShowsResizeIndicator = ShowsResizeIndicator(&mResizeIndicatorRect);
-  mHasRoundedBottomCorners = [mView isKindOfClass:[ChildView class]] &&
-                             [(ChildView*)mView hasRoundedBottomCorners];
-  CGFloat cornerRadius = [(ChildView*)mView bottomCornerRadius];
+  mHasRoundedBottomCorners = [(ChildView*)mView hasRoundedBottomCorners];
+  CGFloat cornerRadius = [(ChildView*)mView cornerRadius];
   mDevPixelCornerRadius = cornerRadius * BackingScaleFactor();
+  mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar];
+  if (mIsCoveringTitlebar) {
+    mTitlebarRect = RectContainingTitlebarControls();
+    UpdateTitlebarImageBuffer();
+  }
 }
 
 void
 nsChildView::CleanupWindowEffects()
 {
   mResizerImage = nullptr;
   mCornerMaskImage = nullptr;
+  mTitlebarImage = nullptr;
 }
 
 void
 nsChildView::PreRender(LayerManager* aManager)
 {
   nsAutoPtr<GLManager> manager(GLManager::CreateGLManager(aManager));
   if (!manager) {
     return;
@@ -1847,18 +1884,32 @@ nsChildView::PreRender(LayerManager* aMa
 void
 nsChildView::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect)
 {
   nsAutoPtr<GLManager> manager(GLManager::CreateGLManager(aManager));
   if (!manager) {
     return;
   }
 
+  manager->gl()->PushScissorRect(aRect);
+
+  MaybeDrawTitlebar(manager, aRect);
   MaybeDrawResizeIndicator(manager, aRect);
-  MaybeDrawRoundedBottomCorners(manager, aRect);
+  MaybeDrawRoundedCorners(manager, aRect);
+
+  manager->gl()->PopScissorRect();
+}
+
+static void
+ClearRegion(gfxASurface* aSurface, nsIntRegion aRegion)
+{
+  nsRefPtr<gfxContext> ctx = new gfxContext(aSurface);
+  gfxUtils::ClipToRegion(ctx, aRegion);
+  ctx->SetOperator(gfxContext::OPERATOR_CLEAR);
+  ctx->Paint();
 }
 
 static void
 DrawResizer(CGContextRef aCtx)
 {
   CGContextSetShouldAntialias(aCtx, false);
   CGPoint points[6];
   points[0] = CGPointMake(13.0f, 4.0f);
@@ -1885,24 +1936,24 @@ DrawResizer(CGContextRef aCtx)
   points[3] = CGPointMake(9.0f, 14.0f);
   points[5] = CGPointMake(13.0f, 13.9f);
   points[4] = CGPointMake(13.0f, 14.0f);
   CGContextSetRGBStrokeColor(aCtx, 0.84f, 0.84f, 0.84f, 0.55f);
   CGContextStrokeLineSegments(aCtx, points, 6);
 }
 
 void
-nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, nsIntRect aRect)
-{
+nsChildView::MaybeDrawResizeIndicator(GLManager* aManager, const nsIntRect& aRect)
+{
+  MutexAutoLock lock(mEffectsLock);
   if (!mShowsResizeIndicator || mFailedResizerImage) {
     return;
   }
 
   if (!mResizerImage) {
-    MutexAutoLock lock(mEffectsLock);
     mResizerImage =
       aManager->gl()->CreateTextureImage(nsIntSize(mResizeIndicatorRect.width,
                                                    mResizeIndicatorRect.height),
                                          gfxASurface::CONTENT_COLOR_ALPHA,
                                          LOCAL_GL_CLAMP_TO_EDGE,
                                          TextureImage::UseNearestFilter);
 
     // Creation of texture images can fail.
@@ -1911,36 +1962,37 @@ nsChildView::MaybeDrawResizeIndicator(GL
 
     nsIntRegion update(nsIntRect(0, 0, mResizeIndicatorRect.width, mResizeIndicatorRect.height));
     gfxASurface *asurf = mResizerImage->BeginUpdate(update);
     if (!asurf) {
       mResizerImage = nullptr;
       return;
     }
 
-    // is the CairoSurface of a non QuartzSurface usable in the gfxQuartzSurface constructor ?
-    // answer seems to be NO (see comments on bug 675410
-    // this should not work for instance: new gfxQuartzSurface(asurf->CairoSurface(), false))
+    // We need a Quartz surface because DrawResizer wants a CGContext.
     if (asurf->GetType() != gfxASurface::SurfaceTypeQuartz) {
       NS_WARN_IF_FALSE(FALSE, "mResizerImage's surface is not Quartz");
+      mResizerImage->EndUpdate();
       mResizerImage = nullptr;
       mFailedResizerImage = true;
       return;
     }
+
+    ClearRegion(asurf, update);
+
     nsRefPtr<gfxQuartzSurface> image = static_cast<gfxQuartzSurface*>(asurf);
-
     DrawResizer(image->GetCGContext());
 
     mResizerImage->EndUpdate();
   }
 
   NS_ABORT_IF_FALSE(mResizerImage, "Must have a texture allocated by now!");
 
-  float bottomX = aRect.x + aRect.width;
-  float bottomY = aRect.y + aRect.height;
+  float bottomX = aRect.XMost();
+  float bottomY = aRect.YMost();
 
   TextureImage::ScopedBindTexture texBind(mResizerImage, LOCAL_GL_TEXTURE0);
 
   ShaderProgramOGL *program =
     aManager->GetProgram(mResizerImage->GetShaderProgramType());
   program->Activate();
   program->SetLayerQuadRect(nsIntRect(bottomX - resizeIndicatorWidth,
                                       bottomY - resizeIndicatorHeight,
@@ -1949,32 +2001,221 @@ nsChildView::MaybeDrawResizeIndicator(GL
   program->SetLayerTransform(gfx3DMatrix());
   program->SetLayerOpacity(1.0);
   program->SetRenderOffset(nsIntPoint(0,0));
   program->SetTextureUnit(0);
 
   aManager->BindAndDrawQuad(program);
 }
 
+// Draw the highlight line at the top of the titlebar.
+// This function draws into the current NSGraphicsContext and assumes flippedness.
+static void
+DrawTitlebarHighlight(NSSize aWindowSize, CGFloat aRadius, CGFloat aDevicePixelWidth)
+{
+  [NSGraphicsContext saveGraphicsState];
+
+  // Set up the clip path. We start with the outer rectangle and cut out a
+  // slightly smaller inner rectangle with rounded corners.
+  // The outer corners of the resulting path will be square, but they will be
+  // masked away in a later step.
+  NSBezierPath* path = [NSBezierPath bezierPath];
+  [path setWindingRule:NSEvenOddWindingRule];
+  NSRect pathRect = NSMakeRect(0, 0, aWindowSize.width, kTitlebarHighlightHeight + 2);
+  [path appendBezierPathWithRect:pathRect];
+  pathRect = NSInsetRect(pathRect, aDevicePixelWidth, aDevicePixelWidth);
+  CGFloat innerRadius = aRadius - aDevicePixelWidth;
+  [path appendBezierPathWithRoundedRect:pathRect xRadius:innerRadius yRadius:innerRadius];
+  [path addClip];
+
+  // Now we fill the path with a subtle highlight gradient.
+  NSColor* topColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.4];
+  NSColor* bottomColor = [NSColor colorWithDeviceWhite:1.0 alpha:0.0];
+  NSGradient* gradient = [[NSGradient alloc] initWithStartingColor:topColor endingColor:bottomColor];
+  [gradient drawInRect:NSMakeRect(0, 0, aWindowSize.width, kTitlebarHighlightHeight) angle:90];
+  [gradient release];
+
+  [NSGraphicsContext restoreGraphicsState];
+}
+
+// When this method is entered, mEffectsLock is already being held.
+void
+nsChildView::UpdateTitlebarImageBuffer()
+{
+  nsIntRegion dirtyTitlebarRegion = mDirtyTitlebarRegion;
+  mDirtyTitlebarRegion.SetEmpty();
+
+  if (!mTitlebarImageBuffer ||
+      mTitlebarImageBuffer->GetSize() != mTitlebarRect.Size()) {
+    dirtyTitlebarRegion = mTitlebarRect;
+
+    mTitlebarImageBuffer = new gfxQuartzSurface(mTitlebarRect.Size(),
+                                                gfxASurface::ImageFormatARGB32);
+  }
+
+  if (dirtyTitlebarRegion.IsEmpty())
+    return;
+
+  ClearRegion(mTitlebarImageBuffer, dirtyTitlebarRegion);
+
+  CGContextRef ctx = mTitlebarImageBuffer->GetCGContext();
+
+  double scale = BackingScaleFactor();
+  CGContextScaleCTM(ctx, scale, scale);
+  NSGraphicsContext* oldContext = [NSGraphicsContext currentContext];
+  [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
+
+  CGContextSaveGState(ctx);
+
+  BaseWindow* window = (BaseWindow*)[mView window];
+  NSView* frameView = [[window contentView] superview];
+  if (![frameView isFlipped]) {
+    CGContextTranslateCTM(ctx, 0, [frameView bounds].size.height);
+    CGContextScaleCTM(ctx, 1, -1);
+  }
+
+  // Draw the titlebar controls into the titlebar image.
+  for (id view in [window titlebarControls]) {
+    NSRect viewFrame = [view frame];
+    nsIntRect viewRect = CocoaPointsToDevPixels([mView convertRect:viewFrame fromView:frameView]);
+    nsIntRegion intersection;
+    intersection.And(dirtyTitlebarRegion, viewRect);
+    if (intersection.IsEmpty()) {
+      continue;
+    }
+    // All of the titlebar controls we're interested in are subclasses of
+    // NSButton.
+    if (![view isKindOfClass:[NSButton class]]) {
+      continue;
+    }
+    NSButton *button = (NSButton *) view;
+    id cellObject = [button cell];
+    if (![cellObject isKindOfClass:[NSCell class]]) {
+      continue;
+    }
+    NSCell *cell = (NSCell *) cellObject;
+
+    CGContextSaveGState(ctx);
+    CGContextTranslateCTM(ctx, viewFrame.origin.x, viewFrame.origin.y);
+
+    if ([view isFlipped]) {
+      CGContextTranslateCTM(ctx, 0, viewFrame.size.height);
+      CGContextScaleCTM(ctx, 1, -1);
+    }
+
+    NSRect intersectRect = DevPixelsToCocoaPoints(intersection.GetBounds());
+    [cell drawWithFrame:[view convertRect:intersectRect fromView:mView] inView:button];
+
+    CGContextRestoreGState(ctx);
+  }
+
+  CGContextRestoreGState(ctx);
+
+  DrawTitlebarHighlight([frameView bounds].size, [(ChildView*)mView cornerRadius],
+                        DevPixelsToCocoaPoints(1));
+
+  [NSGraphicsContext setCurrentContext:oldContext];
+
+  mUpdatedTitlebarRegion.Or(mUpdatedTitlebarRegion, dirtyTitlebarRegion);
+}
+
+// When this method is entered, mEffectsLock is already being held.
+void
+nsChildView::UpdateTitlebarImage(GLManager* aManager, const nsIntRect& aRect)
+{
+  nsIntRegion updatedTitlebarRegion;
+  updatedTitlebarRegion.And(mUpdatedTitlebarRegion, mTitlebarRect);
+  mUpdatedTitlebarRegion.SetEmpty();
+
+  if (!mTitlebarImage || mTitlebarImage->GetSize() != mTitlebarRect.Size()) {
+    updatedTitlebarRegion = mTitlebarRect;
+
+    mTitlebarImage =
+      aManager->gl()->CreateTextureImage(mTitlebarRect.Size(),
+                                         gfxASurface::CONTENT_COLOR_ALPHA,
+                                         LOCAL_GL_CLAMP_TO_EDGE,
+                                         TextureImage::UseNearestFilter);
+
+    // Creation of texture images can fail.
+    if (!mTitlebarImage)
+      return;
+  }
+
+  if (updatedTitlebarRegion.IsEmpty())
+    return;
+
+  gfxASurface *asurf = mTitlebarImage->BeginUpdate(updatedTitlebarRegion);
+  if (!asurf) {
+    mTitlebarImage = nullptr;
+    return;
+  }
+
+  nsRefPtr<gfxContext> ctx = new gfxContext(asurf);
+  ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+  ctx->SetSource(mTitlebarImageBuffer);
+  ctx->Paint();
+
+  mTitlebarImage->EndUpdate();
+}
+
+// This method draws an overlay in the top of the window which contains the
+// titlebar controls (e.g. close, min, zoom, fullscreen) and the titlebar
+// highlight effect.
+// This is necessary because the real titlebar controls are covered by our
+// OpenGL context. Note that in terms of the NSView hierarchy, our ChildView
+// is actually below the titlebar controls - that's why hovering and clicking
+// them works as expected - but their visual representation is only drawn into
+// the normal window buffer, and the window buffer surface lies below the
+// GLContext surface. In order to make the titlebar controls visible, we have
+// to redraw them inside the OpenGL context surface.
+void
+nsChildView::MaybeDrawTitlebar(GLManager* aManager, const nsIntRect& aRect)
+{
+  MutexAutoLock lock(mEffectsLock);
+  if (!mIsCoveringTitlebar) {
+    return;
+  }
+
+  UpdateTitlebarImage(aManager, aRect);
+
+  if (!mTitlebarImage) {
+    return;
+  }
+
+  TextureImage::ScopedBindTexture texBind(mTitlebarImage, LOCAL_GL_TEXTURE0);
+
+  ShaderProgramOGL *program =
+    aManager->GetProgram(mTitlebarImage->GetShaderProgramType());
+  program->Activate();
+  program->SetLayerQuadRect(nsIntRect(nsIntPoint(0, 0),
+                                      mTitlebarImage->GetSize()));
+  program->SetLayerTransform(gfx3DMatrix());
+  program->SetLayerOpacity(1.0);
+  program->SetRenderOffset(nsIntPoint(0,0));
+  program->SetTextureUnit(0);
+
+  aManager->BindAndDrawQuad(program);
+}
+
 static void
 DrawTopLeftCornerMask(CGContextRef aCtx, int aRadius)
 {
   CGContextSetRGBFillColor(aCtx, 1.0, 1.0, 1.0, 1.0);
   CGContextFillEllipseInRect(aCtx, CGRectMake(0, 0, aRadius * 2, aRadius * 2));
 }
 
 void
-nsChildView::MaybeDrawRoundedBottomCorners(GLManager* aManager, nsIntRect aRect)
-{
-  if (!mHasRoundedBottomCorners ||
-      mFailedCornerMaskImage)
-    return;
-  
+nsChildView::MaybeDrawRoundedCorners(GLManager* aManager, const nsIntRect& aRect)
+{
   MutexAutoLock lock(mEffectsLock);
   
+  if (mFailedCornerMaskImage) {
+    return;
+  }
+
   if (!mCornerMaskImage) {
     mCornerMaskImage =
       aManager->gl()->CreateTextureImage(nsIntSize(mDevPixelCornerRadius,
                                                    mDevPixelCornerRadius),
                                          gfxASurface::CONTENT_COLOR_ALPHA,
                                          LOCAL_GL_CLAMP_TO_EDGE,
                                          TextureImage::UseNearestFilter);
 
@@ -1983,58 +2224,69 @@ nsChildView::MaybeDrawRoundedBottomCorne
       return;
 
     nsIntRegion update(nsIntRect(0, 0, mDevPixelCornerRadius, mDevPixelCornerRadius));
     gfxASurface *asurf = mCornerMaskImage->BeginUpdate(update);
     if (!asurf) {
       mCornerMaskImage = nullptr;
       return;
     }
-    
-    // is the CairoSurface of a non QuartzSurface usable in the gfxQuartzSurface constructor ?
-    // answer seems to be NO (see comments on bug 675410
-    // this should not work for instance: new gfxQuartzSurface(asurf->CairoSurface(), false))
+
     if (asurf->GetType() != gfxASurface::SurfaceTypeQuartz) {
-      NS_WARN_IF_FALSE(FALSE, "mCornerMaskImage's surface is not Quartz");
+      NS_WARNING("mCornerMaskImage's surface is not Quartz");
+      mCornerMaskImage->EndUpdate();
       mCornerMaskImage = nullptr;
       mFailedCornerMaskImage = true;
       return;
     }
+
+    ClearRegion(asurf, update);
+
     nsRefPtr<gfxQuartzSurface> image = static_cast<gfxQuartzSurface*>(asurf);
-    
     DrawTopLeftCornerMask(image->GetCGContext(), mDevPixelCornerRadius);
-    
+
     mCornerMaskImage->EndUpdate();
   }
   
   NS_ABORT_IF_FALSE(mCornerMaskImage, "Must have a texture allocated by now!");
   
   TextureImage::ScopedBindTexture texBind(mCornerMaskImage, LOCAL_GL_TEXTURE0);
   
   ShaderProgramOGL *program = aManager->GetProgram(mCornerMaskImage->GetShaderProgramType());
   program->Activate();
-  program->SetLayerQuadRect(nsIntRect(0, 0, // aRect.x, aRect.y,
-                                      mDevPixelCornerRadius,
-                                      mDevPixelCornerRadius));
+  program->SetLayerQuadRect(nsIntRect(nsIntPoint(0, 0),
+                                      mCornerMaskImage->GetSize()));
   program->SetLayerOpacity(1.0);
   program->SetRenderOffset(nsIntPoint(0,0));
   program->SetTextureUnit(0);
-  
+
   // Use operator destination in: multiply all 4 channels with source alpha.
   aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA,
                                      LOCAL_GL_ZERO, LOCAL_GL_SRC_ALPHA);
   
-  // Draw; first bottom left, then bottom right.
-  program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(1, -1, 1) *
-                             gfx3DMatrix::Translation(0, aRect.height, 0));
-  aManager->BindAndDrawQuad(program);
-  program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(-1, -1, 1) *
-                             gfx3DMatrix::Translation(aRect.width, aRect.height, 0));
-  aManager->BindAndDrawQuad(program);
-  
+  if (mIsCoveringTitlebar) {
+    // Mask the top corners.
+    program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(1, 1, 1) *
+                               gfx3DMatrix::Translation(0, 0, 0));
+    aManager->BindAndDrawQuad(program);
+    program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(-1, 1, 1) *
+                               gfx3DMatrix::Translation(aRect.width, 0, 0));
+    aManager->BindAndDrawQuad(program);
+  }
+
+  if (mHasRoundedBottomCorners) {
+    // Mask the bottom corners.
+    program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(1, -1, 1) *
+                               gfx3DMatrix::Translation(0, aRect.height, 0));
+    aManager->BindAndDrawQuad(program);
+    program->SetLayerTransform(gfx3DMatrix::ScalingMatrix(-1, -1, 1) *
+                               gfx3DMatrix::Translation(aRect.width, aRect.height, 0));
+    aManager->BindAndDrawQuad(program);
+  }
+
   // Reset blend mode.
   aManager->gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA,
                                      LOCAL_GL_ONE, LOCAL_GL_ONE);
 }
 
 static int32_t
 FindTitlebarBottom(const nsTArray<nsIWidget::ThemeGeometry>& aThemeGeometries,
                    int32_t aWindowWidth)
@@ -2230,16 +2482,18 @@ NSEvent* gLastDragMouseDownEvent = nil;
     mDidForceRefreshOpenGL = NO;
 
     [self setFocusRingType:NSFocusRingTypeNone];
 
 #ifdef __LP64__
     mCancelSwipeAnimation = nil;
     mCurrentSwipeDir = 0;
 #endif
+
+    mTopLeftCornerMask = NULL;
   }
 
   // register for things we'll take from other applications
   [ChildView registerViewForDraggedTypes:self];
 
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(windowBecameMain:)
                                                name:NSWindowDidBecomeMainNotification
@@ -2330,16 +2584,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
 - (void)dealloc
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [mGLContext release];
   [mPendingDirtyRects release];
   [mLastMouseDownEvent release];
   [mClickThroughMouseDownEvent release];
+  CGImageRelease(mTopLeftCornerMask);
   ChildViewMouseTracker::OnDestroyView(self);
 
   [[NSNotificationCenter defaultCenter] removeObserver:self];
   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
 
   [super dealloc];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
@@ -2683,16 +2938,23 @@ NSEvent* gLastDragMouseDownEvent = nil;
   if (mGeckoChild) {
     // actually, it could be the color space that's changed,
     // but we can't tell the difference here except by retrieving
     // the backing scale factor and comparing to the old value
     mGeckoChild->BackingScaleFactorChanged();
   }
 }
 
+- (BOOL)isCoveringTitlebar
+{
+  return [[self window] isKindOfClass:[BaseWindow class]] &&
+         [(BaseWindow*)[self window] mainChildView] == self &&
+         [(BaseWindow*)[self window] drawsContentsIntoWindowFrame];
+}
+
 // 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
@@ -2719,72 +2981,82 @@ NSEvent* gLastDragMouseDownEvent = nil;
            self, mGeckoChild,
            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
 
-  if ([self isUsingOpenGL]) {
-    // We usually don't get here for Gecko-initiated repaints. Instead, those
-    // eventually call drawUsingOpenGL, and don't go through drawRect.
-    // Paints that come through here are triggered by something that Cocoa
-    // controls, for example by window resizing or window focus changes.
-
-    // Since our window is usually declared as opaque, the window's pixel
-    // buffer may now contain garbage which we need to prevent from reaching
-    // the screen. The only place where garbage can show is in the bottom
-    // corners - the rest of the window is covered by our OpenGL surface.
-    // So we need to clear the pixel buffer contents in the corners.
-    [self maybeClearBottomCorners];
-
-    // Do GL composition and return.
-    [self drawUsingOpenGL];
-    return;
-  }
-
-  PROFILER_LABEL("widget", "ChildView::drawRect");
-
   nsIntRegion region;
   nsIntRect boundingRect = mGeckoChild->CocoaPointsToDevPixels(aRect);
   const NSRect *rects;
   NSInteger count, i;
   [[NSView focusView] getRectsBeingDrawn:&rects count:&count];
+
+  CGContextClipToRects(aContext, (CGRect*)rects, count);
+
   if (count < MAX_RECTS_IN_REGION) {
     for (i = 0; i < count; ++i) {
       // Add the rect to the region.
       NSRect r = [self convertRect:rects[i] fromView:[NSView focusView]];
       region.Or(region, mGeckoChild->CocoaPointsToDevPixels(r));
     }
     region.And(region, boundingRect);
   } else {
     region = boundingRect;
   }
 
-  // Create Cairo objects.
+  if ([self isUsingOpenGL]) {
+    // For Gecko-initiated repaints in OpenGL mode, drawUsingOpenGL is
+    // directly called from a delayed perform callback - without going through
+    // drawRect.
+    // Paints that come through here are triggered by something that Cocoa
+    // controls, for example by window resizing or window focus changes.
+
+    // Since this view is usually declared as opaque, the window's pixel
+    // buffer may now contain garbage which we need to prevent from reaching
+    // the screen. The only place where garbage can show is in the window
+    // corners - the rest of the window is covered by opaque content in our
+    // OpenGL surface.
+    // So we need to clear the pixel buffer contents in the corners.
+    [self clearCorners];
+
+    // When our view covers the titlebar, we need to repaint the titlebar
+    // texture buffer when, for example, the window buttons are hovered.
+    // So we notify our nsChildView about any areas needing repainting.
+    mGeckoChild->NotifyDirtyRegion(region);
+
+    // Do GL composition and return.
+    [self drawUsingOpenGL];
+    return;
+  }
+
+  PROFILER_LABEL("widget", "ChildView::drawRect");
 
   // 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.
   double scale = mGeckoChild->BackingScaleFactor();
+  CGContextSaveGState(aContext);
   CGContextScaleCTM(aContext, 1.0 / scale, 1.0 / scale);
 
   NSSize viewSize = [self bounds].size;
   nsIntSize backingSize(viewSize.width * scale, viewSize.height * scale);
 
+  CGContextSaveGState(aContext);
+
+  // Create Cairo objects.
   nsRefPtr<gfxQuartzSurface> targetSurface =
     new gfxQuartzSurface(aContext, backingSize);
   targetSurface->SetAllowUseAsSource(false);
 
   nsRefPtr<gfxContext> targetContext = new gfxContext(targetSurface);
 
-  gfxContextMatrixAutoSaveRestore save(targetContext);
-
   // 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));
@@ -2801,26 +3073,36 @@ NSEvent* gLastDragMouseDownEvent = nil;
     // We only need this so that we actually get DidPaintWindow fired
     if (Compositor::GetBackend() == LAYERS_BASIC) {
       ClientLayerManager *manager = static_cast<ClientLayerManager*>(mGeckoChild->GetLayerManager());
       manager->SetShadowTarget(targetContext);
     }
     painted = mGeckoChild->PaintWindow(region);
   }
 
+  targetContext = nullptr;
+  targetSurface = nullptr;
+
+  CGContextRestoreGState(aContext);
+
+  // Undo the scale transform so that from now on the context is in
+  // CocoaPoints again.
+  CGContextRestoreGState(aContext);
+
   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, 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)
+  if ([self isCoveringTitlebar]) {
+    [self drawTitlebarHighlight];
+    [self maskTopCornersInContext:aContext];
+  }
 
 #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,
@@ -2895,49 +3177,102 @@ NSEvent* gLastDragMouseDownEvent = nil;
 }
 
 - (BOOL)hasRoundedBottomCorners
 {
   return [[self window] respondsToSelector:@selector(bottomCornerRounded)] &&
   [[self window] bottomCornerRounded];
 }
 
-- (CGFloat)bottomCornerRadius
-{
-  if (![self hasRoundedBottomCorners])
-    return 0.0f;
-  NSView* borderView = [[[self window] contentView] superview];
-  if (!borderView || ![borderView respondsToSelector:@selector(roundedCornerRadius)])
+- (CGFloat)cornerRadius
+{
+  NSView* frameView = [[[self window] contentView] superview];
+  if (!frameView || ![frameView respondsToSelector:@selector(roundedCornerRadius)])
     return 4.0f;
-  return [borderView roundedCornerRadius];
+  return [frameView roundedCornerRadius];
 }
 
 // Accelerated windows have two NSSurfaces:
 //  (1) The window's pixel buffer in the back and
 //  (2) the OpenGL view in the front.
-// These two surfaces are composited by the window manager. Drawing with the
-// usual CGContext functions ends up in (1).
-// When our window has rounded bottom corners, the OpenGL view has transparent
-// pixels in the corners. In these places the contents of the window's pixel
-// buffer can show through. So we need to make sure that the pixel buffer is
+// These two surfaces are composited by the window manager. Drawing into the
+// CGContext which is provided by drawRect ends up in (1).
+// When our window has rounded corners, the OpenGL view has transparent pixels
+// in the corners. In these places the contents of the window's pixel buffer
+// can show through. So we need to make sure that the pixel buffer is
 // transparent in the corners so that no garbage reaches the screen.
 // The contents of the pixel buffer in the rest of the window don't matter
-// because they're covered by the OpenGL view.
-// Making the bottom corners transparent works even though our window is
+// because they're covered by opaque pixels of the OpenGL context.
+// Making the corners transparent works even though our window is
 // declared "opaque" (in the NSWindow's isOpaque method).
-- (void)maybeClearBottomCorners
-{
-  if (![self hasRoundedBottomCorners])
-    return;
-  
-  int radius = [self bottomCornerRadius];
-  int w = [self bounds].size.width, h = [self bounds].size.height;
+- (void)clearCorners
+{
+  CGFloat radius = [self cornerRadius];
+  CGFloat w = [self bounds].size.width, h = [self bounds].size.height;
   [[NSColor clearColor] set];
-  NSRectFill(NSMakeRect(0, h - radius, radius, radius));
-  NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
+
+  if ([self isCoveringTitlebar]) {
+    NSRectFill(NSMakeRect(0, 0, radius, radius));
+    NSRectFill(NSMakeRect(w - radius, 0, radius, radius));
+  }
+
+  if ([self hasRoundedBottomCorners]) {
+    NSRectFill(NSMakeRect(0, h - radius, radius, radius));
+    NSRectFill(NSMakeRect(w - radius, h - radius, radius, radius));
+  }
+}
+
+// This is the analog of nsChildView::MaybeDrawRoundedCorners for CGContexts.
+// We only need to mask the top corners here because Cocoa does the masking
+// for the window's bottom corners automatically (starting with 10.7).
+- (void)maskTopCornersInContext:(CGContextRef)aContext
+{
+  CGFloat radius = [self cornerRadius];
+  int32_t devPixelCornerRadius = mGeckoChild->CocoaPointsToDevPixels(radius);
+
+  // First make sure that mTopLeftCornerMask is set up.
+  if (!mTopLeftCornerMask ||
+      int32_t(CGImageGetWidth(mTopLeftCornerMask)) != devPixelCornerRadius) {
+    CGImageRelease(mTopLeftCornerMask);
+    CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+    CGContextRef imgCtx = CGBitmapContextCreate(NULL,
+                                                devPixelCornerRadius,
+                                                devPixelCornerRadius,
+                                                8, devPixelCornerRadius * 4,
+                                                rgb, kCGImageAlphaPremultipliedFirst);
+    CGColorSpaceRelease(rgb);
+    DrawTopLeftCornerMask(imgCtx, devPixelCornerRadius);
+    mTopLeftCornerMask = CGBitmapContextCreateImage(imgCtx);
+    CGContextRelease(imgCtx);
+  }
+
+  // kCGBlendModeDestinationIn is the secret sauce which allows us to erase
+  // already painted pixels. It's defined as R = D * Sa: multiply all channels
+  // of the destination pixel with the alpha of the source pixel. In our case,
+  // the source is mTopLeftCornerMask.
+  CGContextSaveGState(aContext);
+  CGContextSetBlendMode(aContext, kCGBlendModeDestinationIn);
+
+  CGRect destRect = CGRectMake(0, 0, radius, radius);
+
+  // Erase the top left corner...
+  CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
+
+  // ... and the top right corner.
+  CGContextTranslateCTM(aContext, [self bounds].size.width, 0);
+  CGContextScaleCTM(aContext, -1, 1);
+  CGContextDrawImage(aContext, destRect, mTopLeftCornerMask);
+
+  CGContextRestoreGState(aContext);
+}
+
+- (void)drawTitlebarHighlight
+{
+  DrawTitlebarHighlight([self bounds].size, [self cornerRadius],
+                        mGeckoChild->DevPixelsToCocoaPoints(1));
 }
 
 - (void)releaseWidgets:(NSArray*)aWidgetArray
 {
   if (!aWidgetArray) {
     return;
   }
   NSInteger count = [aWidgetArray count];
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -92,16 +92,18 @@ typedef struct _nsCocoaWindowList {
 - (void)mouseEntered:(NSEvent*)aEvent;
 - (void)mouseExited:(NSEvent*)aEvent;
 - (void)mouseMoved:(NSEvent*)aEvent;
 - (void)updateTrackingArea;
 - (NSView*)trackingAreaView;
 
 - (ChildView*)mainChildView;
 
+- (NSArray*)titlebarControls;
+
 @end
 
 @interface NSWindow (Undocumented)
 
 // If a window has been explicitly removed from the "window cache" (to
 // deactivate it), it's sometimes necessary to "reset" it to reactivate it
 // (and put it back in the "window cache").  One way to do this, which Apple
 // often uses, is to set the "window number" to '-1' and then back to its
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -2711,16 +2711,25 @@ static const NSString* kStateShowsToolba
 
   // Now move the contentView to the bottommost layer so that it's guaranteed
   // to be under the window buttons.
   NSView* frameView = [aView superview];
   [aView removeFromSuperview];
   [frameView addSubview:aView positioned:NSWindowBelow relativeTo:nil];
 }
 
+- (NSArray*)titlebarControls
+{
+  // Return all subviews of the frameView which are not the content view.
+  NSView* frameView = [[self contentView] superview];
+  NSMutableArray* array = [[[frameView subviews] mutableCopy] autorelease];
+  [array removeObject:[self contentView]];
+  return array;
+}
+
 - (BOOL)respondsToSelector:(SEL)aSelector
 {
   // Claim the window doesn't respond to this so that the system
   // doesn't steal keyboard equivalents for it. Bug 613710.
   if (aSelector == @selector(cancelOperation:)) {
     return NO;
   }