Bug 1061147, embedding/content process tooltips shouldn't autohide, r=neil
authorNeil Deakin <neil@mozilla.com>
Thu, 09 Oct 2014 12:21:15 -0400
changeset 232925 d32376a43283bef59d0b60531fa0c6f327737dac
parent 232924 78268b8c6e83b77bd37ffaea2c04a74be0063aac
child 232926 c0762a2a6b4215a6522978f1de0d9ddda43d5e45
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil
bugs1061147
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1061147, embedding/content process tooltips shouldn't autohide, r=neil
embedding/browser/nsDocShellTreeOwner.cpp
embedding/browser/nsDocShellTreeOwner.h
--- a/embedding/browser/nsDocShellTreeOwner.cpp
+++ b/embedding/browser/nsDocShellTreeOwner.cpp
@@ -1130,17 +1130,17 @@ NS_IMPL_ISUPPORTS(ChromeTooltipListener,
 // ChromeTooltipListener ctor
 //
 
 ChromeTooltipListener::ChromeTooltipListener(nsWebBrowser* inBrowser,
                                              nsIWebBrowserChrome* inChrome) 
   : mWebBrowser(inBrowser), mWebBrowserChrome(inChrome),
      mTooltipListenerInstalled(false),
      mMouseClientX(0), mMouseClientY(0),
-     mShowingTooltip(false)
+     mShowingTooltip(false), mTooltipShownOnce(false)
 {
   mTooltipTextProvider = do_GetService(NS_TOOLTIPTEXTPROVIDER_CONTRACTID);
   if (!mTooltipTextProvider) {
     nsISupports *pProvider = (nsISupports *) new DefaultTooltipTextProvider;
     mTooltipTextProvider = do_QueryInterface(pProvider);
   }
 } // ctor
 
@@ -1265,21 +1265,27 @@ ChromeTooltipListener::RemoveTooltipList
 
 NS_IMETHODIMP
 ChromeTooltipListener::HandleEvent(nsIDOMEvent* aEvent)
 {
   nsAutoString eventType;
   aEvent->GetType(eventType);
 
   if (eventType.EqualsLiteral("keydown") ||
-      eventType.EqualsLiteral("mousedown") ||
-      eventType.EqualsLiteral("mouseout"))
+      eventType.EqualsLiteral("mousedown")) {
     return HideTooltip();
-  if (eventType.EqualsLiteral("mousemove"))
+  }
+  else if (eventType.EqualsLiteral("mouseout")) {
+    // Reset flag so that tooltip will display on the next MouseMove
+    mTooltipShownOnce = false;
+    return HideTooltip();
+  }
+  else if (eventType.EqualsLiteral("mousemove")) {
     return MouseMove(aEvent);
+  }
 
   NS_ERROR("Unexpected event type");
   return NS_OK;
 }
 
 //
 // MouseMove
 //
@@ -1297,45 +1303,53 @@ ChromeTooltipListener::MouseMove(nsIDOME
   // timer callback. On win32, we'll get a MouseMove event even when a popup goes away --
   // even when the mouse doesn't change position! To get around this, we make sure the
   // mouse has really moved before proceeding.
   int32_t newMouseX, newMouseY;
   mouseEvent->GetClientX(&newMouseX);
   mouseEvent->GetClientY(&newMouseY);
   if ( mMouseClientX == newMouseX && mMouseClientY == newMouseY )
     return NS_OK;
+
+  // Filter out minor mouse movements.
+  if (mShowingTooltip &&
+      (abs(mMouseClientX - newMouseX) <= kTooltipMouseMoveTolerance) &&
+      (abs(mMouseClientY - newMouseY) <= kTooltipMouseMoveTolerance))
+    return NS_OK;
+
   mMouseClientX = newMouseX; mMouseClientY = newMouseY;
   mouseEvent->GetScreenX(&mMouseScreenX);
   mouseEvent->GetScreenY(&mMouseScreenY);
 
-  // We want to close the tip if it is being displayed and the mouse moves. Recall 
-  // that |mShowingTooltip| is set when the popup is showing. Furthermore, as the mouse
-  // moves, we want to make sure we reset the timer to show it, so that the delay
-  // is from when the mouse stops moving, not when it enters the element.
-  if ( mShowingTooltip )
-    return HideTooltip();
-  if ( mTooltipTimer )
+  if ( mTooltipTimer ) {
     mTooltipTimer->Cancel();
+  }
 
-  mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1");
-  if ( mTooltipTimer ) {
-    nsCOMPtr<EventTarget> eventTarget = aMouseEvent->InternalDOMEvent()->GetTarget();
-    if ( eventTarget )
-      mPossibleTooltipNode = do_QueryInterface(eventTarget);
-    if ( mPossibleTooltipNode ) {
-      nsresult rv =
-        mTooltipTimer->InitWithFuncCallback(sTooltipCallback, this,
-          LookAndFeel::GetInt(LookAndFeel::eIntID_TooltipDelay, 500),
-          nsITimer::TYPE_ONE_SHOT);
-      if (NS_FAILED(rv))
-        mPossibleTooltipNode = nullptr;
+  if (!mShowingTooltip && !mTooltipShownOnce) {
+    mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1");
+    if ( mTooltipTimer ) {
+      nsCOMPtr<EventTarget> eventTarget = aMouseEvent->InternalDOMEvent()->GetTarget();
+      if ( eventTarget )
+        mPossibleTooltipNode = do_QueryInterface(eventTarget);
+      if ( mPossibleTooltipNode ) {
+        nsresult rv =
+          mTooltipTimer->InitWithFuncCallback(sTooltipCallback, this,
+            LookAndFeel::GetInt(LookAndFeel::eIntID_TooltipDelay, 500),
+            nsITimer::TYPE_ONE_SHOT);
+        if (NS_FAILED(rv))
+          mPossibleTooltipNode = nullptr;
+      }
     }
+    else
+      NS_WARNING ( "Could not create a timer for tooltip tracking" );
   }
-  else
-    NS_WARNING ( "Could not create a timer for tooltip tracking" );
+  else {
+    mTooltipShownOnce = true;
+    return HideTooltip();
+  }
 
   return NS_OK;
 
 } // MouseMove
 
 
 //
 // ShowTooltip
@@ -1374,20 +1388,16 @@ ChromeTooltipListener::HideTooltip()
   
   // shut down the relevant timers
   if ( mTooltipTimer ) {
     mTooltipTimer->Cancel();
     mTooltipTimer = nullptr;
     // release tooltip target
     mPossibleTooltipNode = nullptr;
   }
-  if ( mAutoHideTimer ) {
-    mAutoHideTimer->Cancel();
-    mAutoHideTimer = nullptr;
-  }
 
   // if we're showing the tip, tell the chrome to hide it
   if ( mShowingTooltip ) {
     nsCOMPtr<nsITooltipListener> tooltipListener ( do_QueryInterface(mWebBrowserChrome) );
     if ( tooltipListener ) {
       rv = tooltipListener->OnHideTooltip ( );
       if ( NS_SUCCEEDED(rv) )
         mShowingTooltip = false;
@@ -1456,72 +1466,30 @@ ChromeTooltipListener::sTooltipCallback(
     if (self->mTooltipTextProvider) {
       bool textFound = false;
 
       self->mTooltipTextProvider->GetNodeText(
           self->mPossibleTooltipNode, getter_Copies(tooltipText), &textFound);
       
       if (textFound) {
         nsString tipText(tooltipText);
-        self->CreateAutoHideTimer();
         nsIntPoint screenDot = widget->WidgetToScreenOffset();
         self->ShowTooltip (self->mMouseScreenX - screenDot.x,
                            self->mMouseScreenY - screenDot.y,
                            tipText);
       }
     }
     
     // release tooltip target if there is one, NO MATTER WHAT
     self->mPossibleTooltipNode = nullptr;
   } // if "self" data valid
   
 } // sTooltipCallback
 
 
-//
-// CreateAutoHideTimer
-//
-// Create a new timer to see if we should auto-hide. It's ok if this fails.
-//
-void
-ChromeTooltipListener::CreateAutoHideTimer()
-{
-  // just to be anal (er, safe)
-  if ( mAutoHideTimer ) {
-    mAutoHideTimer->Cancel();
-    mAutoHideTimer = nullptr;
-  }
-  
-  mAutoHideTimer = do_CreateInstance("@mozilla.org/timer;1");
-  if ( mAutoHideTimer )
-    mAutoHideTimer->InitWithFuncCallback(sAutoHideCallback, this, kTooltipAutoHideTime, 
-                                         nsITimer::TYPE_ONE_SHOT);
-
-} // CreateAutoHideTimer
-
-
-//
-// sAutoHideCallback
-//
-// This fires after a tooltip has been open for a certain length of time. Just tell
-// the listener to close the popup. We don't have to worry, because HideTooltip() can
-// be called multiple times, even if the tip has already been closed.
-//
-void
-ChromeTooltipListener::sAutoHideCallback(nsITimer *aTimer, void* aListener)
-{
-  ChromeTooltipListener* self = static_cast<ChromeTooltipListener*>(aListener);
-  if ( self )
-    self->HideTooltip();
-
-  // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling ClosePopup();
-  
-} // sAutoHideCallback
-
-
 NS_IMPL_ISUPPORTS(ChromeContextMenuListener, nsIDOMEventListener)
 
 
 //
 // ChromeTooltipListener ctor
 //
 ChromeContextMenuListener::ChromeContextMenuListener(nsWebBrowser* inBrowser, nsIWebBrowserChrome* inChrome ) 
   : mContextMenuListenerInstalled(false),
--- a/embedding/browser/nsDocShellTreeOwner.h
+++ b/embedding/browser/nsDocShellTreeOwner.h
@@ -161,17 +161,18 @@ public:
     // the embedding chrome implements.
   NS_IMETHOD AddChromeListeners();
   NS_IMETHOD RemoveChromeListeners();
 
 private:
 
     // various delays for tooltips
   enum {
-    kTooltipAutoHideTime = 5000        // 5000ms = 5 seconds
+    kTooltipAutoHideTime = 5000,       // 5000ms = 5 seconds
+    kTooltipMouseMoveTolerance = 7     // 7 pixel tolerance for mousemove event
   };
 
   NS_IMETHOD AddTooltipListener();
   NS_IMETHOD RemoveTooltipListener();
 
   NS_IMETHOD ShowTooltip ( int32_t inXCoords, int32_t inYCoords, const nsAString & inTipText ) ;
   NS_IMETHOD HideTooltip ( ) ;
 
@@ -187,21 +188,17 @@ private:
 
   bool mTooltipListenerInstalled;
 
   nsCOMPtr<nsITimer> mTooltipTimer;
   static void sTooltipCallback ( nsITimer* aTimer, void* aListener ) ;
   int32_t mMouseClientX, mMouseClientY;       // mouse coordinates for last mousemove event we saw
   int32_t mMouseScreenX, mMouseScreenY;       // mouse coordinates for tooltip event
   bool mShowingTooltip;
-
-    // a timer for auto-hiding the tooltip after a certain delay
-  nsCOMPtr<nsITimer> mAutoHideTimer;
-  static void sAutoHideCallback ( nsITimer* aTimer, void* aListener ) ;
-  void CreateAutoHideTimer ( ) ;
+  bool mTooltipShownOnce;
 
     // The node hovered over that fired the timer. This may turn into the node that
     // triggered the tooltip, but only if the timer ever gets around to firing.
     // This is a strong reference, because the tooltip content can be destroyed while we're
     // waiting for the tooltip to pup up, and we need to detect that.
     // It's set only when the tooltip timer is created and launched. The timer must
     // either fire or be cancelled (or possibly released?), and we release this
     // reference in each of those cases. So we don't leak.