зеркало из https://github.com/mozilla/gecko-dev.git
Fix several issues with popup windows in Cocoa widgets. b=387164 r=josh+cbarrett sr=pink
This commit is contained in:
Родитель
2111e3c4cb
Коммит
1cb5716262
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче