Bug 647216: Allow mouse event handling in the title bar on OS X. A number of contributors to this patch, including Markus Stange, Paul O'Shannessy, Mike Conley, Stephen Pohl, and myself (Josh Aas). r=mstange,dao,enndeakin

This commit is contained in:
Josh Aas 2013-02-04 09:00:42 -06:00
parent dd7a85d6a7
commit 41e8b6b37d
15 changed files with 293 additions and 69 deletions

View File

@ -36,7 +36,7 @@ ifneq (,$(filter windows cocoa gtk2, $(MOZ_WIDGET_TOOLKIT)))
DEFINES += -DCONTEXT_COPY_IMAGE_CONTENTS=1
endif
ifneq (,$(filter windows, $(MOZ_WIDGET_TOOLKIT)))
ifneq (,$(filter windows cocoa, $(MOZ_WIDGET_TOOLKIT)))
DEFINES += -DCAN_DRAW_IN_TITLEBAR=1
endif

View File

@ -489,6 +489,7 @@
#ifdef CAN_DRAW_IN_TITLEBAR
<vbox id="titlebar">
<hbox id="titlebar-content">
#ifdef MENUBAR_CAN_AUTOHIDE
<hbox id="appmenu-button-container">
<button id="appmenu-button"
type="menu"
@ -497,6 +498,7 @@
#include browser-appmenu.inc
</button>
</hbox>
#endif
<spacer id="titlebar-spacer" flex="1"/>
<hbox id="titlebar-buttonbox-container" align="start">
<hbox id="titlebar-buttonbox">

View File

@ -38,6 +38,15 @@
background-color: #eeeeee;
}
#titlebar-buttonbox-container,
#titlebar:not(:-moz-lwtheme) {
display: none;
}
#main-window[drawintitlebar="true"] > #titlebar {
height: 22px;
}
#main-window[chromehidden~="toolbar"][chromehidden~="location"][chromehidden~="directories"] {
border-top: 1px solid rgba(0,0,0,0.65);
}

View File

@ -560,11 +560,12 @@ nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
int32_t aModifiers,
bool aIgnoreRootScrollFrame,
float aPressure,
unsigned short aInputSourceArg)
unsigned short aInputSourceArg,
bool *aPreventDefault)
{
return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
aIgnoreRootScrollFrame, aPressure,
aInputSourceArg, false);
aInputSourceArg, false, aPreventDefault);
}
NS_IMETHODIMP
@ -581,7 +582,7 @@ nsDOMWindowUtils::SendMouseEventToWindow(const nsAString& aType,
SAMPLE_LABEL("nsDOMWindowUtils", "SendMouseEventToWindow");
return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
aIgnoreRootScrollFrame, aPressure,
aInputSourceArg, true);
aInputSourceArg, true, nullptr);
}
static nsIntPoint
@ -604,7 +605,8 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType,
bool aIgnoreRootScrollFrame,
float aPressure,
unsigned short aInputSourceArg,
bool aToWindow)
bool aToWindow,
bool *aPreventDefault)
{
if (!nsContentUtils::IsCallerChrome()) {
return NS_ERROR_DOM_SECURITY_ERR;
@ -631,7 +633,9 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType,
else if (aType.EqualsLiteral("contextmenu")) {
msg = NS_CONTEXTMENU;
contextMenuKey = (aButton == 0);
} else
} else if (aType.EqualsLiteral("MozMouseHittest"))
msg = NS_MOUSE_MOZHITTEST;
else
return NS_ERROR_FAILURE;
if (aInputSourceArg == nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN) {
@ -672,7 +676,10 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType,
status = nsEventStatus_eIgnore;
return presShell->HandleEvent(view->GetFrame(), &event, false, &status);
}
return widget->DispatchEvent(&event, status);
nsresult rv = widget->DispatchEvent(&event, status);
*aPreventDefault = (status == nsEventStatus_eConsumeNoDefault);
return rv;
}
NS_IMETHODIMP

View File

@ -47,7 +47,8 @@ protected:
bool aIgnoreRootScrollFrame,
float aPressure,
unsigned short aInputSourceArg,
bool aToWindow);
bool aToWindow,
bool *aPreventDefault);
static mozilla::widget::Modifiers GetWidgetModifiers(int32_t aModifiers);
};

View File

@ -41,7 +41,7 @@ interface nsIDOMClientRect;
interface nsIURI;
interface nsIDOMEventTarget;
[scriptable, uuid(458f5b08-4966-4a91-8617-258afb87070e)]
[scriptable, uuid(020deb5a-cba6-41dd-8551-72a880d01970)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -213,7 +213,8 @@ interface nsIDOMWindowUtils : nsISupports {
const long MODIFIER_OS = 0x0400;
/** Synthesize a mouse event. The event types supported are:
* mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu
* mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu,
* MozMouseHitTest
*
* Events are sent in coordinates offset by aX and aY from the window.
*
@ -244,16 +245,18 @@ interface nsIDOMWindowUtils : nsISupports {
* @param aPressure touch input pressure: 0.0 -> 1.0
* @param aInputSourceArg input source, see nsIDOMMouseEvent for values,
* defaults to mouse input.
*
* returns true if the page called prevent default on this event
*/
void sendMouseEvent(in AString aType,
in float aX,
in float aY,
in long aButton,
in long aClickCount,
in long aModifiers,
[optional] in boolean aIgnoreRootScrollFrame,
[optional] in float aPressure,
[optional] in unsigned short aInputSourceArg);
boolean sendMouseEvent(in AString aType,
in float aX,
in float aY,
in long aButton,
in long aClickCount,
in long aModifiers,
[optional] in boolean aIgnoreRootScrollFrame,
[optional] in float aPressure,
[optional] in unsigned short aInputSourceArg);
/** Synthesize a touch event. The event types supported are:
* touchstart, touchend, touchmove, and touchcancel

View File

@ -1345,8 +1345,9 @@ TabChild::RecvMouseEvent(const nsString& aType,
{
nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
NS_ENSURE_TRUE(utils, true);
bool ignored = false;
utils->SendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers,
aIgnoreRootScrollFrame, 0, 0);
aIgnoreRootScrollFrame, 0, 0, &ignored);
return true;
}

View File

@ -206,11 +206,13 @@ function _parseModifiers(aEvent)
* a mousedown followed by a mouse up is performed.
*
* aWindow is optional, and defaults to the current window object.
*
* Returns whether the event had preventDefault() called on it.
*/
function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
var rect = aTarget.getBoundingClientRect();
synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
aEvent, aWindow);
}
function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
@ -234,6 +236,7 @@ function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
var defaultPrevented = false;
if (utils) {
var button = aEvent.button || 0;
@ -241,12 +244,17 @@ function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
var modifiers = _parseModifiers(aEvent);
var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
var types = (("type" in aEvent) && aEvent.type) ? [aEvent.type] : ["mousedown", "mouseup"];
types.forEach(function(type) {
utils.sendMouseEvent(type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
});
if (("type" in aEvent) && aEvent.type) {
defaultPrevented = utils.sendMouseEvent(type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
}
else {
utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
}
}
return defaultPrevented;
}
function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
{

View File

@ -2,12 +2,18 @@
* 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
this.EXPORTED_SYMBOLS = [ "WindowDraggingElement" ];
this.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
@ -54,7 +60,7 @@ WindowDraggingElement.prototype = {
},
handleEvent: function(aEvent) {
let isPanel = this.isPanel();
#ifdef XP_WIN
#ifdef USE_HITTEST
if (!isPanel) {
if (this.shouldDrag(aEvent))
aEvent.preventDefault();

View File

@ -52,7 +52,6 @@
<statusbar id="statusbar">
<statusbarpanel>
<label id="statuslabel" value="Status"/>
<label id="statuslabelnodrag" value="No Drag" onmousedown="event.preventDefault()"/>
</statusbarpanel>
</statusbar>
@ -106,26 +105,18 @@ function test_titlebar()
var titlebar = document.getElementById("titlebar");
var label = document.getElementById("label");
// on Mac, the window can also be moved with the statusbar
// On Mac, the window can also be moved with the statusbar, but this works
// via the MozMouseHittest event, not via mouse events.
if (navigator.platform.indexOf("Mac") >= 0) {
var preventDefaulted;
var statuslabel = document.getElementById("statuslabel");
var statuslabelnodrag = document.getElementById("statuslabelnodrag");
preventDefaulted = synthesizeMouse(statuslabel, 2, 2, { type: "MozMouseHittest" });
SimpleTest.ok(preventDefaulted, "MozMouseHittest should have been defaultPrevented over statusbar");
origoldx = window.screenX;
origoldy = window.screenY;
synthesizeMouse(statuslabel, 2, 2, { type: "mousedown" });
synthesizeMouse(statuslabel, 22, 22, { type: "mousemove" });
SimpleTest.is(window.screenX, origoldx + 20, "move window with statusbar horizontal");
SimpleTest.is(window.screenY, origoldy + 20, "move window with statusbar vertical");
synthesizeMouse(statuslabel, 22, 22, { type: "mouseup" });
// event was cancelled so the drag should not have occurred
synthesizeMouse(statuslabelnodrag, 2, 2, { type: "mousedown" });
synthesizeMouse(statuslabelnodrag, 22, 22, { type: "mousemove" });
SimpleTest.is(window.screenX, origoldx + 20, "move window with statusbar cancelled mousedown horizontal");
SimpleTest.is(window.screenY, origoldy + 20, "move window with statusbar cancelled mousedown vertical");
synthesizeMouse(statuslabelnodrag, 22, 22, { type: "mouseup" });
var button = document.getElementById("button");
preventDefaulted = synthesizeMouse(button, 2, 2, { type: "MozMouseHittest" });
SimpleTest.ok(!preventDefaulted, "MozMouseHittest should NOT have been defaultPrevented over button");
}
origoldx = window.screenX;

View File

@ -26,12 +26,6 @@
-moz-binding: url("chrome://global/content/bindings/general.xml#root-element");
}
%ifdef XP_MACOSX
:root[drawintitlebar="true"] {
padding-top: 22px;
}
%endif
:root:-moz-locale-dir(rtl) {
direction: rtl;
}

View File

@ -184,6 +184,10 @@ typedef NSInteger NSEventGestureAxis;
- (NSEventPhase)momentumPhase;
@end
@protocol EventRedirection
- (NSView*)targetView;
@end
@interface ChildView : NSView<
#ifdef ACCESSIBILITY
mozAccessible,
@ -268,6 +272,8 @@ typedef NSInteger NSEventGestureAxis;
// 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;
@ -279,6 +285,8 @@ typedef NSInteger NSEventGestureAxis;
- (void)handleMouseMoved:(NSEvent*)aEvent;
- (void)updateWindowDraggableStateOnMouseMove:(NSEvent*)theEvent;
- (void)drawRect:(NSRect)aRect inTitlebarContext:(CGContextRef)aContext;
- (void)drawTitlebar:(NSRect)aRect inTitlebarContext:(CGContextRef)aContext;

View File

@ -1990,6 +1990,20 @@ NSEvent* gLastDragMouseDownEvent = nil;
}
}
+ (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
{
@ -2037,17 +2051,8 @@ NSEvent* gLastDragMouseDownEvent = nil;
}
// 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
@ -2417,7 +2422,7 @@ NSEvent* gLastDragMouseDownEvent = nil;
- (BOOL)mouseDownCanMoveWindow
{
return NO;
return [[self window] isMovableByWindowBackground];
}
- (void)lockFocus
@ -3388,6 +3393,25 @@ NSEvent* gLastDragMouseDownEvent = nil;
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;
@ -4911,6 +4935,11 @@ ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
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;
@ -5009,7 +5038,7 @@ ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent,
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];
}

View File

@ -18,6 +18,7 @@
class nsCocoaWindow;
class nsChildView;
class nsMenuBarX;
@class ChildView;
// Value copied from BITMAP_MAX_AREA, used in nsNativeThemeCocoa.mm
#define CUIDRAW_MAX_AREA 500000
@ -176,6 +177,7 @@ typedef struct _nsCocoaWindowList {
TitlebarAndBackgroundColor *mColor;
float mUnifiedToolbarHeight;
NSColor *mBackgroundColor;
NSView *mTitlebarView; // strong
}
// Pass nil here to get the default appearance.
- (void)setTitlebarColor:(NSColor*)aColor forActiveWindow:(BOOL)aActive;
@ -186,6 +188,7 @@ typedef struct _nsCocoaWindowList {
- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect sync:(BOOL)aSync;
- (void)setTitlebarNeedsDisplayInRect:(NSRect)aRect;
- (void)setDrawsContentsIntoWindowFrame:(BOOL)aState;
- (ChildView*)mainChildView;
@end
class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa

View File

@ -462,6 +462,10 @@ nsresult nsCocoaWindow::CreateNativeWindow(const NSRect &aRect,
[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;
@ -2657,7 +2661,94 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
@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
@ -2691,6 +2782,11 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
// 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
@ -2725,6 +2821,7 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
[mColor release];
[mBackgroundColor release];
[mTitlebarView release];
[super dealloc];
NS_OBJC_END_TRY_ABORT_BLOCK;
@ -2804,11 +2901,48 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
[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) {
@ -2823,10 +2957,35 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
// 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
@ -2895,6 +3054,9 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton";
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())
@ -2960,13 +3122,13 @@ TitlebarDrawCallback(void* aInfo, CGContextRef aContext)
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;
CGContextTranslateCTM(aContext, 0.0f, [window frame].size.height - titlebarRect.size.height);
[(ChildView*)view drawTitlebar:[window frame] inTitlebarContext:aContext];
[view drawTitlebar:[window frame] inTitlebarContext:aContext];
} else {
BOOL isMain = [window isMainWindow];
NSColor *titlebarColor = [window titlebarColorForActiveWindow:isMain];