Bug 647216: Add ability to handle mouse events in the title bar on OS X. Work started by Markus Stange and Paul O'Shannessy. r=mstange
authorJosh Aas <joshmoz@gmail.com>
Fri, 26 Oct 2012 15:42:50 -0400
changeset 111691 6332aa884dab4f516ecfdd3179c21fb5a782d61d
parent 111690 479dc045b543cf2d5407258f5c93343e21070514
child 111692 487f6dcb3d85c322852a86f9417ae2b1a2ce9676
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersmstange
bugs647216
milestone19.0a1
Bug 647216: Add ability to handle mouse events in the title bar on OS X. Work started by Markus Stange and Paul O'Shannessy. r=mstange
toolkit/content/WindowDraggingUtils.jsm
widget/cocoa/nsChildView.h
widget/cocoa/nsChildView.mm
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
--- a/toolkit/content/WindowDraggingUtils.jsm
+++ b/toolkit/content/WindowDraggingUtils.jsm
@@ -1,18 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#ifdef XP_WIN
+#define USE_HITTEST
+#elifdef MOZ_WIDGET_COCOA
+#define USE_HITTEST
+#endif
+
 let EXPORTED_SYMBOLS = [ "WindowDraggingElement" ];
 
 function WindowDraggingElement(elem) {
   this._elem = elem;
   this._window = elem.ownerDocument.defaultView;
-#ifdef XP_WIN
+#ifdef USE_HITTEST
   if (!this.isPanel())
     this._elem.addEventListener("MozMouseHittest", this, false);
   else
 #endif
   this._elem.addEventListener("mousedown", this, false);
 }
 
 WindowDraggingElement.prototype = {
@@ -49,17 +55,17 @@ WindowDraggingElement.prototype = {
     return true;
   },
   isPanel : function() {
     return this._elem instanceof Components.interfaces.nsIDOMXULElement &&
            this._elem.localName == "panel";
   },
   handleEvent: function(aEvent) {
     let isPanel = this.isPanel();
-#ifdef XP_WIN
+#ifdef USE_HITTEST
     if (!isPanel) {
       if (this.shouldDrag(aEvent))
         aEvent.preventDefault();
       return;
     }
 #endif
 
     switch (aEvent.type) {
--- a/widget/cocoa/nsChildView.h
+++ b/widget/cocoa/nsChildView.h
@@ -178,16 +178,20 @@ typedef NSInteger NSEventGestureAxis;
 // automatically firing momentum scroll events.
 @interface NSEvent (ScrollPhase)
 // Leopard and SnowLeopard
 - (long long)_scrollPhase;
 // Lion and above
 - (NSEventPhase)momentumPhase;
 @end
 
+@protocol EventRedirection
+  - (NSView*)targetView;
+@end
+
 @interface ChildView : NSView<
 #ifdef ACCESSIBILITY
                               mozAccessible,
 #endif
                               mozView, NSTextInput>
 {
 @private
   // the nsChildView that created the view. It retains this NSView, so
@@ -262,25 +266,29 @@ typedef NSInteger NSEventGestureAxis;
 
   // Whether this uses off-main-thread compositing.
   BOOL mUsingOMTCompositor;
 }
 
 // class initialization
 + (void)initialize;
 
++ (void)registerViewForDraggedTypes:(NSView*)aView;
+
 // these are sent to the first responder when the window key status changes
 - (void)viewsWindowDidBecomeKey;
 - (void)viewsWindowDidResignKey;
 
 // Stop NSView hierarchy being changed during [ChildView drawRect:]
 - (void)delayedTearDown;
 
 - (void)sendFocusEvent:(uint32_t)eventType;
 
+- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent;
+
 - (void)handleMouseMoved:(NSEvent*)aEvent;
 
 - (void)drawRect:(NSRect)aRect inTitlebarContext:(CGContextRef)aContext;
 
 - (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
                             enter:(BOOL)aEnter
                              type:(nsMouseEvent::exitType)aType;
 
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -1900,16 +1900,30 @@ NSEvent* gLastDragMouseDownEvent = nil;
 
     [sendTypes release];
     [returnTypes release];
 
     initialized = YES;
   }
 }
 
++ (void)registerViewForDraggedTypes:(NSView*)aView
+{
+  [aView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
+                                                           NSStringPboardType,
+                                                           NSHTMLPboardType,
+                                                           NSURLPboardType,
+                                                           NSFilesPromisePboardType,
+                                                           kWildcardPboardType,
+                                                           kCorePboardType_url,
+                                                           kCorePboardType_urld,
+                                                           kCorePboardType_urln,
+                                                           nil]];
+}
+
 // initWithFrame:geckoChild:
 - (id)initWithFrame:(NSRect)inFrame geckoChild:(nsChildView*)inChild
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   if ((self = [super initWithFrame:inFrame])) {
     mGeckoChild = inChild;
     mIsPluginView = NO;
@@ -1947,27 +1961,18 @@ NSEvent* gLastDragMouseDownEvent = nil;
     [self setFocusRingType:NSFocusRingTypeNone];
 
 #ifdef __LP64__
     mSwipeAnimationCancelled = nil;
 #endif
   }
   
   // register for things we'll take from other applications
-  PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView initWithFrame: registering drag types\n"));
-  [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
-                                                          NSStringPboardType,
-                                                          NSHTMLPboardType,
-                                                          NSURLPboardType,
-                                                          NSFilesPromisePboardType,
-                                                          kWildcardPboardType,
-                                                          kCorePboardType_url,
-                                                          kCorePboardType_urld,
-                                                          kCorePboardType_urln,
-                                                          nil]];
+  [ChildView registerViewForDraggedTypes:self];
+
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(windowBecameMain:)
                                                name:NSWindowDidBecomeMainNotification
                                              object:nil];
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(windowResignedMain:)
                                                name:NSWindowDidResignMainNotification
                                              object:nil];
@@ -2327,17 +2332,17 @@ NSEvent* gLastDragMouseDownEvent = nil;
   }
   [super scrollRect:aRect by:offset];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (BOOL)mouseDownCanMoveWindow
 {
-  return NO;
+  return [[self window] isMovableByWindowBackground];
 }
 
 - (void)lockFocus
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [super lockFocus];
 
@@ -3273,16 +3278,35 @@ NSEvent* gLastDragMouseDownEvent = nil;
   }
 
   event.exit = aType;
 
   nsEventStatus status; // ignored
   mGeckoChild->DispatchEvent(&event, status);
 }
 
+- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent
+{
+  if (!theEvent || !mGeckoChild) {
+    return;
+  }
+
+  nsCocoaWindow* windowWidget = mGeckoChild->GetXULWindowWidget();
+  if (!windowWidget) {
+    return;
+  }
+
+  // We assume later on that sending a hit test event won't cause widget destruction.
+  nsMouseEvent hitTestEvent(true, NS_MOUSE_MOZHITTEST, mGeckoChild, nsMouseEvent::eReal);
+  [self convertCocoaMouseEvent:theEvent toGeckoEvent:&hitTestEvent];
+  bool result = mGeckoChild->DispatchWindowEvent(hitTestEvent);
+
+  [windowWidget->GetCocoaWindow() setMovableByWindowBackground:result];
+}
+
 - (void)handleMouseMoved:(NSEvent*)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (!mGeckoChild)
     return;
 
   nsMouseEvent geckoEvent(true, NS_MOUSE_MOVE, mGeckoChild, nsMouseEvent::eReal);
@@ -4797,16 +4821,21 @@ ChildView*
 ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
 {
   NSWindow* window = sWindowUnderMouse;
   if (!window)
     return nil;
 
   NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
   NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
+
+  while([view conformsToProtocol:@protocol(EventRedirection)]) {
+    view = [(id<EventRedirection>)view targetView];
+  }
+
   if (![view isKindOfClass:[ChildView class]])
     return nil;
 
   ChildView* childView = (ChildView*)view;
   // If childView is being destroyed return nil.
   if (![childView widget])
     return nil;
   return WindowAcceptsEvent(window, aEvent, childView) ? childView : nil;
@@ -4895,17 +4924,17 @@ ChildViewMouseTracker::WindowAcceptsEven
 // triggers bmo bugs 431902 and 476393.  So here we make sure that a
 // ToolbarWindow's contentView always returns NO from the
 // mouseDownCanMoveWindow method.
 - (BOOL)nsChildView_NSView_mouseDownCanMoveWindow
 {
   NSWindow *ourWindow = [self window];
   NSView *contentView = [ourWindow contentView];
   if ([ourWindow isKindOfClass:[ToolbarWindow class]] && (self == contentView))
-    return NO;
+    return [ourWindow isMovableByWindowBackground];
   return [self nsChildView_NSView_mouseDownCanMoveWindow];
 }
 
 @end
 
 #ifdef __LP64__
 // When using blocks, at least on OS X 10.7, the OS sometimes calls
 // +[NSEvent removeMonitor:] more than once on a single event monitor, which
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -13,16 +13,17 @@
 #include "nsBaseWidget.h"
 #include "nsPIWidgetCocoa.h"
 #include "nsAutoPtr.h"
 #include "nsCocoaUtils.h"
 
 class nsCocoaWindow;
 class nsChildView;
 class nsMenuBarX;
+@class ChildView;
 
 // Value copied from BITMAP_MAX_AREA, used in nsNativeThemeCocoa.mm
 #define CUIDRAW_MAX_AREA 500000
 
 // If we are using an SDK older than 10.7, define bits we need that are missing
 // from it.
 #if !defined(MAC_OS_X_VERSION_10_7) || \
     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
@@ -171,26 +172,28 @@ typedef struct _nsCocoaWindowList {
 @end
 
 // NSWindow subclass for handling windows with toolbars.
 @interface ToolbarWindow : BaseWindow
 {
   TitlebarAndBackgroundColor *mColor;
   float mUnifiedToolbarHeight;
   NSColor *mBackgroundColor;
+  NSView *mTitlebarView; // strong
 }
 // Pass nil here to get the default appearance.
 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
 - (void)setUnifiedToolbarHeight:(float)aHeight;
 - (float)unifiedToolbarHeight;
 - (float)titlebarHeight;
 - (NSRect)titlebarRect;
 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync;
 - (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect;
 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
+- (ChildView*)mainChildView;
 @end
 
 class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa
 {
 private:
   
   typedef nsBaseWidget Inherited;
 
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -442,16 +442,20 @@ nsresult nsCocoaWindow::CreateNativeWind
     [mWindow setHasShadow:YES];
   }
 
   [mWindow setBackgroundColor:[NSColor clearColor]];
   [mWindow setOpaque:NO];
   [mWindow setContentMinSize:NSMakeSize(60, 60)];
   [mWindow disableCursorRects];
 
+  // Make sure the window starts out not draggable by the background.
+  // We will turn it on as necessary.
+  [mWindow setMovableByWindowBackground:NO];
+
   [[WindowDataMap sharedWindowDataMap] ensureDataForWindow:mWindow];
   mWindowMadeHere = true;
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
@@ -2578,17 +2582,104 @@ static const NSString* kStateShowsToolba
     retval = [NSArray arrayWithArray:holder];
   }
 
   return retval;
 }
 
 @end
 
-// This class allows us to have a "unified toolbar" style window. It works like this:
+@interface TitlebarMouseHandlingView : NSView<EventRedirection>
+{
+  ToolbarWindow* mWindow; // weak
+  BOOL mProcessingRightMouseDown;
+}
+
+- (id)initWithWindow:(ToolbarWindow*)aWindow;
+@end
+
+@implementation TitlebarMouseHandlingView
+
+- (id)initWithWindow:(ToolbarWindow*)aWindow
+{
+  if ((self = [super initWithFrame:[aWindow titlebarRect]])) {
+    mWindow = aWindow;
+    [self setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
+    [ChildView registerViewForDraggedTypes:self];
+    mProcessingRightMouseDown = NO;
+  }
+  return self;
+}
+
+- (NSView*)targetView
+{
+  return [mWindow mainChildView];
+}
+
+- (BOOL)mouseDownCanMoveWindow
+{
+  return [mWindow isMovableByWindowBackground];
+}
+
+// We redirect many types of events to the window's mainChildView simply by
+// passing the event object to the respective handler method. We don't need any
+// coordinate transformations because event coordinates are relative to the
+// window.
+// We only need to handle event types whose target NSView is determined by the
+// event's position. We don't need to handle key events and NSMouseMoved events
+// because those are only sent to the window's first responder. This view
+// doesn't override acceptsFirstResponder, so it will never receive those kinds
+// of events.
+
+- (void)mouseMoved:(NSEvent*)aEvent            { [[self targetView] mouseMoved:aEvent]; }
+- (void)mouseDown:(NSEvent*)aEvent             { [[self targetView] mouseDown:aEvent]; }
+- (void)mouseUp:(NSEvent*)aEvent               { [[self targetView] mouseUp:aEvent]; }
+- (void)mouseDragged:(NSEvent*)aEvent          { [[self targetView] mouseDragged:aEvent]; }
+- (void)rightMouseDown:(NSEvent*)aEvent
+{
+  // To avoid recursion...
+  if (mProcessingRightMouseDown)
+    return;
+  mProcessingRightMouseDown = YES;
+  [[self targetView] rightMouseDown:aEvent];
+  mProcessingRightMouseDown = NO;
+}
+- (void)rightMouseUp:(NSEvent*)aEvent          { [[self targetView] rightMouseUp:aEvent]; }
+- (void)rightMouseDragged:(NSEvent*)aEvent     { [[self targetView] rightMouseDragged:aEvent]; }
+- (void)otherMouseDown:(NSEvent*)aEvent        { [[self targetView] otherMouseDown:aEvent]; }
+- (void)otherMouseUp:(NSEvent*)aEvent          { [[self targetView] otherMouseUp:aEvent]; }
+- (void)otherMouseDragged:(NSEvent*)aEvent     { [[self targetView] otherMouseDragged:aEvent]; }
+- (void)scrollWheel:(NSEvent*)aEvent           { [[self targetView] scrollWheel:aEvent]; }
+- (void)swipeWithEvent:(NSEvent*)aEvent        { [[self targetView] swipeWithEvent:aEvent]; }
+- (void)beginGestureWithEvent:(NSEvent*)aEvent { [[self targetView] beginGestureWithEvent:aEvent]; }
+- (void)magnifyWithEvent:(NSEvent*)aEvent      { [[self targetView] magnifyWithEvent:aEvent]; }
+- (void)rotateWithEvent:(NSEvent*)aEvent       { [[self targetView] rotateWithEvent:aEvent]; }
+- (void)endGestureWithEvent:(NSEvent*)aEvent   { [[self targetView] endGestureWithEvent:aEvent]; }
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+  { return [[self targetView] draggingEntered:sender]; }
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+  { return [[self targetView] draggingUpdated:sender]; }
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+  { [[self targetView] draggingExited:sender]; }
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+  { return [[self targetView] performDragOperation:sender]; }
+- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+  { [[self targetView] draggedImage:anImage endedAt:aPoint operation:operation]; }
+- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
+  { return [[self targetView] draggingSourceOperationMaskForLocal:isLocal]; }
+- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL*)dropDestination
+  { return [[self targetView] namesOfPromisedFilesDroppedAtDestination:dropDestination]; }
+- (NSMenu*)menuForEvent:(NSEvent*)aEvent
+  { return [[self targetView] menuForEvent:aEvent]; }
+
+@end
+
+// This class allows us to exercise control over the window's title bar. This
+// allows for a "unified toolbar" look, and for extending the content area into
+// the title bar. It works like this:
 // 1) We set the window's style to textured.
 // 2) Because of this, the background color applies to the entire window, including
 //     the titlebar area. For normal textured windows, the default pattern is a 
 //    "brushed metal" image on Tiger and a unified gradient on Leopard.
 // 3) We set the background color to a custom NSColor subclass that knows how tall the window is.
 //    When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback,
 //    it paints the the titlebar and background colors in the correct areas of the context it's given,
 //    which will fill the entire window (CG will tile it horizontally for us).
@@ -2612,16 +2703,21 @@ static const NSString* kStateShowsToolba
 //
 // Whenever the unified gradient is drawn in the titlebar or the toolbar, both
 // titlebar height and toolbar height must be known in order to construct the
 // correct gradient (which is a linear gradient with the length
 // titlebarHeight + toolbarHeight - 1). But you can only get from the toolbar frame
 // to the containing window - the other direction doesn't work. That's why the
 // toolbar height is cached in the ToolbarWindow but nsNativeThemeCocoa can simply
 // query the window for its titlebar height when drawing the toolbar.
+@interface ToolbarWindow(Private)
+- (void)installTitlebarMouseHandlingView;
+- (void)uninstallTitlebarMouseHandlingView;
+@end;
+
 @implementation ToolbarWindow
 
 - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
   aStyle = aStyle | NSTexturedBackgroundWindowMask;
   if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) {
@@ -2646,16 +2742,17 @@ static const NSString* kStateShowsToolba
 }
 
 - (void)dealloc
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [mColor release];
   [mBackgroundColor release];
+  [mTitlebarView release];
   [super dealloc];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive
 {
   [super setTitlebarColor:aColor forActiveWindow:aActive];
@@ -2725,39 +2822,101 @@ static const NSString* kStateShowsToolba
   [self setContentBorderThickness:mUnifiedToolbarHeight forEdge:NSMaxYEdge];
 
   // Redraw the title bar. If we're inside painting, we'll do it right now,
   // otherwise we'll just invalidate it.
   BOOL needSyncRedraw = ([NSView focusView] != nil);
   [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw];
 }
 
+// Extending the content area into the title bar works by redirection of both
+// drawing and mouse events.
+// The window's NSView hierarchy looks like this:
+//  - border view ([[window contentView] superview])
+//     - transparent title bar event redirection view
+//     - window controls (traffic light buttons)
+//     - content view ([window contentView], default NSView provided by the window)
+//        - our main Gecko ChildView ([window mainChildView]), which has an
+//          OpenGL context attached to it when accelerated
+//           - possibly more ChildViews for plugins
+//
+// When the window is in title bar extension mode, the mainChildView covers the
+// whole window but is only visible in the content area of the window, because
+// it's a subview of the window's contentView and thus clipped to its dimensions.
+// This clipping is a good thing because it avoids a few problems. For example,
+// if the mainChildView weren't clipped and thus visible in the titlebar, we'd
+// have have to do the rounded corner masking and the drawing of the highlight
+// line ourselves.
+// This would be especially hard in combination with OpenGL acceleration since
+// rounded corners would require making the OpenGL context transparent, which
+// would bring another set of challenges with it. Having the window controls
+// draw on top of an OpenGL context could be hard, too.
+//
+// So title bar drawing happens in the border view. The border view's drawRect
+// method is not under our control, but we can get it to call into our code
+// using some tricks, see the TitlebarAndBackgroundColor class below.
+// Specifically, we have it call the TitlebarDrawCallback function, which
+// draws the contents of mainChildView into the provided CGContext.
+// (Even if the ChildView uses OpenGL for rendering, drawing in the title bar
+// will happen non-accelerated in that CGContext.)
+//
+// Mouse event redirection happens via a TitlebarMouseHandlingView which we
+// install below.
 - (void)setDrawsContentsIntoWindowFrame:(BOOL)aState
 {
   BOOL stateChanged = ([self drawsContentsIntoWindowFrame] != aState);
   [super setDrawsContentsIntoWindowFrame:aState];
   if (stateChanged && [[self delegate] isKindOfClass:[WindowDelegate class]]) {
+    // Here we extend / shrink our mainChildView. We do that by firing a resize
+    // event which will cause the ChildView to be resized to the rect returned
+    // by nsCocoaWindow::GetClientBounds. GetClientBounds bases its return
+    // value on what we return from drawsContentsIntoWindowFrame.
     WindowDelegate *windowDelegate = (WindowDelegate *)[self delegate];
     nsCocoaWindow *geckoWindow = [windowDelegate geckoWidget];
     if (geckoWindow) {
       // Re-layout our contents.
       geckoWindow->ReportSizeEvent();
     }
 
     // Resizing the content area causes a reflow which would send a synthesized
     // mousemove event to the old mouse position relative to the top left
     // corner of the content area. But the mouse has shifted relative to the
     // content area, so that event would have wrong position information. So
     // we'll send a mouse move event with the correct new position.
     ChildViewMouseTracker::ResendLastMouseMoveEvent();
 
-    [self setTitlebarNeedsDisplayInRect:[self titlebarRect]];
+    if (aState) {
+      [self installTitlebarMouseHandlingView];
+    } else {
+      [self uninstallTitlebarMouseHandlingView];
+    }
   }
 }
 
+- (void)installTitlebarMouseHandlingView
+{
+  mTitlebarView = [[TitlebarMouseHandlingView alloc] initWithWindow:self];
+  [[[self contentView] superview] addSubview:mTitlebarView positioned:NSWindowBelow relativeTo:nil];
+}
+
+- (void)uninstallTitlebarMouseHandlingView
+{
+  [mTitlebarView removeFromSuperview];
+  [mTitlebarView release];
+  mTitlebarView = nil;
+}
+
+- (ChildView*)mainChildView
+{
+  NSView* view = [[[self contentView] subviews] lastObject];
+  if (view && [view isKindOfClass:[ChildView class]])
+    return (ChildView*)view;
+  return nil;
+}
+
 // Returning YES here makes the setShowsToolbarButton method work even though
 // the window doesn't contain an NSToolbar.
 - (BOOL)_hasToolbar
 {
   return YES;
 }
 
 // Dispatch a toolbar pill button clicked message to Gecko.
@@ -2816,16 +2975,19 @@ static const NSString* kStateShowsToolba
     {
       // Drop all mouse events if a modal window has appeared above us.
       // This helps make us behave as if the OS were running a "real" modal
       // event loop.
       id delegate = [self delegate];
       if (delegate && [delegate isKindOfClass:[WindowDelegate class]]) {
         nsCocoaWindow *widget = [(WindowDelegate *)delegate geckoWidget];
         if (widget) {
+          if (type == NSMouseMoved) {
+            [[self mainChildView] updateWindowDraggableStateOnMouseMove:anEvent];
+          }
           if (gGeckoAppModalWindowList && (widget != gGeckoAppModalWindowList->window))
             return;
           if (widget->HasModalDescendents())
             return;
         }
       }
       break;
     }
@@ -2881,29 +3043,29 @@ DrawNativeTitlebar(CGContextRef aContext
 // Pattern draw callback for standard titlebar gradients and solid titlebar colors
 static void
 TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
 {
   ToolbarWindow *window = (ToolbarWindow*)aInfo;
   NSRect titlebarRect = [window titlebarRect];
 
   if ([window drawsContentsIntoWindowFrame]) {
-    NSView* view = [[[window contentView] subviews] lastObject];
-    if (!view || ![view isKindOfClass:[ChildView class]])
+    ChildView* view = [window mainChildView];
+    if (!view)
       return;
 
     // Gecko drawing assumes flippedness, but the current context isn't flipped
     // (because we're painting into the window's border view, which is not a
     // ChildView, so it isn't flipped).
     // So we need to set a flip transform.
     CGContextScaleCTM(aContext, 1.0f, -1.0f);
     CGContextTranslateCTM(aContext, 0.0f, -[window frame].size.height);
 
     NSRect flippedTitlebarRect = { NSZeroPoint, titlebarRect.size };
-    [(ChildView*)view drawRect:flippedTitlebarRect inTitlebarContext:aContext];
+    [view drawRect:flippedTitlebarRect inTitlebarContext:aContext];
   } else {
     BOOL isMain = [window isMainWindow];
     NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];
     if (!titlebarColor) {
       // If the titlebar color is nil, draw the default titlebar shading.
       DrawNativeTitlebar(aContext, NSRectToCGRect(titlebarRect),
                          [window unifiedToolbarHeight], isMain);
     } else {