Bug 515003 - Rewrite native mouse event handling. Also add tests for native mouse events (bug 470845). r=josh, sr=roc

This commit is contained in:
Markus Stange 2009-09-23 14:31:37 +12:00
Родитель d234cc450e
Коммит c2055691da
14 изменённых файлов: 842 добавлений и 352 удалений

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

@ -391,6 +391,27 @@ nsDOMWindowUtils::SendNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
aModifiers, aCharacters, aUnmodifiedCharacters);
}
NS_IMETHODIMP
nsDOMWindowUtils::SendNativeMouseEvent(PRInt32 aScreenX,
PRInt32 aScreenY,
PRInt32 aNativeMessage,
PRInt32 aModifierFlags,
nsIDOMElement* aElement)
{
PRBool hasCap = PR_FALSE;
if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled("UniversalXPConnect", &hasCap))
|| !hasCap)
return NS_ERROR_DOM_SECURITY_ERR;
// get the widget to send the event to
nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
if (!widget)
return NS_ERROR_FAILURE;
return widget->SynthesizeNativeMouseEvent(nsIntPoint(aScreenX, aScreenY),
aNativeMessage, aModifierFlags);
}
NS_IMETHODIMP
nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
{
@ -442,6 +463,28 @@ nsDOMWindowUtils::GetWidget(nsPoint* aOffset)
return nsnull;
}
nsIWidget*
nsDOMWindowUtils::GetWidgetForElement(nsIDOMElement* aElement)
{
if (!aElement)
return GetWidget();
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
nsIDocument* doc = content->GetCurrentDoc();
nsIPresShell* presShell = doc ? doc->GetPrimaryShell() : nsnull;
if (presShell) {
nsIFrame* frame = presShell->GetPrimaryFrameFor(content);
if (!frame) {
frame = presShell->GetRootFrame();
}
if (frame)
return frame->GetWindow();
}
return nsnull;
}
NS_IMETHODIMP
nsDOMWindowUtils::Focus(nsIDOMElement* aElement)
{

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

@ -57,6 +57,7 @@ protected:
// If aOffset is non-null, it gets filled in with an offset, in app
// units, that should be added to any event offset we're given.
nsIWidget* GetWidget(nsPoint* aOffset = nsnull);
nsIWidget* GetWidgetForElement(nsIDOMElement* aElement);
nsPresContext* GetPresContext();
};

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

@ -48,7 +48,7 @@
interface nsIDOMElement;
interface nsIDOMHTMLCanvasElement;
[scriptable, uuid(99d904c0-3b9e-44b7-b1e0-372766d18308)]
[scriptable, uuid(c1b779af-7297-4e2e-9fc4-a6f22038770f)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -198,6 +198,20 @@ interface nsIDOMWindowUtils : nsISupports {
in AString aCharacters,
in AString aUnmodifiedCharacters);
/**
* See nsIWidget::SynthesizeNativeMouseEvent
*
* Will be called on the widget that contains aElement.
* Cannot be accessed from unprivileged context (not content-accessible)
* Will throw a DOM security error if called without UniversalXPConnect
* privileges.
*/
void sendNativeMouseEvent(in long aScreenX,
in long aScreenY,
in long aNativeMessage,
in long aModifierFlags,
in nsIDOMElement aElement);
/**
* See nsIWidget::ActivateNativeMenuItemAt
*

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

@ -102,8 +102,8 @@ typedef nsEventStatus (* EVENT_CALLBACK)(nsGUIEvent *event);
#endif
#define NS_IWIDGET_IID \
{ 0xb681539f, 0x5dac, 0x45af, \
{ 0x8a, 0x25, 0xdf, 0xd7, 0x14, 0xe0, 0x9f, 0x43 } }
{ 0x5c55f106, 0xb7ab, 0x4f54, \
{ 0x89, 0xf3, 0xd3, 0xcf, 0x91, 0xf9, 0x63, 0x95 } }
/*
* Window shadow styles
@ -930,6 +930,18 @@ class nsIWidget : public nsISupports {
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters) = 0;
/**
* Utility method intended for testing. Dispatches native mouse events
* to this widget and may even move the mouse cursor.
* @param aPoint screen location of the mouse, in pixels, with origin at
* the top left
* @param aNativeMessage *platform-specific* event type (e.g. NSMouseMoved)
* @param aModifierFlags *platform-specific* modifier flags
*/
virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
PRUint32 aNativeMessage,
PRUint32 aModifierFlags) = 0;
/**
* Activates a native menu item at the position specified by the index
* string. The index string is a string of positive integers separated

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

@ -209,6 +209,12 @@ enum {
- (void)sendFocusEvent:(PRUint32)eventType;
- (void)handleMouseMoved:(NSEvent*)aEvent;
- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
enter:(BOOL)aEnter
type:(nsMouseEvent::exitType)aType;
- (void) processPluginKeyEvent:(EventRef)aKeyEvent;
// Simple gestures support
@ -271,6 +277,22 @@ private:
static void KillComposing();
};
class ChildViewMouseTracker {
public:
static void MouseMoved(NSEvent* aEvent);
static void OnDestroyView(ChildView* aView);
static BOOL WindowAcceptsEvent(NSWindow* aWindow, NSEvent* aEvent);
static ChildView* sLastMouseEventView;
private:
static NSWindow* WindowForEvent(NSEvent* aEvent);
static ChildView* ViewForEvent(NSEvent* aEvent);
};
//-------------------------------------------------------------------------
//
// nsChildView
@ -386,6 +408,16 @@ public:
virtual nsTransparencyMode GetTransparencyMode();
virtual void SetTransparencyMode(nsTransparencyMode aMode);
virtual nsresult SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters);
virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
PRUint32 aNativeMessage,
PRUint32 aModifierFlags);
// Mac specific methods
virtual PRBool DispatchWindowEvent(nsGUIEvent& event);
@ -424,12 +456,6 @@ protected:
void TearDownView();
nsCocoaWindow* GetXULWindowWidget();
virtual nsresult SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
PRInt32 aNativeKeyCode,
PRUint32 aModifierFlags,
const nsAString& aCharacters,
const nsAString& aUnmodifiedCharacters);
protected:
NSView<mozView>* mView; // my parallel cocoa view (ChildView or NativeScrollbarView), [STRONG]

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

@ -300,9 +300,9 @@ PRBool nsTSMManager::sIgnoreCommit = PR_FALSE;
NSView<mozView>* nsTSMManager::sComposingView = nsnull;
TSMDocumentID nsTSMManager::sDocumentID = nsnull;
NSString* nsTSMManager::sComposingString = nsnull;
ChildView* ChildViewMouseTracker::sLastMouseEventView = nil;
static NS_DEFINE_CID(kRegionCID, NS_REGION_CID);
static NSView* sLastViewEntered = nil;
#ifdef INVALIDATE_DEBUGGING
static void blinkRect(Rect* r);
static void blinkRgn(RgnHandle rgn);
@ -354,8 +354,6 @@ PRUint32 nsChildView::sLastInputEventCount = 0;
- (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv;
- (BOOL)ensureCorrectMouseEventTarget:(NSEvent *)anEvent;
- (void)maybeInitContextMenuTracking;
+ (NSEvent*)makeNewCocoaEventWithType:(NSEventType)type fromEvent:(NSEvent*)theEvent;
@ -1684,6 +1682,40 @@ nsresult nsChildView::SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
nsresult nsChildView::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
PRUint32 aNativeMessage,
PRUint32 aModifierFlags)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
// Move the mouse cursor to the requested position and reconnect it to the mouse.
CGWarpMouseCursorPosition(CGPointMake(aPoint.x, aPoint.y));
CGAssociateMouseAndMouseCursorPosition(true);
// aPoint is given with the origin on the top left, but convertScreenToBase
// expects a point in a coordinate system that has its origin on the bottom left.
NSPoint screenPoint = NSMakePoint(aPoint.x, [[NSScreen mainScreen] frame].size.height - aPoint.y);
NSPoint windowPoint = [[mView window] convertScreenToBase:screenPoint];
NSEvent* event = [NSEvent mouseEventWithType:aNativeMessage
location:windowPoint
modifierFlags:aModifierFlags
timestamp:[NSDate timeIntervalSinceReferenceDate]
windowNumber:[[mView window] windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:0.0];
if (!event)
return NS_ERROR_FAILURE;
[NSApp sendEvent:event];
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
// First argument has to be an NSMenu representing the application's top-level
// menu bar. The returned item is *not* retained.
static NSMenuItem* NativeMenuItemWithLocation(NSMenu* menubar, NSString* locationString)
@ -2394,9 +2426,6 @@ NSEvent* gLastDragEvent = nil;
if (mPluginTSMDoc)
::DeleteTSMDocument(mPluginTSMDoc);
if (sLastViewEntered == self)
sLastViewEntered = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
@ -2414,6 +2443,7 @@ NSEvent* gLastDragEvent = nil;
- (void)widgetDestroyed
{
nsTSMManager::OnDestroyView(self);
ChildViewMouseTracker::OnDestroyView(self);
mGeckoChild = nsnull;
// Just in case we're destroyed abruptly and missed the draggingExited
@ -2798,85 +2828,6 @@ static const PRInt32 sShadowInvalidationInterval = 100;
}
#endif
// We sometimes need to reroute events when there is a rollup widget and the
// event isn't targeted at it.
//
// Rerouting may be 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 also works 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)ensureCorrectMouseEventTarget:(NSEvent*)anEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
// Don't bother if we've been destroyed: [self window] will now be nil, which
// makes all our work here pointless, and can even cause us to resend the
// event to ourselves in an infinte loop (since targetWindow == [self window] no
// longer tests whether targetWindow is us).
if (!mGeckoChild || ![self window])
return YES;
// Find the window that the event is over.
BOOL isUnderMouse;
NSWindow* targetWindow = nsCocoaUtils::FindWindowForEvent(anEvent, &isUnderMouse);
// If this is the rollup widget and the event is not a mouse move then trust the OS routing.
// The reason for this trust is complicated.
//
// There are three types of mouse events that can legitimately need to be targeted at a window
// that they are not over. Mouse moves, mouse drags, and mouse ups. Anything else our app wouldn't
// handle (if the mouse was not over any window) or it would go to the appropriate window.
//
// We need to do manual event rerouting for mouse moves because we know that in some cases, like
// when there is a submenu opened from a popup window, the OS will route mouse move events to the
// submenu even if the mouse is over the parent. Mouse move events are never tied to a particular
// window because of some originating action like the starting point of a drag for drag events or
// a mouse down event for mouse up events, so it is always safe to do our own routing on them here.
//
// As for mouse drags and mouse ups, they have originating actions that tie them to windows they
// may no longer be over. If there is a rollup window present when one of these events is getting
// processed but we are not it, we are probably the window where the action originated, and that
// action must have caused the rollup window to come into existence. In that case, we might need
// to reroute the event if it is over the rollup window. That is why if we're not the rollup window
// we don't return YES here.
if (gRollupWidget) {
NSWindow* rollupWindow = (NSWindow*)gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
if ([self window] == rollupWindow && [anEvent type] != NSMouseMoved)
return YES;
// If the event was not over any window, send it to the rollup window.
if (!isUnderMouse)
targetWindow = rollupWindow;
}
// If there's no window that's more appropriate than our window then just return
// yes so we handle it. No need to redirect.
if (!targetWindow || targetWindow == [self window])
return YES;
// Send the event to its new destination.
NSPoint newWindowLocation = nsCocoaUtils::EventLocationForWindow(anEvent, targetWindow);
NSEvent *newEvent = [NSEvent mouseEventWithType:[anEvent type]
location:newWindowLocation
modifierFlags:nsCocoaUtils::GetCocoaEventModifierFlags(anEvent)
timestamp:GetCurrentEventTime()
windowNumber:[targetWindow windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:0.0];
[targetWindow sendEvent:newEvent];
// Return NO because we just sent the event somewhere else.
return NO;
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
}
// 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
@ -3190,12 +3141,10 @@ static const PRInt32 sShadowInvalidationInterval = 100;
mLastMouseDownEvent = [theEvent retain];
}
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
nsAutoRetainCocoaObject kungFuDeathGrip(self);
if ([self maybeRollup:theEvent])
if ([self maybeRollup:theEvent] ||
!ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent))
return;
#if USE_CLICK_HOLD_CONTEXTMENU
@ -3258,8 +3207,6 @@ static const PRInt32 sShadowInvalidationInterval = 100;
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
if (!mGeckoChild)
return;
@ -3306,39 +3253,27 @@ static const PRInt32 sShadowInvalidationInterval = 100;
NS_OBJC_END_TRY_ABORT_BLOCK;
}
// sends a mouse enter or exit event into gecko
static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
PRUint32 msg,
nsIWidget *widget,
nsMouseEvent::reasonType aReason,
NSPoint* localEventLocation,
nsMouseEvent::exitType type,
unsigned int modifierFlags,
int buttonNumber,
float deltaX,
float deltaY,
float deltaZ)
- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
enter:(BOOL)aEnter
type:(nsMouseEvent::exitType)aType
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
if (!mGeckoChild)
return;
if (!widget || !localEventLocation)
return nsEventStatus_eIgnore;
NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil];
nsMouseEvent event(isTrusted, msg, widget, aReason);
event.refPoint.x = nscoord((PRInt32)localEventLocation->x);
event.refPoint.y = nscoord((PRInt32)localEventLocation->y);
PRUint32 msg = aEnter ? NS_MOUSE_ENTER : NS_MOUSE_EXIT;
nsMouseEvent event(PR_TRUE, msg, mGeckoChild, nsMouseEvent::eReal);
event.refPoint.x = nscoord((PRInt32)localEventLocation.x);
event.refPoint.y = nscoord((PRInt32)localEventLocation.y);
// Create event for use by plugins.
// We need to know the plugin event model for the target widget.
nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(widget);
if (!pluginWidget)
return nsEventStatus_eIgnore;
int eventModel;
pluginWidget->GetPluginEventModel(&eventModel);
// This is going to our child view so we don't need to look up the destination
// event type.
#ifndef NP_NO_CARBON
EventRecord carbonEvent;
if (static_cast<NPEventModel>(eventModel) == NPEventModelCarbon) {
if (mPluginEventModel == NPEventModelCarbon) {
carbonEvent.what = NPEventType_AdjustCursorEvent;
carbonEvent.message = 0;
carbonEvent.when = ::TickCount();
@ -3348,142 +3283,37 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
}
#endif
NPCocoaEvent cocoaEvent;
if (static_cast<NPEventModel>(eventModel) == NPEventModelCocoa) {
if (mPluginEventModel == NPEventModelCocoa) {
InitNPCocoaEvent(&cocoaEvent);
cocoaEvent.type = ((msg == NS_MOUSE_ENTER) ? NPCocoaEventMouseEntered : NPCocoaEventMouseExited);
cocoaEvent.data.mouse.modifierFlags = modifierFlags;
cocoaEvent.data.mouse.modifierFlags = [aEvent modifierFlags];
cocoaEvent.data.mouse.pluginX = 5;
cocoaEvent.data.mouse.pluginY = 5;
cocoaEvent.data.mouse.buttonNumber = buttonNumber;
cocoaEvent.data.mouse.deltaX = deltaX;
cocoaEvent.data.mouse.deltaY = deltaY;
cocoaEvent.data.mouse.deltaZ = deltaZ;
cocoaEvent.data.mouse.buttonNumber = [aEvent buttonNumber];
cocoaEvent.data.mouse.deltaX = [aEvent deltaX];
cocoaEvent.data.mouse.deltaY = [aEvent deltaY];
cocoaEvent.data.mouse.deltaZ = [aEvent deltaZ];
event.nativeMsg = &cocoaEvent;
}
event.exit = type;
event.exit = aType;
nsEventStatus status;
widget->DispatchEvent(&event, status);
// After the cursor exits the window set it to a visible regular arrow cursor.
// This lets us recover from plugins that mess with it.
if (msg == NS_MOUSE_EXIT && type == nsMouseEvent::eTopLevel) {
[[nsCursorManager sharedInstance] setCursor:eCursor_standard];
}
return status;
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsEventStatus_eIgnore);
nsEventStatus status; // ignored
mGeckoChild->DispatchEvent(&event, status);
}
- (void)mouseMoved:(NSEvent*)theEvent
- (void)mouseMoved:(NSEvent*)aEvent
{
ChildViewMouseTracker::MouseMoved(aEvent);
}
- (void)handleMouseMoved:(NSEvent*)theEvent
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (![self window])
return;
// Work around an Apple bug that causes the OS to continue sending
// mouseMoved events to a window for a while after it's been miniaturized.
// This may be related to a similar problem with popup windows (bmo bug
// 378645, popup windows continue to receive mouseMoved events after having
// been "ordered out"), which is worked around in nsCocoaWindow::Show()
// (search on 378645 in nsCocoaWindow.mm). This problem is bmo bug 410219,
// and exists in both OS X 10.4 and 10.5.
if ([[self window] isMiniaturized])
return;
NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(theEvent, [self window]);
NSPoint viewEventLocation = [self convertPoint:windowEventLocation fromView:nil];
// 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.
BOOL mouseEventIsOverRollupWidget = NO;
if (gRollupWidget) {
NSWindow *popupWindow = (NSWindow*)gRollupWidget->GetNativeData(NS_NATIVE_WINDOW);
mouseEventIsOverRollupWidget = nsCocoaUtils::IsEventOverWindow(theEvent, popupWindow);
}
if (![NSApp isActive] && !mouseEventIsOverRollupWidget) {
if (sLastViewEntered) {
nsIWidget* lastViewEnteredWidget = [(NSView<mozView>*)sLastViewEntered widget];
NSPoint exitEventLocation = [sLastViewEntered convertPoint:windowEventLocation fromView:nil];
SendGeckoMouseEnterOrExitEvent(PR_TRUE, NS_MOUSE_EXIT, lastViewEnteredWidget, nsMouseEvent::eReal,
&exitEventLocation, nsMouseEvent::eTopLevel,
[theEvent modifierFlags], [theEvent buttonNumber], [theEvent deltaX],
[theEvent deltaY], [theEvent deltaZ]);
sLastViewEntered = nil;
}
return;
}
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
NSView* view = [[[self window] contentView] hitTest:windowEventLocation];
if (view) {
// we shouldn't handle this if the hit view is not us
if (view != (NSView*)self) {
[view mouseMoved:theEvent];
return;
}
}
else {
// If the hit test returned nil then the mouse isn't over the window. If thse mouse
// exited the window then send mouse exit to the last view in the window it was over.
if (sLastViewEntered) {
NSPoint exitEventLocation = [sLastViewEntered convertPoint:windowEventLocation fromView:nil];
// NSLog(@"sending NS_MOUSE_EXIT event with point %f,%f\n", exitEventLocation.x, exitEventLocation.y);
nsIWidget* lastViewEnteredWidget = [(NSView<mozView>*)sLastViewEntered widget];
SendGeckoMouseEnterOrExitEvent(PR_TRUE, NS_MOUSE_EXIT, lastViewEnteredWidget, nsMouseEvent::eReal,
&exitEventLocation, nsMouseEvent::eTopLevel,
[theEvent modifierFlags], [theEvent buttonNumber], [theEvent deltaX],
[theEvent deltaY], [theEvent deltaZ]);
sLastViewEntered = nil;
}
return;
}
// At this point we are supposed to handle this event. If we were not the last view entered, then
// we should send an exit event to the last view entered and an enter event to ourselves.
if (!mGeckoChild)
return;
nsAutoRetainCocoaObject kungFuDeathGrip(self);
if (sLastViewEntered != self) {
if (sLastViewEntered) {
NSPoint exitEventLocation = [sLastViewEntered convertPoint:windowEventLocation fromView:nil];
// NSLog(@"sending NS_MOUSE_EXIT event with point %f,%f\n", exitEventLocation.x, exitEventLocation.y);
nsIWidget* lastViewEnteredWidget = [(NSView<mozView>*)sLastViewEntered widget];
SendGeckoMouseEnterOrExitEvent(PR_TRUE, NS_MOUSE_EXIT, lastViewEnteredWidget, nsMouseEvent::eReal,
&exitEventLocation, nsMouseEvent::eChild,
[theEvent modifierFlags], [theEvent buttonNumber], [theEvent deltaX],
[theEvent deltaY], [theEvent deltaZ]);
// The mouse exit event we just sent may have destroyed this widget, bail if that happened.
if (!mGeckoChild)
return;
}
// NSLog(@"sending NS_MOUSE_ENTER event with point %f,%f\n", viewEventLocation.x, viewEventLocation.y);
SendGeckoMouseEnterOrExitEvent(PR_TRUE, NS_MOUSE_ENTER, mGeckoChild, nsMouseEvent::eReal,
&viewEventLocation, nsMouseEvent::eChild,
[theEvent modifierFlags], [theEvent buttonNumber], [theEvent deltaX],
[theEvent deltaY], [theEvent deltaZ]);
// The mouse enter event we just sent may have destroyed this widget, bail if that happened.
if (!mGeckoChild)
return;
// mark this view as the last view entered
sLastViewEntered = (NSView*)self;
}
nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_MOVE, nsnull, nsMouseEvent::eReal);
[self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
@ -3526,9 +3356,6 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
if (!mGeckoChild)
return;
@ -3582,9 +3409,6 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
nsAutoRetainCocoaObject kungFuDeathGrip(self);
[self maybeRollup:theEvent];
@ -3639,9 +3463,6 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
if (!mGeckoChild)
return;
@ -3686,9 +3507,6 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
- (void)rightMouseDragged:(NSEvent*)theEvent
{
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
if (!mGeckoChild)
return;
@ -3705,12 +3523,10 @@ static nsEventStatus SendGeckoMouseEnterOrExitEvent(PRBool isTrusted,
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
if (![self ensureCorrectMouseEventTarget:theEvent])
return;
nsAutoRetainCocoaObject kungFuDeathGrip(self);
if ([self maybeRollup:theEvent])
if ([self maybeRollup:theEvent] ||
!ChildViewMouseTracker::WindowAcceptsEvent([self window], theEvent))
return;
if (!mGeckoChild)
@ -6774,6 +6590,128 @@ nsTSMManager::CancelIME()
NS_OBJC_END_TRY_ABORT_BLOCK;
}
#pragma mark -
void
ChildViewMouseTracker::OnDestroyView(ChildView* aView)
{
if (sLastMouseEventView == aView)
sLastMouseEventView = nil;
}
void
ChildViewMouseTracker::MouseMoved(NSEvent* aEvent)
{
ChildView* view = ViewForEvent(aEvent);
if (view != sLastMouseEventView) {
// Send enter and / or exit events.
nsMouseEvent::exitType type = [view window] == [sLastMouseEventView window] ?
nsMouseEvent::eChild : nsMouseEvent::eTopLevel;
[sLastMouseEventView sendMouseEnterOrExitEvent:aEvent enter:NO type:type];
// After the cursor exits the window set it to a visible regular arrow cursor.
if (type == nsMouseEvent::eTopLevel) {
[[nsCursorManager sharedInstance] setCursor:eCursor_standard];
}
[view sendMouseEnterOrExitEvent:aEvent enter:YES type:type];
}
sLastMouseEventView = view;
[view handleMouseMoved:aEvent];
}
ChildView*
ChildViewMouseTracker::ViewForEvent(NSEvent* aEvent)
{
NSWindow* window = WindowForEvent(aEvent);
if (!window || !WindowAcceptsEvent(window, aEvent))
return nil;
NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, window);
NSView* view = [[[window contentView] superview] hitTest:windowEventLocation];
NS_ASSERTION(view, "How can the mouse be over a window but not over a view in that window?");
return [view isKindOfClass:[ChildView class]] ? (ChildView*)view : nil;
}
// Find the active window under the mouse. Returns nil if the mouse isn't over
// any active window.
NSWindow*
ChildViewMouseTracker::WindowForEvent(NSEvent* anEvent)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
NSInteger windowCount;
NSCountWindows(&windowCount);
NSInteger* windowList = (NSInteger*)malloc(sizeof(NSInteger) * windowCount);
if (!windowList)
return nil;
// The list we get back here is in order from front to back.
NSWindowList(windowCount, windowList);
NSPoint screenPoint = nsCocoaUtils::ScreenLocationForEvent(anEvent);
for (NSInteger i = 0; i < windowCount; i++) {
NSWindow* currentWindow = [NSApp windowWithWindowNumber:windowList[i]];
if (currentWindow && NSMouseInRect(screenPoint, [currentWindow frame], NO)) {
free(windowList);
return currentWindow;
}
}
free(windowList);
return nil;
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
}
BOOL
ChildViewMouseTracker::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* anEvent)
{
// Right mouse down events may get through to all windows, even to a top level
// window with an open sheet.
if (!aWindow || [anEvent type] == NSRightMouseDown)
return YES;
id delegate = [aWindow delegate];
if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
return YES;
nsIWidget *windowWidget = [(WindowDelegate *)delegate geckoWidget];
if (!windowWidget)
return YES;
nsWindowType windowType;
windowWidget->GetWindowType(windowType);
switch (windowType) {
case eWindowType_popup:
// If this is a context menu, it won't have a parent. So we'll always
// accept mouse move events on context menus even when none of our windows
// is active, which is the right thing to do.
// For panels, the parent window is the XUL window that owns the panel.
return WindowAcceptsEvent([aWindow parentWindow], anEvent);
case eWindowType_toplevel:
case eWindowType_dialog:
// Block all mouse events other than RightMouseDown on background windows
// and on windows behind sheets.
return [aWindow isMainWindow] && ![aWindow attachedSheet];
case eWindowType_sheet: {
nsIWidget* parentWidget = windowWidget->GetSheetWindowParent();
if (!parentWidget)
return YES;
// Only accept mouse events on a sheet whose containing window is active.
NSWindow* parentWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW);
return [parentWindow isMainWindow];
}
default:
return YES;
}
}
#pragma mark -
// Target for text services events sent as the result of calls made to
// TSMProcessRawKeyEvent() in [ChildView keyDown:] (above) when a plugin has
// the focus. The calls to TSMProcessRawKeyEvent() short-circuit Cocoa-based

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

@ -140,18 +140,12 @@ class nsCocoaUtils
// is for the window. Does not take window z-order into account.
static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow);
// Determines if the window should accept mouse events.
static BOOL WindowAcceptsEvent(NSWindow* aWindow, NSEvent* anEvent);
// Events are set up so that their coordinates refer to the window to which they
// were originally sent. If we reroute the event somewhere else, we'll have
// to get the window coordinates this way. Do not call this unless the window
// the event was originally targeted at is still alive!
static NSPoint EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow);
// Finds the foremost window that is under the mouse for the current application.
static NSWindow* FindWindowForEvent(NSEvent* anEvent, BOOL* isUnderMouse);
// Hides the Menu bar and the Dock. Multiple hide/show requests can be nested.
static void HideOSChromeOnScreen(PRBool aShouldHide, NSScreen* aScreen);

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

@ -126,90 +126,6 @@ NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow
NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
}
BOOL nsCocoaUtils::WindowAcceptsEvent(NSWindow* aWindow, NSEvent* anEvent)
{
// Right mouse down events may get through to all windows, even to a top level
// window with an open sheet.
if (!aWindow || [anEvent type] == NSRightMouseDown)
return YES;
id delegate = [aWindow delegate];
if (!delegate || ![delegate isKindOfClass:[WindowDelegate class]])
return YES;
nsIWidget *windowWidget = [(WindowDelegate *)delegate geckoWidget];
if (!windowWidget)
return YES;
nsWindowType windowType;
windowWidget->GetWindowType(windowType);
switch (windowType) {
case eWindowType_popup:
// If this is a context menu, it won't have a parent. So we'll always
// accept mouse move events on context menus even when none of our windows
// is active, which is the right thing to do.
// For panels, the parent window is the XUL window that owns the panel.
return WindowAcceptsEvent([aWindow parentWindow], anEvent);
case eWindowType_toplevel:
case eWindowType_dialog:
// Block all mouse events other than RightMouseDown on background windows
// and on windows behind sheets.
return [aWindow isMainWindow] && ![aWindow attachedSheet];
case eWindowType_sheet: {
nsIWidget* parentWidget = windowWidget->GetSheetWindowParent();
if (!parentWidget)
return YES;
// Only accept mouse events on a sheet whose containing window is active.
NSWindow* parentWindow = (NSWindow*)parentWidget->GetNativeData(NS_NATIVE_WINDOW);
return [parentWindow isMainWindow];
}
default:
return YES;
}
}
// Find the active window under the mouse. If the mouse isn't over any active
// window, just return the topmost active window and set *isUnderMouse to NO.
NSWindow* nsCocoaUtils::FindWindowForEvent(NSEvent* anEvent, BOOL* isUnderMouse)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
*isUnderMouse = NO;
NSInteger windowCount;
NSCountWindows(&windowCount);
NSInteger* windowList = (NSInteger*)malloc(sizeof(NSInteger) * windowCount);
if (!windowList)
return nil;
// The list we get back here is in order from front to back.
NSWindowList(windowCount, windowList);
NSWindow* activeWindow = nil;
NSPoint screenPoint = ScreenLocationForEvent(anEvent);
for (NSInteger i = 0; i < windowCount; i++) {
NSWindow* currentWindow = [NSApp windowWithWindowNumber:windowList[i]];
if (currentWindow && WindowAcceptsEvent(currentWindow, anEvent)) {
if (NSPointInRect(screenPoint, [currentWindow frame])) {
free(windowList);
*isUnderMouse = YES;
return currentWindow;
}
if (!activeWindow)
activeWindow = currentWindow;
}
}
free(windowList);
return activeWindow;
NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
}
void nsCocoaUtils::HideOSChromeOnScreen(PRBool aShouldHide, NSScreen* aScreen)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK;

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

@ -237,6 +237,9 @@ public:
NS_IMETHOD SetWindowShadowStyle(PRInt32 aStyle);
virtual void SetShowsToolbarButton(PRBool aShow);
NS_IMETHOD SetWindowTitlebarColor(nscolor aColor, PRBool aActive);
virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
PRUint32 aNativeMessage,
PRUint32 aModifierFlags);
void DispatchSizeModeEvent();

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

@ -1427,6 +1427,21 @@ NS_IMETHODIMP nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor, PRBool aActi
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint,
PRUint32 aNativeMessage,
PRUint32 aModifierFlags)
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
if (mPopupContentView)
return mPopupContentView->SynthesizeNativeMouseEvent(aPoint, aNativeMessage,
aModifierFlags);
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
gfxASurface* nsCocoaWindow::GetThebesSurface()
{
if (mPopupContentView)

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

@ -169,6 +169,11 @@ protected:
const nsAString& aUnmodifiedCharacters)
{ return NS_ERROR_UNEXPECTED; }
virtual nsresult SynthesizeNativeMouseEvent(nsIntPoint aPoint,
PRUint32 aNativeMessage,
PRUint32 aModifierFlags)
{ return NS_ERROR_UNEXPECTED; }
// Stores the clip rectangles in aRects into mClipRects. Returns true
// if the new rectangles are different from the old rectangles.
PRBool StoreWindowClipRegion(const nsTArray<nsIntRect>& aRects);

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

@ -66,6 +66,8 @@ _TEST_FILES = test_bug343416.xul \
ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
_TEST_FILES += native_menus_window.xul \
test_native_menus.xul \
native_mouse_mac_window.xul \
test_native_mouse_mac.xul \
test_bug428405.xul \
test_bug466599.xul \
$(NULL)

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

@ -0,0 +1,487 @@
<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Native Menus Test code
-
- The Initial Developer of the Original Code is
- Mozilla Corporation.
- Portions created by the Initial Developer are Copyright (C) 2009
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Markus Stange <mstange@themasta.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window id="NativeMenuWindow"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml"
width="600"
height="600"
title="Native Mouse Event Test"
orient="vertical">
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<box height="200" id="box"/>
<menupopup id="popup" width="250" height="50"/>
<panel id="panel" width="250" height="50" noautohide="true"/>
<script type="application/javascript"><![CDATA[
function ok(condition, message) {
window.opener.wrappedJSObject.SimpleTest.ok(condition, message);
}
function is(a, b, message) {
window.opener.wrappedJSObject.SimpleTest.is(a, b, message);
}
function todo(condition, message) {
window.opener.wrappedJSObject.SimpleTest.todo(condition, message);
}
function todo_is(a, b, message) {
window.opener.wrappedJSObject.SimpleTest.todo_is(a, b, message);
}
function onTestsFinished() {
gRightWindow.close();
window.close();
window.opener.wrappedJSObject.SimpleTest.finish();
}
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const xulWin = 'data:application/vnd.mozilla.xul+xml,<?xml version="1.0"?><?xml-stylesheet href="chrome://global/skin" type="text/css"?><window xmlns="' + XUL_NS + '"/>';
const NSLeftMouseDown = 1,
NSLeftMouseUp = 2,
NSRightMouseDown = 3,
NSRightMouseUp = 4,
NSMouseMoved = 5,
NSLeftMouseDragged = 6,
NSRightMouseDragged = 7,
NSMouseEntered = 8,
NSMouseExited = 9,
NSKeyDown = 10,
NSKeyUp = 11,
NSFlagsChanged = 12,
NSAppKitDefined = 13,
NSSystemDefined = 14,
NSApplicationDefined = 15,
NSPeriodic = 16,
NSCursorUpdate = 17,
NSScrollWheel = 22,
NSTabletPoint = 23,
NSTabletProximity = 24,
NSOtherMouseDown = 25,
NSOtherMouseUp = 26,
NSOtherMouseDragged = 27,
NSEventTypeGesture = 29,
NSEventTypeMagnify = 30,
NSEventTypeSwipe = 31,
NSEventTypeRotate = 18,
NSEventTypeBeginGesture = 19,
NSEventTypeEndGesture = 20;
var gExpectedEvents = [];
var gRightWindow = null, gPopup = null;
function testMouse(x, y, msg, elem, win, exp, callback) {
clearExpectedEvents();
exp.forEach(function (expEv) {
expEv.screenX = x;
expEv.screenY = y;
gExpectedEvents.push(expEv);
});
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
utils.sendNativeMouseEvent(x, y, msg, 0, elem);
SimpleTest.executeSoon(function () {
clearExpectedEvents();
callback();
});
}
function eventListenOnce(elem, name, callback) {
elem.addEventListener(name, function(e) {
elem.removeEventListener(name, arguments.callee, false);
callback(e);
}, false);
}
function focusAndThen(win, callback) {
eventListenOnce(win, "focus", callback);
win.focus();
}
function eventToString(e) {
return JSON.stringify({
type: e.type, target: e.target.nodeName, screenX: e.screenX, screenY: e.screenY
});
}
function clearExpectedEvents() {
while (gExpectedEvents.length > 0) {
var expectedEvent = gExpectedEvents.shift();
var errFun = expectedEvent.todoShouldHaveFired ? todo : ok;
errFun(false, "didn't receive expected event: " + eventToString(expectedEvent));
}
}
var gEventNum = 0;
function eventMonitor(e) {
var expectedEvent = gExpectedEvents.shift();
while (expectedEvent && expectedEvent.todoShouldHaveFired) {
todo(false, "Should have got event: " + eventToString(expectedEvent));
expectedEvent = gExpectedEvents.shift();
}
if (!expectedEvent) {
ok(false, "received event I didn't expect: " + eventToString(e));
return true;
}
gEventNum++;
is(e.screenX, expectedEvent.screenX, gEventNum + " | wrong X coord for event " + eventToString(e));
is(e.screenY, expectedEvent.screenY, gEventNum + " | wrong Y coord for event " + eventToString(e));
is(e.type, expectedEvent.type, gEventNum + " | wrong event type for event " + eventToString(e));
is(e.target, expectedEvent.target, gEventNum + " | wrong target for event " + eventToString(e));
if (expectedEvent.todoShouldNotHaveFired) {
todo(false, gEventNum + " | Got an event that should not have fired: " + eventToString(e));
}
}
function observe(elem, fun) {
elem.addEventListener("mousemove", fun, false);
elem.addEventListener("mouseover", fun, false);
elem.addEventListener("mouseout", fun, false);
elem.addEventListener("mousedown", fun, false);
elem.addEventListener("mouseup", fun, false);
elem.addEventListener("click", fun, false);
}
function start() {
window.resizeTo(200, 200);
window.moveTo(50, 50);
gRightWindow = open(xulWin, '', 'chrome,screenX=300,screenY=50,width=200,height=200');
eventListenOnce(gRightWindow, "focus", function () {
focusAndThen(window, runTests);
});
gPopup = document.getElementById("popup");
}
function runTests() {
observe(window, eventMonitor);
observe(gRightWindow, eventMonitor);
observe(gPopup, eventMonitor);
var left = window, right = gRightWindow;
var leftElem = document.getElementById("box");
var rightElem = gRightWindow.document.documentElement;
var panel = document.getElementById("panel");
var tooltip = (function createTooltipInRightWindow() {
var _tooltip = right.document.createElementNS(XUL_NS, "tooltip");
_tooltip.setAttribute("id", "tip");
_tooltip.setAttribute("width", "80");
_tooltip.setAttribute("height", "20");
right.document.documentElement.appendChild(_tooltip);
return _tooltip;
})();
var tests = [
// Enter the left window, which is focused.
[150, 150, NSMouseMoved, null, left, [
{ type: "mouseover", target: leftElem },
{ type: "mousemove", target: leftElem }
]],
// Test that moving inside the window fires mousemove events.
[170, 150, NSMouseMoved, null, left, [
{ type: "mousemove", target: leftElem },
]],
// Leaving the window should fire a mouseout event...
[170, 20, NSMouseMoved, null, left, [
{ type: "mouseout", target: leftElem },
]],
// ... and entering a mouseover event.
[170, 120, NSMouseMoved, null, left, [
{ type: "mouseover", target: leftElem },
{ type: "mousemove", target: leftElem },
]],
// Move over the right window, which is inactive.
// Inactive windows shouldn't respond to mousemove events,
// so we should only get a mouseout event, no mouseover event.
[400, 150, NSMouseMoved, null, right, [
{ type: "mouseout", target: leftElem },
]],
// Clicking an inactive window shouldn't have any effect, other
// than focusing it.
[400, 150, NSLeftMouseDown, null, right, [
]],
[400, 150, NSLeftMouseUp, null, right, [
]],
// Now it's focused, so we should get a mousedown event when clicking.
[400, 150, NSLeftMouseDown, null, right, [
{ type: "mousedown", target: rightElem },
{ type: "mouseover", target: rightElem, todoShouldHaveFired: true },
]],
// Let's drag to the right without letting the button go. It would be better
// if the mouseover event had fired as soon as the mouse entered the window,
// and not only when dragging, but that's ok.
[410, 150, NSLeftMouseDragged, null, right, [
{ type: "mouseover", target: rightElem, todoShouldNotHaveFired: true },
{ type: "mousemove", target: rightElem },
]],
// Let go of the mouse.
[410, 150, NSLeftMouseUp, null, right, [
{ type: "mouseup", target: rightElem },
{ type: "click", target: rightElem },
]],
// Now we're being sneaky. The left window is inactive, but *right*-clicks to it
// should still get through. Test that.
// Ideally we'd be bracketing that event with over and out events, too, but it
// probably doesn't matter too much.
[150, 170, NSRightMouseDown, null, left, [
{ type: "mouseover", target: leftElem, todoShouldHaveFired: true },
{ type: "mousedown", target: leftElem },
{ type: "mouseout", target: leftElem, todoShouldHaveFired: true },
]],
// Let go of the mouse.
[150, 170, NSRightMouseUp, null, left, [
{ type: "mouseover", target: leftElem, todoShouldHaveFired: true },
{ type: "mouseup", target: leftElem },
{ type: "click", target: leftElem },
{ type: "mouseout", target: leftElem, todoShouldHaveFired: true },
]],
// Right clicking hasn't focused it, so the window is still inactive.
// Let's focus it; this time without the mouse, for variaton's sake.
function raiseLeftWindow(callback) {
focusAndThen(left, callback);
},
// It's active, so it should respond to mousemove events now.
[150, 170, NSMouseMoved, null, left, [
{ type: "mouseover", target: leftElem },
{ type: "mousemove", target: leftElem },
]],
// This was boring... let's introduce a popup. It will overlap both the left
// and the right window.
function openPopupInLeftWindow(callback) {
eventListenOnce(gPopup, "popupshown", callback);
gPopup.openPopupAtScreen(150, 50, true);
},
// Move the mouse over the popup.
// We'll get duplicate events on the popup; ignore them.
[200, 80, NSMouseMoved, gPopup, left, [
{ type: "mouseout", target: leftElem },
{ type: "mouseover", target: gPopup },
{ type: "mouseover", target: gPopup, todoShouldNotHaveFired: true },
{ type: "mousemove", target: gPopup },
{ type: "mousemove", target: gPopup, todoShouldNotHaveFired: true },
]],
// Move the mouse back over the left window outside the popup.
[160, 170, NSMouseMoved, null, left, [
{ type: "mouseout", target: gPopup },
{ type: "mouseout", target: gPopup, todoShouldNotHaveFired: true },
{ type: "mouseover", target: leftElem },
{ type: "mousemove", target: leftElem },
]],
// Back over the popup... (double events again)
[190, 80, NSMouseMoved, gPopup, left, [
{ type: "mouseout", target: leftElem },
{ type: "mouseover", target: gPopup },
{ type: "mouseover", target: gPopup, todoShouldNotHaveFired: true },
{ type: "mousemove", target: gPopup },
{ type: "mousemove", target: gPopup, todoShouldNotHaveFired: true },
]],
// ...and over into the right window. (... again)
// It's inactive, so it shouldn't get mouseover events yet.
[400, 170, NSMouseMoved, null, right, [
{ type: "mouseout", target: gPopup },
{ type: "mouseout", target: gPopup, todoShouldNotHaveFired: true },
]],
// Again, no mouse events please, even though a popup is open. (bug 425556)
[400, 180, NSMouseMoved, null, right, [
]],
// Activate the right window with a click.
// This will close the popup.
[400, 180, NSLeftMouseDown, null, right, [
]],
[400, 180, NSLeftMouseUp, null, right, [
]],
function verifyPopupClosed(callback) {
is(gPopup.popupBoxObject.popupState, "closed", "popup should have closed when clicking");
callback();
},
// Now the right window is active; click it again, just for fun.
// (Would be good to have a mouseover event here.)
[400, 180, NSLeftMouseDown, null, right, [
{ type: "mouseover", target: rightElem, todoShouldHaveFired: true },
{ type: "mousedown", target: rightElem },
]],
[400, 180, NSLeftMouseUp, null, right, [
{ type: "mouseup", target: rightElem },
{ type: "click", target: rightElem },
]],
// Time for our next trick: a tooltip!
// Install the tooltip, but don't show it yet.
function setTooltip(callback) {
rightElem.setAttribute("tooltip", "tip");
callback();
},
// Move the mouse to trigger the appearance of the tooltip.
// ... and what's that, a mousemove event without preceding mouseover? Bad.
[410, 180, NSMouseMoved, null, right, [
{ type: "mousemove", target: rightElem },
]],
// Wait for the tooltip to appear.
function (callback) {
var timer = setTimeout(callback, 2000); // just in case the tooltip is shy
eventListenOnce(rightElem, "popupshown", function () {
clearTimeout(timer);
callback();
});
},
// Now the tooltip is visible.
// Move the mouse a little to the right, but send the event to the tooltip's
// widget, even though the mouse is not over the tooltip, because that's what
// Mac OS X does.
[411, 180, NSMouseMoved, tooltip, right, [
{ type: "mousemove", target: rightElem },
]],
// Move another pixel. This time send the event to the right widget.
// However, that must not make a difference.
[412, 180, NSMouseMoved, null, right, [
{ type: "mousemove", target: rightElem },
]],
// Move up and click to make the tooltip go away.
[412, 80, NSMouseMoved, null, right, [
{ type: "mousemove", target: rightElem },
]],
[412, 80, NSLeftMouseDown, null, right, [
{ type: "mousedown", target: rightElem },
]],
[412, 80, NSLeftMouseUp, null, right, [
{ type: "mouseup", target: rightElem },
{ type: "click", target: rightElem },
]],
// OK, next round. Open a panel in the left window, which is inactive.
function openPanel(callback) {
eventListenOnce(panel, "popupshown", callback);
panel.openPopupAtScreen(150, 150, false);
},
// The panel is parented, so it will be z-ordered over its parent but
// under the active window.
// Now we move the mouse over the part where the panel rect intersects the
// right window's rect. Since the panel is under the window, all the events
// should target the right window.
// Try with sending to three different targets.
[390, 170, NSMouseMoved, null, right, [
{ type: "mousemove", target: rightElem },
]],
[390, 171, NSMouseMoved, null, left, [
{ type: "mousemove", target: rightElem },
]],
[391, 171, NSMouseMoved, panel, left, [
{ type: "mousemove", target: rightElem },
]],
// Now move off the right window, so that the mouse is directly over the
// panel.
[260, 170, NSMouseMoved, null, left, [
{ type: "mouseout", target: rightElem },
]],
[260, 171, NSMouseMoved, null, left, [
]],
[261, 171, NSMouseMoved, panel, left, [
]],
// Let's be evil and click it.
[261, 171, NSLeftMouseDown, panel, left, [
]],
[261, 171, NSLeftMouseUp, panel, left, [
{ type: "mouseup", target: panel },
]],
// This didn't focus the window, unfortunately, so let's do it ourselves.
function raiseLeftWindowTakeTwo(callback) {
focusAndThen(left, callback);
},
// Now mouse events should get through to the panel (which is now over the
// right window).
[387, 170, NSMouseMoved, null, right, [
{ type: "mouseover", target: panel },
{ type: "mousemove", target: panel },
{ type: "mouseout", target: panel, todoShouldNotHaveFired: true },
{ type: "mouseover", target: left.document.documentElement, todoShouldNotHaveFired: true },
]],
// Why does left.document.documentElement get entered? This makes no sense.
[387, 171, NSMouseMoved, null, left, [
{ type: "mouseout", target: left.document.documentElement, todoShouldNotHaveFired: true },
{ type: "mouseover", target: panel, todoShouldNotHaveFired: true },
{ type: "mousemove", target: panel },
]],
[388, 171, NSMouseMoved, panel, left, [
{ type: "mousemove", target: panel },
]],
// Click the panel.
[388, 171, NSLeftMouseDown, panel, left, [
{ type: "mousedown", target: panel }
]],
[388, 171, NSLeftMouseUp, panel, left, [
{ type: "mouseup", target: panel },
{ type: "click", target: panel },
]],
// Last test for today: Hit testing in the Canyon of Nowhere -
// the pixel row directly south of the panel, over the left window.
// Before bug 515003 we wrongly thought the mouse wasn't over any window.
[173, 200, NSMouseMoved, panel, left, [
{ type: "mouseout", target: panel },
{ type: "mouseover", target: leftElem },
{ type: "mousemove", target: leftElem },
]],
[173, 201, NSMouseMoved, panel, left, [
{ type: "mousemove", target: leftElem },
]],
];
function runNextTest() {
if (!tests.length)
return onTestsFinished();
var test = tests.shift();
if (typeof test == "function")
return test(runNextTest);
var [x, y, msg, elem, win, exp] = test;
testMouse(x, y, msg, elem, win, exp, runNextTest);
}
runNextTest();
}
SimpleTest.waitForFocus(start);
]]></script>
</window>

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

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window title="Native mouse event tests"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<title>Native mouse event tests</title>
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<body xmlns="http://www.w3.org/1999/xhtml">
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
</body>
<script class="testbody" type="application/javascript">
<![CDATA[
SimpleTest.waitForExplicitFinish();
window.open("native_mouse_mac_window.xul", "NativeMouseWindow",
"chrome,width=600,height=600");
]]>
</script>
</window>