Bug 300904 - Use tracking rects to determine whether the mouse is over a different app and block mouse move events when that's the case. r=josh
☠☠ backed out by e3f00dd9617b ☠ ☠
authorMarkus Stange <mstange@themasta.com>
Wed, 21 Oct 2009 09:04:40 +0200
changeset 34052 3ee95c194798dfee926d4485165dfa650a0a27fc
parent 34051 f9ea7e1fce8daf528149818c2c68ba1fcf7f6128
child 34053 ef1036610eb23ba31ee4e8c095b4b37bacf7182b
child 34069 e3f00dd9617b2918b6ee116026914972eef95a61
push idunknown
push userunknown
push dateunknown
reviewersjosh
bugs300904
milestone1.9.3a1pre
Bug 300904 - Use tracking rects to determine whether the mouse is over a different app and block mouse move events when that's the case. r=josh
widget/src/cocoa/nsChildView.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsCocoaWindow.mm
widget/tests/native_mouse_mac_window.xul
--- a/widget/src/cocoa/nsChildView.h
+++ b/widget/src/cocoa/nsChildView.h
@@ -168,16 +168,25 @@ enum {
   // when handling |draggingUpdated:| messages.
   nsIDragService* mDragService;
   
   // For use with plugins, so that we can support IME in them.  We can't use
   // Cocoa TSM documents (those created and managed by the NSTSMInputContext
   // class) -- for some reason TSMProcessRawKeyEvent() doesn't work with them.
   TSMDocumentID mPluginTSMDoc;
 
+  // This view's tracking rect.
+  NSTrackingRectTag mTrackingRect;
+
+  enum MouseEnterState {
+    eMouseEnterState_Unknown,
+    eMouseEnterState_Inside,
+    eMouseEnterState_Outside
+  } mMouseEnterState;
+
   // Simple gestures support
   //
   // mGestureState is used to detect when Cocoa has called both
   // magnifyWithEvent and rotateWithEvent within the same
   // beginGestureWithEvent and endGestureWithEvent sequence. We
   // discard the spurious gesture event so as not to confuse Gecko.
   //
   // mCumulativeMagnification keeps track of the total amount of
@@ -206,16 +215,20 @@ enum {
 
 // Stop NSView hierarchy being changed during [ChildView drawRect:]
 - (void)delayedTearDown;
 
 - (void)setTransparent:(BOOL)transparent;
 
 - (void)sendFocusEvent:(PRUint32)eventType;
 
+- (MouseEnterState)mouseEnterState;
+
+- (NSTrackingRectTag)trackingRect;
+
 - (void)handleMouseMoved:(NSEvent*)aEvent;
 
 - (void)sendMouseEnterOrExitEvent:(NSEvent*)aEvent
                             enter:(BOOL)aEnter
                              type:(nsMouseEvent::exitType)aType;
 
 #ifndef NP_NO_CARBON
 - (void) processPluginKeyEvent:(EventRef)aKeyEvent;
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -181,16 +181,20 @@ PRUint32 nsChildView::sLastInputEventCou
 
 - (void)setIsPluginView:(BOOL)aIsPlugin;
 - (BOOL)isPluginView;
 - (void)setPluginEventModel:(NPEventModel)eventModel;
 - (NPEventModel)pluginEventModel;
 
 - (BOOL)isRectObscuredBySubview:(NSRect)inRect;
 
+- (void)updateTrackingRect;
+- (void)removeTrackingRect;
+- (void)addTrackingRect;
+
 - (void)processPendingRedraws;
 
 - (PRBool)processKeyDownEvent:(NSEvent*)theEvent keyEquiv:(BOOL)isKeyEquiv;
 
 - (void)maybeInitContextMenuTracking;
 
 + (NSEvent*)makeNewCocoaEventWithType:(NSEventType)type fromEvent:(NSEvent*)theEvent;
 
@@ -1474,25 +1478,58 @@ nsresult nsChildView::SynthesizeNativeMo
   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];
+  NSEvent* event = nil;
+  switch (aNativeMessage) {
+    case NSLeftMouseDown:
+    case NSLeftMouseUp:
+    case NSRightMouseDown:
+    case NSRightMouseUp:
+    case NSMouseMoved:
+    case NSLeftMouseDragged:
+    case NSRightMouseDragged:
+      event = [NSEvent mouseEventWithType:aNativeMessage
+                                 location:windowPoint
+                            modifierFlags:aModifierFlags
+                                timestamp:[NSDate timeIntervalSinceReferenceDate]
+                             windowNumber:[[mView window] windowNumber]
+                                  context:nil
+                              eventNumber:0
+                               clickCount:1
+                                 pressure:0.0];
+      break;
+    case NSMouseEntered:
+    case NSMouseExited:
+    case NSCursorUpdate:
+    {
+      NSTrackingRectTag trackingRect = 0;
+      if ([mView isKindOfClass:[ChildView class]]) {
+        trackingRect = [(ChildView*)mView trackingRect];
+      }
+      event = [NSEvent enterExitEventWithType:aNativeMessage
+                                     location:windowPoint
+                                modifierFlags:aModifierFlags
+                                    timestamp:[NSDate timeIntervalSinceReferenceDate]
+                                 windowNumber:[[mView window] windowNumber]
+                                      context:nil
+                                  eventNumber:0
+                               trackingNumber:trackingRect
+                                     userData:nil];
+    }
+      break;
+    default:
+      NS_WARNING("unhandled message");
+      break;
+  }
 
   if (!event)
     return NS_ERROR_FAILURE;
 
   [NSApp sendEvent:event];
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
@@ -2185,21 +2222,25 @@ NSEvent* gLastDragEvent = nil;
     mMarkedRange.location = NSNotFound;
     mMarkedRange.length = 0;
 
     mLastMouseDownEvent = nil;
     mDragService = nsnull;
 
     mPluginTSMDoc = nil;
 
+    mTrackingRect = 0;
+    mMouseEnterState = eMouseEnterState_Unknown;
+
     mGestureState = eGestureState_None;
     mCumulativeMagnification = 0.0;
     mCumulativeRotation = 0.0;
 
     [self setFocusRingType:NSFocusRingTypeNone];
+    [self addTrackingRect];
   }
   
   // register for things we'll take from other applications
   PR_LOG(sCocoaLog, PR_LOG_ALWAYS, ("ChildView initWithFrame: registering drag types\n"));
   [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType,
                                                           NSStringPboardType,
                                                           NSHTMLPboardType,
                                                           NSURLPboardType,
@@ -2386,26 +2427,30 @@ NSEvent* gLastDragEvent = nil;
 {
   return YES;
 }
 
 - (void)viewWillMoveToWindow:(NSWindow *)newWindow
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
+  [self removeTrackingRect];
+
   if (!newWindow)
     HideChildPluginViews(self);
 
   [super viewWillMoveToWindow:newWindow];
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 - (void)viewDidMoveToWindow
 {
+  [self addTrackingRect];
+
   if ([self window] && [self isPluginView] && mGeckoChild) {
     mGeckoChild->UpdatePluginPort();
   }
 
   [super viewDidMoveToWindow];
 }
 
 - (void)scrollRect:(NSRect)aRect by:(NSSize)offset
@@ -2933,16 +2978,73 @@ static const PRInt32 sShadowInvalidation
   // Clear the gestures state.
   mGestureState = eGestureState_None;
   mCumulativeMagnification = 0.0;
   mCumulativeRotation = 0.0;
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
+- (void)updateTrackingRect
+{
+  [self removeTrackingRect];
+  [self addTrackingRect];
+}
+
+- (void)removeTrackingRect
+{
+  if (mTrackingRect) {
+    [self removeTrackingRect:mTrackingRect];
+    mTrackingRect = 0;
+  }
+}
+
+- (void)addTrackingRect
+{
+  if ([self window] && !NSIsEmptyRect([self bounds])) {
+    mMouseEnterState = eMouseEnterState_Unknown;
+    mTrackingRect = [self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:NO];
+  }
+}
+
+- (void)resetCursorRects
+{
+  [self updateTrackingRect];
+}
+
+- (void)setFrame:(NSRect)aFrame
+{
+  [super setFrame:aFrame];
+  [self updateTrackingRect];
+}
+
+- (void)setBounds:(NSRect)aBounds
+{
+  [super setBounds:aBounds];
+  [self updateTrackingRect];
+}
+
+- (void)mouseEntered:(NSEvent*)aEvent {
+  mMouseEnterState = eMouseEnterState_Inside;
+}
+
+- (void)mouseExited:(NSEvent*)aEvent {
+  mMouseEnterState = eMouseEnterState_Outside;
+}
+
+- (MouseEnterState)mouseEnterState
+{
+  return mMouseEnterState;
+}
+
+- (NSTrackingRectTag)trackingRect
+{
+  return mTrackingRect;
+}
+
 - (void)mouseDown:(NSEvent*)theEvent
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   // If we've already seen this event due to direct dispatch from menuForEvent:
   // just bail; if not, remember it.
   if (mLastMouseDownEvent == theEvent) {
     [mLastMouseDownEvent release];
@@ -6461,17 +6563,29 @@ ChildViewMouseTracker::ViewForEvent(NSEv
 {
   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;
+
+  if (![view isKindOfClass:[ChildView class]])
+    return nil;
+
+  // Now we know the view that the mouse is over, assuming the front-most window
+  // is one of our own windows. However, there might be windows of other
+  // applications floating in front of us, for example the Dock or the
+  // Dashboard. If that's the case, then our view's tracking rect knows about it.
+  ChildView* childView = (ChildView*)view;
+  if ([childView mouseEnterState] == eMouseEnterState_Outside)
+    return nil;
+
+  return childView;
 }
 
 // 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;
--- a/widget/src/cocoa/nsCocoaWindow.mm
+++ b/widget/src/cocoa/nsCocoaWindow.mm
@@ -422,16 +422,17 @@ NS_IMETHODIMP nsCocoaWindow::CreatePopup
   NS_ADDREF(mPopupContentView);
 
   nsIWidget* thisAsWidget = static_cast<nsIWidget*>(this);
   mPopupContentView->Create(thisAsWidget, nsnull, aRect, aHandleEventFunction,
                             aContext, aAppShell, aToolkit, nsnull);
 
   ChildView* newContentView = (ChildView*)mPopupContentView->GetNativeData(NS_NATIVE_WIDGET);
   [mWindow setContentView:newContentView];
+  [newContentView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP nsCocoaWindow::Destroy()
 {
--- a/widget/tests/native_mouse_mac_window.xul
+++ b/widget/tests/native_mouse_mac_window.xul
@@ -278,16 +278,17 @@
           { 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, NSMouseEntered, null, left, []],
         [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) {
@@ -448,16 +449,17 @@
         [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, NSMouseEntered, null, left, []],
         [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 },
         ]],