Fix several issues with popup windows in Cocoa widgets. b=387164 r=josh+cbarrett sr=pink

This commit is contained in:
smichaud@pobox.com 2007-07-17 13:29:39 -07:00
Родитель 2111e3c4cb
Коммит 1cb5716262
8 изменённых файлов: 596 добавлений и 33 удалений

Просмотреть файл

@ -47,6 +47,12 @@
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
#include "nsString.h"
#include "nsIRollupListener.h"
#include "nsIWidget.h"
// defined in nsChildView.mm
extern nsIRollupListener * gRollupListener;
extern nsIWidget * gRollupWidget;
// AppShellDelegate
//
@ -66,6 +72,7 @@
- (void)runAppShell;
- (nsresult)rvFromRun;
- (void)applicationWillTerminate:(NSNotification*)aNotification;
- (void)beginMenuTracking:(NSNotification*)aNotification;
@end
// nsAppShell implementation
@ -412,6 +419,10 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:NSApp];
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:@selector(beginMenuTracking:)
name:@"com.apple.HIToolbox.beginMenuTrackingNotification"
object:nil];
}
return self;
@ -420,6 +431,7 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
@ -464,4 +476,19 @@ nsAppShell::AfterProcessNextEvent(nsIThreadInternal *aThread,
{
mAppShell->WillTerminate();
}
// beginMenuTracking
//
// Roll up our context menu (if any) when some other app (or the OS) opens
// any sort of menu. But make sure we don't do this for notifications we
// send ourselves (whose 'sender' will be @"org.mozilla.gecko.PopupWindow").
- (void)beginMenuTracking:(NSNotification*)aNotification
{
NSString *sender = [aNotification object];
if (!sender || ![sender isEqualToString:@"org.mozilla.gecko.PopupWindow"]) {
if (gRollupListener && gRollupWidget)
gRollupListener->Rollup();
}
}
@end

Просмотреть файл

@ -42,6 +42,7 @@
#include <unistd.h>
#include "nsChildView.h"
#include "nsCocoaWindow.h"
#include "nsCOMPtr.h"
#include "nsToolkit.h"
@ -142,6 +143,12 @@ enum {
- (void)processPendingRedraws;
- (BOOL)maybeRerouteMouseEventToRollupWidget:(NSEvent *)anEvent;
+ (BOOL)mouseEventIsOverRollupWidget:(NSEvent *)anEvent;
- (void)maybeInitContextMenuTracking;
#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)
@ -203,7 +210,7 @@ ConvertGeckoRectToMacRect(const nsRect& aRect, Rect& outMacRect)
static inline void
FlipCocoaScreenCoordinate (NSPoint &inPoint)
{
inPoint.y = HighestPointOnAnyScreen() - inPoint.y;
inPoint.y = CocoaScreenCoordsHeight() - inPoint.y;
}
@ -2385,6 +2392,119 @@ NSEvent* globalDragEvent = nil;
#endif
// If gRollupWidget exists, mouse events (except for mouseDown events) that
// happen over it should be rerouted to it if they've been sent elsewhere.
// Returns 'true' if the event was rerouted, 'false' otherwise. This is
// needed when the user tries to navigate a context menu while keeping the
// mouse-button down (left or right mouse button) -- the OS thinks this is a
// dragging operation, so it sends events (mouseMoved and mouseUp) to the
// window where the dragging operation started (the parent of the context
// menu window). It's also needed to work around a bizarre Apple bug -- if
// (while a context menu is open) you move the mouse over another app's window
// and then back over the context menu, mouseMoved events will be sent to the
// window underneath the context menu.
- (BOOL)maybeRerouteMouseEventToRollupWidget:(NSEvent *)anEvent
{
if (!gRollupWidget)
return PR_FALSE;
// Return PR_FALSE if we can't get ctxMenuWindow or if our window is already
// a context menu.
NSWindow *ctxMenuWindow = (NSWindow*) gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (!ctxMenuWindow || (mWindow == ctxMenuWindow))
return PR_FALSE;
NSPoint windowEventLocation = [anEvent locationInWindow];
NSPoint screenEventLocation = [mWindow convertBaseToScreen:windowEventLocation];
if (!NSPointInRect(screenEventLocation, [ctxMenuWindow frame]))
return PR_FALSE;
NSEventType type = [anEvent type];
NSPoint locationInCtxMenuWindow = [ctxMenuWindow convertScreenToBase:screenEventLocation];
NSEvent *newEvent = nil;
// If anEvent is a mouseUp event, send an extra mouseDown event before
// sending a mouseUp event -- this is needed to support selection by
// dragging the mouse to a menu item and then releasing it. We retain
// ctxMenuWindow in case it gets destroyed as a result of the extra
// mouseDown (and release it below).
if ((type == NSLeftMouseUp) || (type == NSRightMouseUp)) {
[ctxMenuWindow retain];
NSEventType extraEventType;
switch (type) {
case NSLeftMouseUp:
extraEventType = NSLeftMouseDown;
break;
case NSRightMouseUp:
extraEventType = NSRightMouseDown;
break;
default:
extraEventType = (NSEventType) 0;
break;
}
newEvent = [NSEvent mouseEventWithType:extraEventType
location:locationInCtxMenuWindow
modifierFlags:[anEvent modifierFlags]
timestamp:GetCurrentEventTime()
windowNumber:[ctxMenuWindow windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:0.0];
[ctxMenuWindow sendEvent:newEvent];
}
newEvent = [NSEvent mouseEventWithType:type
location:locationInCtxMenuWindow
modifierFlags:[anEvent modifierFlags]
timestamp:GetCurrentEventTime()
windowNumber:[ctxMenuWindow windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:0.0];
[ctxMenuWindow sendEvent:newEvent];
if ((type == NSLeftMouseUp) || (type == NSRightMouseUp))
[ctxMenuWindow release];
return PR_TRUE;
}
+ (BOOL)mouseEventIsOverRollupWidget:(NSEvent *)anEvent
{
if (!gRollupWidget)
return PR_FALSE;
NSWindow *ctxMenuWindow = (NSWindow*) gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (!ctxMenuWindow)
return PR_FALSE;
NSPoint windowEventLocation = [anEvent locationInWindow];
NSPoint screenEventLocation = [[anEvent window] convertBaseToScreen:windowEventLocation];
return NSPointInRect(screenEventLocation, [ctxMenuWindow frame]);
}
// If we've just created a non-native context menu, we need to mark it as
// such and let the OS (and other programs) know when it opens and closes
// (this is how the OS knows to close other programs' context menus when
// ours open). We send the initial notification here, but others are sent
// in nsCocoaWindow::Show().
- (void)maybeInitContextMenuTracking
{
if (!gRollupWidget)
return;
NSWindow *ctxMenuWindow = (NSWindow*)
gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (!ctxMenuWindow || ![ctxMenuWindow isKindOfClass:[PopupWindow class]])
return;
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
object:@"org.mozilla.gecko.PopupWindow"];
[(PopupWindow*) ctxMenuWindow setIsContextMenu:YES];
}
- (void)mouseDown:(NSEvent *)theEvent
{
// Make sure this view is not in the rollup widget. The fastest way to do this
@ -2452,6 +2572,8 @@ NSEvent* globalDragEvent = nil;
geckoCMEvent.nativeMsg = &macEvent;
geckoCMEvent.isControl = ((modifierFlags & NSControlKeyMask) != 0);
mGeckoChild->DispatchMouseEvent(geckoCMEvent);
// Initialize menu tracking if using custom context menus.
[self maybeInitContextMenuTracking];
}
// XXX maybe call markedTextSelectionChanged:client: here?
@ -2466,6 +2588,9 @@ NSEvent* globalDragEvent = nil;
return;
}
if ([self maybeRerouteMouseEventToRollupWidget:theEvent])
return;
if (!mGeckoChild)
return;
@ -2510,8 +2635,30 @@ static nsEventStatus SendMouseEvent(PRBool isTrusted,
NSPoint screenEventLocation = [mWindow convertBaseToScreen:windowEventLocation];
NSPoint viewEventLocation = [self convertPoint:windowEventLocation fromView:nil];
// if this is a popup window and the event is not over it, then we may want to send
// the event to another window
// Installing a mouseMoved handler on the EventMonitor target (in
// nsToolkit::RegisterForAllProcessMouseEvents()) means that some of the
// events received here come from other processes. For this reason we need
// to avoid processing them unless they're over a context menu -- otherwise
// tooltips and other mouse-hover effects will "work" even when our app
// doesn't have the focus.
if (![NSApp isActive] && ![ChildView mouseEventIsOverRollupWidget:theEvent]) {
if (sLastViewEntered) {
nsIWidget* lastViewEnteredWidget = [(NSView<mozView>*)sLastViewEntered widget];
SendMouseEvent(PR_TRUE, NS_MOUSE_EXIT, lastViewEnteredWidget, nsMouseEvent::eReal, &viewEventLocation);
sLastViewEntered = nil;
}
return;
}
if ([self maybeRerouteMouseEventToRollupWidget:theEvent])
return;
// If this is a popup window and the event is not over it, then we may want
// to send the event to another window. (Even with bmo bug 378645 fixed
// ("popup windows still receive native mouse move events after being
// closed"), the following is still needed to deal with mouseMoved events
// that happen over other objects while a context menu is up (and has the
// focus).)
if ([mWindow level] == NSPopUpMenuWindowLevel &&
!NSPointInRect(screenEventLocation, [mWindow frame])) {
NSWindow* otherWindowForEvent = nil;
@ -2628,6 +2775,9 @@ static nsEventStatus SendMouseEvent(PRBool isTrusted,
- (void)mouseDragged:(NSEvent*)theEvent
{
if ([self maybeRerouteMouseEventToRollupWidget:theEvent])
return;
if (!mGeckoChild)
return;
@ -2701,6 +2851,9 @@ static nsEventStatus SendMouseEvent(PRBool isTrusted,
- (void)rightMouseUp:(NSEvent *)theEvent
{
if ([self maybeRerouteMouseEventToRollupWidget:theEvent])
return;
if (!mGeckoChild)
return;
@ -2724,8 +2877,36 @@ static nsEventStatus SendMouseEvent(PRBool isTrusted,
}
- (void)rightMouseDragged:(NSEvent*)theEvent
{
if ([self maybeRerouteMouseEventToRollupWidget:theEvent])
return;
if (!mGeckoChild)
return;
nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_MOVE, nsnull, nsMouseEvent::eReal);
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
geckoEvent.button = nsMouseEvent::eRightButton;
// send event into Gecko by going directly to the
// the widget.
mGeckoChild->DispatchMouseEvent(geckoEvent);
}
- (void)otherMouseDown:(NSEvent *)theEvent
{
// If our view isn't the rollup widget, roll up any context menu that may
// currently be open -- otherwise a disfunctional context menu will appear
// alongside the autoscroll popup (if autoscroll is enabled).
if (gRollupWidget && gRollupListener) {
NSWindow *ourNativeWindow = [self nativeWindow];
NSWindow *rollupNativeWindow = (NSWindow*)gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (ourNativeWindow != rollupNativeWindow)
gRollupListener->Rollup();
}
if (!mGeckoChild)
return;
@ -2751,6 +2932,21 @@ static nsEventStatus SendMouseEvent(PRBool isTrusted,
}
- (void)otherMouseDragged:(NSEvent*)theEvent
{
if (!mGeckoChild)
return;
nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_MOVE, nsnull, nsMouseEvent::eReal);
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
geckoEvent.button = nsMouseEvent::eMiddleButton;
// send event into Gecko by going directly to the
// the widget.
mGeckoChild->DispatchMouseEvent(geckoEvent);
}
// Handle an NSScrollWheel event for a single axis only.
-(void)scrollWheel:(NSEvent*)theEvent forAxis:(enum nsMouseScrollEvent::nsMouseScrollFlags)inAxis
{
@ -2863,6 +3059,9 @@ static nsEventStatus SendMouseEvent(PRBool isTrusted,
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
geckoEvent.button = nsMouseEvent::eRightButton;
mGeckoChild->DispatchMouseEvent(geckoEvent);
// Initialize menu tracking if using custom context menus.
[self maybeInitContextMenuTracking];
// Go up our view chain to fetch the correct menu to return.
return [self contextMenu];

Просмотреть файл

@ -44,8 +44,9 @@
#include "nsRect.h"
// get the highest point on any screen
float HighestPointOnAnyScreen();
// Returns the height (from lowest 'y' to highest 'y', regardless of sign) of
// the global coordinate system that includes all NSScreen objects.
float CocoaScreenCoordsHeight();
/*
* Gecko rects (nsRect) contain an origin (x,y) in a coordinate

Просмотреть файл

@ -39,17 +39,24 @@
#include "nsCocoaUtils.h"
float HighestPointOnAnyScreen()
// Returns the height (from lowest 'y' to highest 'y', regardless of sign) of
// the global coordinate system that includes all NSScreen objects.
float CocoaScreenCoordsHeight()
{
float highestScreenPoint = 0.0;
NSArray* allScreens = [NSScreen screens];
for (unsigned int i = 0; i < [allScreens count]; i++) {
NSRect currScreenFrame = [[allScreens objectAtIndex:i] frame];
float currScreenHighestPoint = currScreenFrame.origin.y + currScreenFrame.size.height;
if (currScreenHighestPoint > highestScreenPoint)
highestScreenPoint = currScreenHighestPoint;
float globalLowestY = 0;
float globalHighestY = 0;
NSArray *allScreens = [NSScreen screens];
for (unsigned i = 0; i < [allScreens count]; ++i) {
NSScreen *aScreen = (NSScreen*) [allScreens objectAtIndex:i];
NSRect screenFrame = [aScreen frame];
float screenLowestY = screenFrame.origin.y;
float screenHighestY = screenFrame.origin.y + screenFrame.size.height;
if (screenLowestY < globalLowestY)
globalLowestY = screenLowestY;
if (screenHighestY > globalHighestY)
globalHighestY = screenHighestY;
}
return highestScreenPoint;
return globalHighestY - globalLowestY;
}
@ -58,7 +65,7 @@ NSRect geckoRectToCocoaRect(const nsRect &geckoRect)
// We only need to change the Y coordinate by starting with the screen
// height, subtracting the gecko Y coordinate, and subtracting the height.
return NSMakeRect(geckoRect.x,
HighestPointOnAnyScreen() - geckoRect.y - geckoRect.height,
CocoaScreenCoordsHeight() - geckoRect.y - geckoRect.height,
geckoRect.width,
geckoRect.height);
}
@ -70,7 +77,7 @@ nsRect cocoaRectToGeckoRect(const NSRect &cocoaRect)
// height and subtracting both the cocoa y origin and the height of the
// cocoa rect.
return nsRect((nscoord)cocoaRect.origin.x,
(nscoord)(HighestPointOnAnyScreen() - (cocoaRect.origin.y + cocoaRect.size.height)),
(nscoord)(CocoaScreenCoordsHeight() - (cocoaRect.origin.y + cocoaRect.size.height)),
(nscoord)cocoaRect.size.width,
(nscoord)cocoaRect.size.height);
}

Просмотреть файл

@ -50,6 +50,43 @@ class nsCocoaWindow;
class nsChildView;
@interface NSApplication (Undocumented)
// It's sometimes necessary to explicitly remove a window from the "window
// cache" in order to deactivate it. The "window cache" is an undocumented
// subsystem, all of whose methods are included in the NSWindowCache category
// of the NSApplication class (in header files generated using class-dump).
- (void)_removeWindowFromCache:(NSWindow *)aWindow;
@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
// original value.
- (void)_setWindowNumber:(int)aNumber;
@end
@interface PopupWindow : NSWindow
{
@private
BOOL mIsContextMenu;
}
- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask
backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation;
- (BOOL)isContextMenu;
- (void)setIsContextMenu:(BOOL)flag;
@end
@interface WindowDelegate : NSObject
{
nsCocoaWindow* mGeckoWindow; // [WEAK] (we are owned by the window)

Просмотреть файл

@ -307,12 +307,17 @@ nsresult nsCocoaWindow::StandardCreate(nsIWidget *aParent,
// NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n",
// rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
// Create the window. If we're a top level window, we want to be able to
// have the toolbar pill button, so use the special ToolbarWindow class.
Class windowClass = (mWindowType == eWindowType_toplevel)
? [ToolbarWindow class] : [NSWindow class];
// Create the window
Class windowClass = [NSWindow class];
// If we're a top level window, we want to be able to have the toolbar
// pill button, so use the special ToolbarWindow class.
if (mWindowType == eWindowType_toplevel)
windowClass = [ToolbarWindow class];
// If we're a popup window we need to use the PopupWindow class.
else if (mWindowType == eWindowType_popup)
windowClass = [PopupWindow class];
mWindow = [[windowClass alloc] initWithContentRect:rect styleMask:features
backing:NSBackingStoreBuffered defer:NO];
backing:NSBackingStoreBuffered defer:NO];
if (mWindowType == eWindowType_popup) {
[mWindow setAlphaValue:0.95];
@ -486,9 +491,27 @@ NS_IMETHODIMP nsCocoaWindow::Show(PRBool bState)
}
else if (mWindowType == eWindowType_popup) {
mVisible = PR_TRUE;
// If a popup window is shown after being hidden, it needs to be "reset"
// for it to receive any mouse events aside from mouse-moved events
// (because it was removed from the "window cache" when it was hidden
// -- see below). Setting the window number to -1 and then back to its
// original value seems to accomplish this. The idea was "borrowed"
// from the Java Embedding Plugin.
int windowNumber = [mWindow windowNumber];
[mWindow _setWindowNumber:-1];
[mWindow _setWindowNumber:windowNumber];
[mWindow setAcceptsMouseMovedEvents:YES];
[mWindow orderFront:nil];
SendSetZLevelEvent();
// If our popup window is a non-native context menu, tell the OS (and
// other programs) that a menu has opened. This is how the OS knows to
// close other programs' context menus when ours open.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*) mWindow isContextMenu]) {
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification"
object:@"org.mozilla.gecko.PopupWindow"];
}
}
else {
mVisible = PR_TRUE;
@ -556,11 +579,29 @@ NS_IMETHODIMP nsCocoaWindow::Show(PRBool bState)
}
else {
[mWindow orderOut:nil];
// Unless it's explicitly removed from NSApp's "window cache", a popup
// window will keep receiving mouse-moved events even after it's been
// "ordered out" (instead of the browser window that was underneath it,
// until you click on that window). This is bmo bug 378645, but it's
// surely an Apple bug. The "window cache" is an undocumented subsystem,
// all of whose methods are included in the NSWindowCache category of
// the NSApplication class (in header files generated using class-dump).
// This workaround was "borrowed" from the Java Embedding Plugin (which
// uses it for a different purpose).
if (mWindowType == eWindowType_popup)
[NSApp _removeWindowFromCache:mWindow];
// it's very important to turn off mouse moved events when hiding a window, otherwise
// the windows' tracking rects will interfere with each other. (bug 356528)
[mWindow setAcceptsMouseMovedEvents:NO];
mVisible = PR_FALSE;
// If our popup window is a non-native context menu, tell the OS (and
// other programs) that a menu has closed.
if ([mWindow isKindOfClass:[PopupWindow class]] &&
[(PopupWindow*) mWindow isContextMenu]) {
[[NSDistributedNotificationCenter defaultCenter]
postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification"
object:@"org.mozilla.gecko.PopupWindow"];
}
}
}
@ -644,7 +685,7 @@ NS_IMETHODIMP nsCocoaWindow::Move(PRInt32 aX, PRInt32 aY)
// the point we have is in Gecko coordinates (origin top-left). Convert
// it to Cocoa ones (origin bottom-left).
NSPoint coord = {aX, HighestPointOnAnyScreen() - aY};
NSPoint coord = {aX, CocoaScreenCoordsHeight() - aY};
//printf("final coords %f %f\n", coord.x, coord.y);
//printf("- window coords before %f %f\n", [mWindow frame].origin.x, [mWindow frame].origin.y);
@ -1270,3 +1311,134 @@ NS_IMETHODIMP nsCocoaWindow::GetAnimatedResize(PRUint16* aAnimation)
}
@end
@implementation PopupWindow
// The OS treats our custom popup windows very strangely -- many mouse events
// sent to them never reach their target NSView objects. (That these windows
// are borderless and of level NSPopUpMenuWindowLevel may have something to do
// with it.) The best solution is to pre-empt the OS, as follows. (All
// events for a given NSWindow object go through its sendEvent: method.)
- (void)sendEvent:(NSEvent *)anEvent
{
NSView *target = nil, *contentView = nil;
NSWindow *window = [anEvent window];
NSEventType type = [anEvent type];
if (window) {
switch (type) {
case NSScrollWheel:
case NSLeftMouseDown:
case NSLeftMouseUp:
case NSRightMouseDown:
case NSRightMouseUp:
case NSOtherMouseDown:
case NSOtherMouseUp:
case NSMouseMoved:
case NSLeftMouseDragged:
case NSRightMouseDragged:
case NSOtherMouseDragged:
if ((contentView = [window contentView]) != nil) {
target = [contentView hitTest:[contentView convertPoint:
[anEvent locationInWindow] fromView:nil]];
}
break;
default:
break;
}
}
if (target) {
switch (type) {
case NSScrollWheel:
[target scrollWheel:anEvent];
break;
case NSLeftMouseDown:
[target mouseDown:anEvent];
// If we're in a context menu we don't want the OS to send the coming
// leftMouseUp event to NSApp via the window server, but we do want
// our ChildView to receive a leftMouseUp event (and to send a Gecko
// NS_MOUSE_BUTTON_UP event to the corresponding nsChildView object).
// If our NSApp isn't active (i.e. if we're in a context menu raised
// by a rightMouseDown event) when it receives the coming leftMouseUp
// via the window server, our browser will (in effect) become partially
// activated, which has strange side effects: For example, if another
// app's window had the focus, that window will lose the focus and the
// other app's main menu will be completely disabled (though it will
// continue to be displayed).
// A side effect of not allowing the coming leftMouseUp event to be
// sent to NSApp via the window server is that our custom context
// menus will roll up whenever the user left-clicks on them, whether
// or not the left-click hit an active menu item. This is how native
// context menus behave, but wasn't how our custom context menus
// behaved previously (on the trunk or e.g. in Firefox 2.0.0.4).
// If our ChildView's corresponding nsChildView object doesn't
// dispatch an NS_MOUSE_BUTTON_UP event, none of our active menu items
// will "work" on a leftMouseDown.
if (mIsContextMenu) {
NSEvent *newEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
location:[anEvent locationInWindow]
modifierFlags:[anEvent modifierFlags]
timestamp:GetCurrentEventTime()
windowNumber:[[anEvent window] windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:0.0];
[target mouseUp:newEvent];
RollUpPopups();
}
break;
case NSLeftMouseUp:
[target mouseUp:anEvent];
break;
case NSRightMouseDown:
[target rightMouseDown:anEvent];
break;
case NSRightMouseUp:
[target rightMouseUp:anEvent];
break;
case NSOtherMouseDown:
[target otherMouseDown:anEvent];
break;
case NSOtherMouseUp:
[target otherMouseUp:anEvent];
break;
case NSMouseMoved:
[target mouseMoved:anEvent];
break;
case NSLeftMouseDragged:
[target mouseDragged:anEvent];
break;
case NSRightMouseDragged:
[target rightMouseDragged:anEvent];
break;
case NSOtherMouseDragged:
[target otherMouseDragged:anEvent];
break;
default:
[super sendEvent:anEvent];
break;
}
} else {
[super sendEvent:anEvent];
}
}
- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)styleMask
backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation
{
mIsContextMenu = false;
return [super initWithContentRect:contentRect styleMask:styleMask
backing:bufferingType defer:deferCreation];
}
- (BOOL)isContextMenu
{
return mIsContextMenu;
}
- (void)setIsContextMenu:(BOOL)flag
{
mIsContextMenu = flag;
}
@end

Просмотреть файл

@ -41,6 +41,7 @@
#include "nsIToolkit.h"
#import <Carbon/Carbon.h>
#import <IOKit/IOKitLib.h>
/**
@ -96,6 +97,7 @@ protected:
void RemoveSleepWakeNotifcations();
void RegisterForAllProcessMouseEvents();
void UnregisterAllProcessMouseEventHandlers();
protected:
@ -103,6 +105,10 @@ protected:
CFRunLoopSourceRef mSleepWakeNotificationRLS;
io_object_t mPowerNotifier;
EventHandlerRef mEventMonitorHandler;
CFMachPortRef mEventTapPort;
CFRunLoopSourceRef mEventTapRLS;
};
extern nsToolkit* NS_CreateToolkitInstance();

Просмотреть файл

@ -52,6 +52,8 @@
#import <IOKit/pwr_mgt/IOPMLib.h>
#import <IOKit/IOMessage.h>
#include "nsCocoaUtils.h"
#include "nsWidgetAtoms.h"
#include "nsIRollupListener.h"
#include "nsIWidget.h"
@ -73,6 +75,9 @@ static PRUintn gToolkitTLSIndex = 0;
nsToolkit::nsToolkit()
: mInited(false)
, mSleepWakeNotificationRLS(nsnull)
, mEventMonitorHandler(nsnull)
, mEventTapPort(nsnull)
, mEventTapRLS(nsnull)
{
}
@ -80,6 +85,7 @@ nsToolkit::nsToolkit()
nsToolkit::~nsToolkit()
{
RemoveSleepWakeNotifcations();
UnregisterAllProcessMouseEventHandlers();
// Remove the TLS reference to the toolkit...
PR_SetThreadPrivate(gToolkitTLSIndex, nsnull);
}
@ -181,23 +187,131 @@ nsToolkit::RemoveSleepWakeNotifcations()
}
static OSStatus AllAppMouseEventHandler(EventHandlerCallRef aCaller, EventRef aEvent, void* aRefcon)
// We shouldn't do anything here. See RegisterForAllProcessMouseEvents() for
// the reason why.
static OSStatus EventMonitorHandler(EventHandlerCallRef aCaller, EventRef aEvent, void* aRefcon)
{
if (![NSApp isActive]) {
if (gRollupListener != nsnull && gRollupWidget != nsnull)
gRollupListener->Rollup();
}
return eventNotHandledErr;
}
return noErr;
// Converts aPoint from the CoreGraphics "global display coordinate" system
// (which includes all displays/screens and has a top-left origin) to its
// (presumed) Cocoa counterpart (assumed to be the same as the "screen
// coordinates" system), which has a bottom-left origin.
static NSPoint ConvertCGGlobalToCocoaScreen(CGPoint aPoint)
{
NSPoint cocoaPoint;
cocoaPoint.x = aPoint.x;
cocoaPoint.y = CocoaScreenCoordsHeight() - aPoint.y;
return cocoaPoint;
}
// Since our event tap is "listen only", events arrive here a little after
// they've already been processed.
static CGEventRef EventTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
if ((type == kCGEventTapDisabledByUserInput) ||
(type == kCGEventTapDisabledByTimeout))
return event;
if (!gRollupWidget || !gRollupListener || [NSApp isActive])
return event;
// Don't bother with rightMouseDown events here -- because of the delay,
// we'll end up closing browser context menus that we just opened. Since
// these events usually raise a context menu, we'll handle them by hooking
// the @"com.apple.HIToolbox.beginMenuTrackingNotification" distributed
// notification (in nsAppShell.mm's AppShellDelegate).
if (type == kCGEventRightMouseDown)
return event;
NSWindow *ctxMenuWindow = (NSWindow*) gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if (!ctxMenuWindow)
return event;
NSPoint screenLocation = ConvertCGGlobalToCocoaScreen(CGEventGetLocation(event));
// Don't roll up the rollup widget if our mouseDown happens over it (doing
// so would break the corresponding context menu).
if (NSPointInRect(screenLocation, [ctxMenuWindow frame]))
return event;
gRollupListener->Rollup();
return event;
}
// Cocoa Firefox's use of custom context menus requires that we explicitly
// handle mouse events from other processes that the OS handles
// "automatically" for native context menus -- mouseMoved events so that
// right-click context menus work properly when our browser doesn't have the
// focus (bmo bug 368077), and mouseDown events so that our browser can
// dismiss a context menu when a mouseDown happens in another process (bmo
// bug 339945).
void
nsToolkit::RegisterForAllProcessMouseEvents()
{
if (!mEventMonitorHandler) {
// Installing a handler for particular Carbon events causes the OS to post
// equivalent Cocoa events to the browser's event stream (the one that
// passes through [NSApp sendEvent:]). For this reason installing a
// handler for kEventMouseMoved fixes bmo bug 368077, even though our
// handler does nothing on mouse-moved events. (Actually it's more
// accurate to say that the OS (working in a different process) sends
// events to the window server, from which the OS (acting in the browser's
// process on its behalf) grabs them and turns them into both Carbon
// events (which get fed to our handler) and Cocoa events (which get fed
// to [NSApp sendEvent:]).)
EventTypeSpec kEvents[] = {{kEventClassMouse, kEventMouseMoved}};
InstallEventHandler(GetEventMonitorTarget(), EventMonitorHandler,
GetEventTypeCount(kEvents), kEvents, 0,
&mEventMonitorHandler);
}
if (!mEventTapRLS) {
// Using an event tap for mouseDown events (instead of installing a
// handler for them on the EventMonitor target) works around an Apple
// bug that causes OS menus (like the Clock menu) not to work properly
// on OS X 10.4.X and below (bmo bug 381448).
// We install our event tap "listen only" to get around yet another Apple
// bug -- when we install it as an event filter on any kind of mouseDown
// event, that kind of event stops working in the main menu, and usually
// mouse event processing stops working in all apps in the current login
// session (so the entire OS appears to be hung)! The downside of
// installing listen-only is that events arrive at our handler slightly
// after they've already been processed.
mEventTapPort = CGEventTapCreate(kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionListenOnly,
CGEventMaskBit(kCGEventLeftMouseDown)
| CGEventMaskBit(kCGEventRightMouseDown)
| CGEventMaskBit(kCGEventOtherMouseDown),
EventTapCallback,
nsnull);
if (!mEventTapPort)
return;
mEventTapRLS = CFMachPortCreateRunLoopSource(nsnull, mEventTapPort, 0);
if (!mEventTapRLS) {
CFRelease(mEventTapPort);
mEventTapPort = nsnull;
return;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), mEventTapRLS, kCFRunLoopDefaultMode);
}
}
void
nsToolkit::RegisterForAllProcessMouseEvents()
nsToolkit::UnregisterAllProcessMouseEventHandlers()
{
EventTypeSpec kEvents[] = {{kEventClassMouse, kEventMouseDown}};
InstallEventHandler(GetEventMonitorTarget(), AllAppMouseEventHandler, GetEventTypeCount(kEvents), kEvents, 0, NULL);
if (mEventMonitorHandler) {
RemoveEventHandler(mEventMonitorHandler);
mEventMonitorHandler = nsnull;
}
if (mEventTapRLS) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), mEventTapRLS,
kCFRunLoopDefaultMode);
CFRelease(mEventTapRLS);
mEventTapRLS = nsnull;
}
if (mEventTapPort) {
CFRelease(mEventTapPort);
mEventTapPort = nsnull;
}
}