Bug 515003 - Rewrite native mouse event handling. Also add tests for native mouse events (bug 470845). r=josh, sr=roc
authorMarkus Stange <mstange@themasta.com>
Wed, 23 Sep 2009 14:31:37 +1200
changeset 32976 810ee87710df51cd90889cb4f846db7ebefa2390
parent 32975 aae30925704b9abf231bbd1164959b714ba0d217
child 32977 98330c8132a955b771b3a3f99452f3020f711306
push idunknown
push userunknown
push dateunknown
reviewersjosh, roc
bugs515003, 470845
milestone1.9.3a1pre
Bug 515003 - Rewrite native mouse event handling. Also add tests for native mouse events (bug 470845). r=josh, sr=roc
dom/base/nsDOMWindowUtils.cpp
dom/base/nsDOMWindowUtils.h
dom/interfaces/base/nsIDOMWindowUtils.idl
widget/public/nsIWidget.h
widget/src/cocoa/nsChildView.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsCocoaUtils.h
widget/src/cocoa/nsCocoaUtils.mm
widget/src/cocoa/nsCocoaWindow.h
widget/src/cocoa/nsCocoaWindow.mm
widget/src/xpwidgets/nsBaseWidget.h
widget/tests/Makefile.in
widget/tests/native_mouse_mac_window.xul
widget/tests/test_native_mouse_mac.xul
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -387,16 +387,37 @@ nsDOMWindowUtils::SendNativeKeyEvent(PRI
   if (!widget)
     return NS_ERROR_FAILURE;
 
   return widget->SynthesizeNativeKeyEvent(aNativeKeyboardLayout, aNativeKeyCode,
                                           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)
 {
   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
@@ -437,16 +458,38 @@ nsDOMWindowUtils::GetWidget(nsPoint* aOf
           return frame->GetView()->GetNearestWidget(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)
 {
   PRBool hasCap = PR_FALSE;
   if (NS_FAILED(nsContentUtils::GetSecurityManager()->IsCapabilityEnabled(
     "UniversalXPConnect", &hasCap)) || !hasCap)
     return NS_ERROR_DOM_SECURITY_ERR;
 
--- a/dom/base/nsDOMWindowUtils.h
+++ b/dom/base/nsDOMWindowUtils.h
@@ -52,11 +52,12 @@ public:
   NS_DECL_NSIDOMWINDOWUTILS
 
 protected:
   nsRefPtr<nsGlobalWindow> mWindow;
 
   // 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();
 };
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -43,17 +43,17 @@
  * elevated privileges; the method implementations should contain the
  * necessary security checks.  Access this interface by calling
  * getInterface on a DOMWindow.
  */
 
 interface nsIDOMElement;
 interface nsIDOMHTMLCanvasElement;
 
-[scriptable, uuid(99d904c0-3b9e-44b7-b1e0-372766d18308)]
+[scriptable, uuid(c1b779af-7297-4e2e-9fc4-a6f22038770f)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -194,16 +194,30 @@ interface nsIDOMWindowUtils : nsISupport
    */
   void sendNativeKeyEvent(in long aNativeKeyboardLayout,
                           in long aNativeKeyCode,
                           in long aModifierFlags,
                           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
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without UniversalXPConnect
    * privileges.
    */
   void activateNativeMenuItemAt(in AString indexString);
 
--- a/widget/public/nsIWidget.h
+++ b/widget/public/nsIWidget.h
@@ -97,18 +97,18 @@ typedef nsEventStatus (* EVENT_CALLBACK)
 #endif
 #ifdef XP_WIN
 #define NS_NATIVE_TSF_THREAD_MGR       100
 #define NS_NATIVE_TSF_CATEGORY_MGR     101
 #define NS_NATIVE_TSF_DISPLAY_ATTR_MGR 102
 #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
  * Also used for the -moz-window-shadow CSS property
  */
 
 #define NS_STYLE_WINDOW_SHADOW_NONE             0
 #define NS_STYLE_WINDOW_SHADOW_DEFAULT          1
@@ -926,16 +926,28 @@ class nsIWidget : public nsISupports {
      */
     virtual nsresult SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
                                               PRInt32 aNativeKeyCode,
                                               PRUint32 aModifierFlags,
                                               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
      * by the "|" (pipe) character. The last integer in the string represents
      * the item index in a submenu located using the integers preceeding it.
      *
      * Example: 1|0|4
      * In this string, the first integer represents the top-level submenu
      * in the native menu bar. Since the integer is 1, it is the second submeu
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -204,16 +204,22 @@ enum {
 
 // Stop NSView hierarchy being changed during [ChildView drawRect:]
 - (void)delayedTearDown;
 
 - (void)setTransparent:(BOOL)transparent;
 
 - (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
 //
 // XXX - The swipeWithEvent, beginGestureWithEvent, magnifyWithEvent,
 // rotateWithEvent, and endGestureWithEvent methods are part of a
 // PRIVATE interface exported by nsResponder and reverse-engineering
 // was necessary to obtain the methods' prototypes. Thus, Apple may
@@ -266,16 +272,32 @@ private:
   static PRBool sIgnoreCommit;
   static NSView<mozView>* sComposingView;
   static TSMDocumentID sDocumentID;
   static NSString* sComposingString;
 
   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
 //
 //-------------------------------------------------------------------------
 
 class nsChildView : public nsBaseWidget,
                     public nsIPluginWidget
@@ -380,16 +402,26 @@ public:
   NS_IMETHOD        EndDrawPlugin();
   NS_IMETHOD        SetPluginInstanceOwner(nsIPluginInstanceOwner* aInstanceOwner);
 
   NS_IMETHOD        SetPluginEventModel(int inEventModel);
   NS_IMETHOD        GetPluginEventModel(int* outEventModel);
 
   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);
   
 #ifdef ACCESSIBILITY
   void              GetDocumentAccessible(nsIAccessible** aAccessible);
 #endif
@@ -419,22 +451,16 @@ protected:
   PRBool            ReportSizeEvent();
 
   // override to create different kinds of child views. Autoreleases, so
   // caller must retain.
   virtual NSView*   CreateCocoaView(NSRect inFrame);
   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]
 
   NSView<mozView>*      mParentView;
   nsIWidget*            mParentWidget;
 
 #ifdef ACCESSIBILITY
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -295,19 +295,19 @@ PRBool gChildViewMethodsSwizzled = PR_FA
 extern nsISupportsArray *gDraggedTransferables;
 
 PRBool nsTSMManager::sIsIMEEnabled = PR_TRUE;
 PRBool nsTSMManager::sIsRomanKeyboardsOnly = PR_FALSE;
 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);
 #endif
 
 nsIRollupListener * gRollupListener = nsnull;
 nsIWidget         * gRollupWidget   = nsnull;
 
@@ -349,18 +349,16 @@ PRUint32 nsChildView::sLastInputEventCou
 - (NPEventModel)pluginEventModel;
 
 - (BOOL)isRectObscuredBySubview:(NSRect)inRect;
 
 - (void)processPendingRedraws;
 
 - (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv;
 
-- (BOOL)ensureCorrectMouseEventTarget:(NSEvent *)anEvent;
-
 - (void)maybeInitContextMenuTracking;
 
 + (NSEvent*)makeNewCocoaEventWithType:(NSEventType)type fromEvent:(NSEvent*)theEvent;
 
 - (void)maybeInvalidateShadow;
 - (void)invalidateShadow;
 
 #if USE_CLICK_HOLD_CONTEXTMENU
@@ -1679,16 +1677,50 @@ nsresult nsChildView::SynthesizeNativeKe
     gOverrideKeyboardLayout = currentLayout;
   }
 
   return NS_OK;
 
   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)
 {
   NSArray* indexes = [locationString componentsSeparatedByString:@"|"];
   unsigned int indexCount = [indexes count];
   if (indexCount == 0)
     return nil;
@@ -2388,19 +2420,16 @@ NSEvent* gLastDragEvent = nil;
 - (void)dealloc
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   [mPendingDirtyRects release];
   [mLastMouseDownEvent release];
   if (mPluginTSMDoc)
     ::DeleteTSMDocument(mPluginTSMDoc);
-  
-  if (sLastViewEntered == self)
-    sLastViewEntered = nil;
 
   [[NSNotificationCenter defaultCenter] removeObserver:self];
   [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
 
   [super dealloc];    
 
 #ifndef NP_NO_QUICKDRAW
   // This sets the current port to _savePort.
@@ -2409,16 +2438,17 @@ NSEvent* gLastDragEvent = nil;
 #endif
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)widgetDestroyed
 {
   nsTSMManager::OnDestroyView(self);
+  ChildViewMouseTracker::OnDestroyView(self);
   mGeckoChild = nsnull;
 
   // Just in case we're destroyed abruptly and missed the draggingExited
   // or performDragOperation message.
   NS_IF_RELEASE(mDragService);
 }
 
 // mozView method, return our gecko child view widget. Note this does not AddRef.
@@ -2793,95 +2823,16 @@ static const PRInt32 sShadowInvalidation
                                                   pressure:[theEvent pressure]];
     [self rightMouseDown:clickHoldEvent];
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 #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
 // ours open).  We send the initial notification here, but others are sent
 // in nsCocoaWindow::Show().
 - (void)maybeInitContextMenuTracking
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
@@ -3185,22 +3136,20 @@ static const PRInt32 sShadowInvalidation
     mLastMouseDownEvent = nil;
     return;
   }
   else {
     [mLastMouseDownEvent release];
     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
   // fire off timer to check for click-hold after two seconds. retains |theEvent|
   [self performSelector:@selector(clickHoldCallback:) withObject:theEvent afterDelay:2.0];
 #endif
 
   // in order to send gecko events we'll need a gecko widget
@@ -3253,18 +3202,16 @@ static const PRInt32 sShadowInvalidation
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)mouseUp:(NSEvent *)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
-  if (![self ensureCorrectMouseEventTarget:theEvent])
-    return;
 
   if (!mGeckoChild)
     return;
 
   nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_BUTTON_UP, nsnull, nsMouseEvent::eReal);
   [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
   if (nsCocoaUtils::GetCocoaEventModifierFlags(theEvent) & NSControlKeyMask)
     geckoEvent.button = nsMouseEvent::eRightButton;
@@ -3301,194 +3248,77 @@ static const PRInt32 sShadowInvalidation
     geckoEvent.nativeMsg = &cocoaEvent;
   }
 
   mGeckoChild->DispatchWindowEvent(geckoEvent);
 
   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)
-{
-  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
-
-  if (!widget || !localEventLocation)
-    return nsEventStatus_eIgnore;
-
-  nsMouseEvent event(isTrusted, msg, widget, aReason);
-  event.refPoint.x = nscoord((PRInt32)localEventLocation->x);
-  event.refPoint.y = nscoord((PRInt32)localEventLocation->y);
+- (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
+                            enter:(BOOL)aEnter
+                             type:(nsMouseEvent::exitType)aType
+{
+  if (!mGeckoChild)
+    return;
+
+  NSPoint windowEventLocation = nsCocoaUtils::EventLocationForWindow(aEvent, [self window]);
+  NSPoint localEventLocation = [self convertPoint:windowEventLocation fromView:nil];
+
+  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();
     ::GetGlobalMouse(&carbonEvent.where);
     carbonEvent.modifiers = ::GetCurrentEventKeyModifiers();
     event.nativeMsg = &carbonEvent;
   }
 #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;
-
-  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);
-}
-
-- (void)mouseMoved:(NSEvent*)theEvent
+  event.exit = aType;
+
+  nsEventStatus status; // ignored
+  mGeckoChild->DispatchEvent(&event, status);
+}
+
+- (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];
 
   // Create event for use by plugins.
   // 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;
@@ -3521,19 +3351,16 @@ static nsEventStatus SendGeckoMouseEnter
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)mouseDragged:(NSEvent*)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
-  if (![self ensureCorrectMouseEventTarget:theEvent])
-    return;
-
   if (!mGeckoChild)
     return;
 
   gLastDragView = self;
   gLastDragEvent = theEvent;
 
   nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_MOVE, nsnull, nsMouseEvent::eReal);
   [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
@@ -3577,19 +3404,16 @@ static nsEventStatus SendGeckoMouseEnter
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)rightMouseDown:(NSEvent *)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
-  if (![self ensureCorrectMouseEventTarget:theEvent])
-    return;
-
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
 
   [self maybeRollup:theEvent];
   if (!mGeckoChild)
     return;
 
   // The right mouse went down, fire off a right mouse down event to gecko
   nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_BUTTON_DOWN, nsnull, nsMouseEvent::eReal);
@@ -3634,19 +3458,16 @@ static nsEventStatus SendGeckoMouseEnter
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)rightMouseUp:(NSEvent *)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
-  if (![self ensureCorrectMouseEventTarget:theEvent])
-    return;
-
   if (!mGeckoChild)
     return;
 
   nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_BUTTON_UP, nsnull, nsMouseEvent::eReal);
   [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
   geckoEvent.button = nsMouseEvent::eRightButton;
   geckoEvent.clickCount = [theEvent clickCount];
 
@@ -3681,41 +3502,36 @@ static nsEventStatus SendGeckoMouseEnter
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
   mGeckoChild->DispatchWindowEvent(geckoEvent);
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)rightMouseDragged:(NSEvent*)theEvent
 {
-  if (![self ensureCorrectMouseEventTarget: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->DispatchWindowEvent(geckoEvent);
 }
 
 - (void)otherMouseDown:(NSEvent *)theEvent
 {
   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)
     return;
 
   nsMouseEvent geckoEvent(PR_TRUE, NS_MOUSE_BUTTON_DOWN, nsnull, nsMouseEvent::eReal);
   [self convertCocoaMouseEvent:theEvent toGeckoEvent:&geckoEvent];
   geckoEvent.button = nsMouseEvent::eMiddleButton;
@@ -6769,16 +6585,138 @@ nsTSMManager::CancelIME()
   // composing in TSM. We also need to kill the our composing transaction too.
   NSAttributedString* str = [[NSAttributedString alloc] initWithString:@""];
   [sComposingView insertText:str];
   [str release];
 
   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
 // IME (which would otherwise interfere with our efforts) and allow Carbon-
 // based IME to work in plugins (via the NPAPI).  This strategy doesn't cause
 // trouble for plugins that (like the Java Embedding Plugin) bypass the NPAPI
 // to get their keyboard events and do their own Cocoa-based IME.
 OSStatus PluginKeyEventsHandler(EventHandlerCallRef inHandlerRef,
--- a/widget/src/cocoa/nsCocoaUtils.h
+++ b/widget/src/cocoa/nsCocoaUtils.h
@@ -135,27 +135,21 @@ class nsCocoaUtils
   // Gives the location for the event in screen coordinates. Do not call this
   // unless the window the event was originally targeted at is still alive!
   static NSPoint ScreenLocationForEvent(NSEvent* anEvent);
   
   // Determines if an event happened over a window, whether or not the event
   // 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);
 
   static nsIWidget* GetHiddenWindowWidget();
 
   static void PrepareForNativeAppModalDialog();
   static void CleanUpAfterNativeAppModalDialog();
--- a/widget/src/cocoa/nsCocoaUtils.mm
+++ b/widget/src/cocoa/nsCocoaUtils.mm
@@ -121,100 +121,16 @@ NSPoint nsCocoaUtils::EventLocationForWi
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   return [aWindow convertScreenToBase:ScreenLocationForEvent(anEvent)];
 
   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;
 
   // Keep track of how many hiding requests have been made, so that they can
   // be nested.
   static int sMenuBarHiddenCount = 0, sDockHiddenCount = 0;
 
--- a/widget/src/cocoa/nsCocoaWindow.h
+++ b/widget/src/cocoa/nsCocoaWindow.h
@@ -232,16 +232,19 @@ public:
     NS_IMETHOD CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent);
     NS_IMETHOD GetAttention(PRInt32 aCycleCount);
     virtual PRBool HasPendingInputEvent();
     virtual nsTransparencyMode GetTransparencyMode();
     virtual void SetTransparencyMode(nsTransparencyMode aMode);
     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();
 
     virtual gfxASurface* GetThebesSurface();
 
     // be notified that a some form of drag event needs to go into Gecko
     virtual PRBool DragEvent(unsigned int aMessage, Point aMouseGlobal, UInt16 aKeyModifiers);
 
--- a/widget/src/cocoa/nsCocoaWindow.mm
+++ b/widget/src/cocoa/nsCocoaWindow.mm
@@ -1422,16 +1422,31 @@ NS_IMETHODIMP nsCocoaWindow::SetWindowTi
                                                                     alpha:NS_GET_A(aColor)/255.0]
                               forActiveWindow:(BOOL)aActive];
   }
   return NS_OK;
 
   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)
     return mPopupContentView->GetThebesSurface();
   return nsnull;
 }
 
 NS_IMETHODIMP nsCocoaWindow::BeginSecureKeyboardInput()
--- a/widget/src/xpwidgets/nsBaseWidget.h
+++ b/widget/src/xpwidgets/nsBaseWidget.h
@@ -164,16 +164,21 @@ protected:
 
   virtual nsresult SynthesizeNativeKeyEvent(PRInt32 aNativeKeyboardLayout,
                                             PRInt32 aNativeKeyCode,
                                             PRUint32 aModifierFlags,
                                             const nsAString& aCharacters,
                                             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);
 
 protected: 
   void*             mClientData;
   EVENT_CALLBACK    mEventCallback;
   nsIDeviceContext  *mContext;
--- a/widget/tests/Makefile.in
+++ b/widget/tests/Makefile.in
@@ -61,15 +61,17 @@ include $(topsrcdir)/config/rules.mk
 		test_wheeltransaction.xul \
 		window_wheeltransaction.xul \
 		test_imestate.html \
 		$(NULL)
 
 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)
 endif
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $^ $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/widget/tests/native_mouse_mac_window.xul
@@ -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>
new file mode 100644
--- /dev/null
+++ b/widget/tests/test_native_mouse_mac.xul
@@ -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>