diff --git a/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp b/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp index 1a64826a5b9..c0dbfba9ad0 100644 --- a/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp +++ b/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.cpp @@ -18,6 +18,8 @@ * * Contributor(s): * Travis Bogard + * Adam Lock + * Mike Pinkerton */ // Local Includes @@ -31,6 +33,7 @@ // Interfaces needed to be included #include "nsIContextMenuListener.h" +#include "nsITooltipListener.h" #include "nsIPrivateDOMEvent.h" #include "nsIDOMNode.h" #include "nsIDOMNodeList.h" @@ -47,7 +50,6 @@ #include "nsIFocusController.h" #include "nsIDOMWindowInternal.h" -// CIDs //***************************************************************************** //*** nsDocShellTreeOwner: Object Management @@ -61,13 +63,17 @@ nsDocShellTreeOwner::nsDocShellTreeOwner() : mOwnerProgressListener(nsnull), mOwnerWin(nsnull), mOwnerRequestor(nsnull), - mMouseListenerActive(PR_FALSE) + mChromeListener(nsnull) { NS_INIT_REFCNT(); } nsDocShellTreeOwner::~nsDocShellTreeOwner() { + if ( mChromeListener ) { + mChromeListener->RemoveChromeListeners(); + NS_RELEASE(mChromeListener); + } } //***************************************************************************** @@ -75,35 +81,7 @@ nsDocShellTreeOwner::~nsDocShellTreeOwner() //***************************************************************************** NS_IMPL_ADDREF(nsDocShellTreeOwner) - //NS_IMPL_RELEASE(nsDocShellTreeOwner) - -// Custom release to break a circular dependancy cause -// by an refcount held by layout. - -NS_IMETHODIMP_(nsrefcnt) -nsDocShellTreeOwner::Release(void) -{ - nsrefcnt count = PR_AtomicDecrement((PRInt32 *)&mRefCnt); - NS_LOG_RELEASE(this, count, "nsDocShellTreeOwner"); - - if (count == 1 && mMouseListenerActive) - { - // layout holds a reference via the MouseListener. - // we will remove ourselves, layout will call release - // on us. - RemoveMouseListener(); - return 0; - } - - if (count == 0) - { - NS_DELETEXPCOM(this); - return 0; - } - - return mRefCnt; -} - +NS_IMPL_RELEASE(nsDocShellTreeOwner) NS_INTERFACE_MAP_BEGIN(nsDocShellTreeOwner) @@ -112,8 +90,6 @@ NS_INTERFACE_MAP_BEGIN(nsDocShellTreeOwner) NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) - NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) - NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener) NS_INTERFACE_MAP_ENTRY(nsICDocShellTreeOwner) NS_INTERFACE_MAP_END @@ -485,7 +461,7 @@ nsDocShellTreeOwner::OnProgressChange(nsIWebProgress* aProgress, // In the absence of DOM document creation event, this method is the // most convenient place to install the mouse listener on the // DOM document. - AddMouseListener(); + AddChromeListeners(); if(!mOwnerProgressListener) return NS_OK; @@ -554,9 +530,11 @@ nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress *aWebProgress, void nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) { - if (aWebBrowser == nsnull) - { - RemoveMouseListener(); + if ( !aWebBrowser ) { + if ( mChromeListener ) { + mChromeListener->RemoveChromeListeners(); + NS_RELEASE(mChromeListener); + } } mWebBrowser = aWebBrowser; } @@ -568,35 +546,31 @@ nsWebBrowser* nsDocShellTreeOwner::WebBrowser() NS_IMETHODIMP nsDocShellTreeOwner::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) { - if(aTreeOwner) - { + if(aTreeOwner) { nsCOMPtr webBrowserChrome(do_GetInterface(aTreeOwner)); NS_ENSURE_TRUE(webBrowserChrome, NS_ERROR_INVALID_ARG); NS_ENSURE_SUCCESS(SetWebBrowserChrome(webBrowserChrome), NS_ERROR_INVALID_ARG); mTreeOwner = aTreeOwner; - } + } else if(mWebBrowserChrome) mTreeOwner = nsnull; - else - { + else { mTreeOwner = nsnull; NS_ENSURE_SUCCESS(SetWebBrowserChrome(nsnull), NS_ERROR_FAILURE); - } + } return NS_OK; } NS_IMETHODIMP nsDocShellTreeOwner::SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome) { - if(!aWebBrowserChrome) - { + if(!aWebBrowserChrome) { mWebBrowserChrome = nsnull; mOwnerWin = nsnull; mOwnerProgressListener = nsnull; mOwnerRequestor = nsnull; - } - else - { + } + else { nsCOMPtr baseWin(do_QueryInterface(aWebBrowserChrome)); nsCOMPtr requestor(do_QueryInterface(aWebBrowserChrome)); nsCOMPtr progressListener(do_QueryInterface(aWebBrowserChrome)); @@ -607,22 +581,90 @@ NS_IMETHODIMP nsDocShellTreeOwner::SetWebBrowserChrome(nsIWebBrowserChrome* aWeb mOwnerWin = baseWin; mOwnerProgressListener = progressListener; mOwnerRequestor = requestor; - } + } return NS_OK; } -/////////////////////////////////////////////////////////////////////////////// - -NS_IMETHODIMP nsDocShellTreeOwner::AddMouseListener() +// +// AddChromeListeners +// +// Hook up things to the chrome like context menus and tooltips, if the chrome +// has implemented the right interfaces. +// +NS_IMETHODIMP +nsDocShellTreeOwner :: AddChromeListeners ( ) { - NS_ENSURE_TRUE(mWebBrowser, NS_ERROR_FAILURE); - - if (mMouseListenerActive) - { - return NS_OK; + nsresult rv = NS_OK; + + if ( !mChromeListener ) { + nsCOMPtr contextListener ( do_QueryInterface(mWebBrowserChrome) ); + nsCOMPtr tooltipListener ( do_QueryInterface(mWebBrowserChrome) ); + if ( contextListener || tooltipListener ) { + mChromeListener = new ChromeListener ( mWebBrowser, mWebBrowserChrome ); + if ( mChromeListener ) { + NS_ADDREF(mChromeListener); + rv = mChromeListener->AddChromeListeners(); + } + else + rv = NS_ERROR_OUT_OF_MEMORY; } + } + + return rv; + +} // AddChromeListeners + +#ifdef XP_MAC +#pragma mark - +#endif + + +NS_IMPL_ADDREF(ChromeListener) +NS_IMPL_RELEASE(ChromeListener) + +NS_INTERFACE_MAP_BEGIN(ChromeListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMMouseListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMMouseListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMMouseListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMMouseMotionListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMKeyListener) +NS_INTERFACE_MAP_END + + +// +// ChromeListener ctor +// +ChromeListener :: ChromeListener ( nsWebBrowser* inBrowser, nsIWebBrowserChrome* inChrome ) + : mWebBrowser(inBrowser), mWebBrowserChrome(inChrome), + mContextMenuListenerInstalled(PR_FALSE), mTooltipListenerInstalled(PR_FALSE), + mShowingTooltip(PR_FALSE), mMouseClientX(0), mMouseClientY(0) +{ + NS_INIT_REFCNT(); +} // ctor + + +// +// ChromeListener dtor +// +ChromeListener :: ~ChromeListener ( ) +{ + RemoveChromeListeners(); + +} // dtor + + +// +// AddChromeListeners +// +// Hook up things to the chrome like context menus and tooltips, if the chrome +// has implemented the right interfaces. +// +NS_IMETHODIMP +ChromeListener :: AddChromeListeners ( ) +{ + if ( !mEventReceiver ) { nsCOMPtr domWindow; mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); @@ -637,102 +679,228 @@ NS_IMETHODIMP nsDocShellTreeOwner::AddMouseListener() piWin->GetChromeEventHandler(getter_AddRefs(chromeHandler)); NS_ENSURE_TRUE(chromeHandler, NS_ERROR_FAILURE); - // Subscribe to mouse events mEventReceiver = do_QueryInterface(chromeHandler); - if (mEventReceiver) - { - nsresult rv; - nsIDOMMouseListener *pListener = NS_STATIC_CAST(nsIDOMMouseListener *, this); - rv = mEventReceiver->AddEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseListener)); - if (NS_SUCCEEDED(rv)) - { - mMouseListenerActive = PR_TRUE; - } - } + } + + // Register the appropriate events for context menus, but only if + // the embedding chrome cares. + nsresult rv = NS_OK; + nsCOMPtr contextListener ( do_QueryInterface(mWebBrowserChrome) ); + if ( contextListener && !mContextMenuListenerInstalled ) { + rv = AddContextMenuListener(); + if ( NS_FAILED(rv) ) + return rv; + } + + // Register the appropriate events for tooltips, but only if + // the embedding chrome cares. + nsCOMPtr tooltipListener ( do_QueryInterface(mWebBrowserChrome) ); + if ( tooltipListener && !mTooltipListenerInstalled ) { + rv = AddTooltipListener(); + if ( NS_FAILED(rv) ) + return rv; + } + + return rv; + +} // AddChromeListeners + +// +// AddContextMenuListener +// +// Subscribe to the events that will allow us to track context menus. Bascially, this +// is just the mouseDown event. +// +NS_IMETHODIMP +ChromeListener :: AddContextMenuListener() +{ + if (mEventReceiver) { + nsIDOMMouseListener *pListener = NS_STATIC_CAST(nsIDOMMouseListener *, this); + nsresult rv = mEventReceiver->AddEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseListener)); + if (NS_SUCCEEDED(rv)) + mContextMenuListenerInstalled = PR_TRUE; + } + + return NS_OK; +} + + +// +// AddTooltipListener +// +// Subscribe to the events that will allow us to track tooltips. We need "mouse" for mouseExit, +// "mouse motion" for mouseMove, and "key" for keyDown. As we add the listeners, keep track +// of how many succeed so we can clean up correctly in Release(). +// +NS_IMETHODIMP +ChromeListener :: AddTooltipListener() +{ + if (mEventReceiver) { + nsIDOMMouseListener *pListener = NS_STATIC_CAST(nsIDOMMouseListener *, this); + nsresult rv = mEventReceiver->AddEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseListener)); + nsresult rv2 = mEventReceiver->AddEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseMotionListener)); + nsresult rv3 = mEventReceiver->AddEventListenerByIID(pListener, NS_GET_IID(nsIDOMKeyListener)); + + // if all 3 succeed, we're a go! + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && NS_SUCCEEDED(rv3)) + mTooltipListenerInstalled = PR_TRUE; + } + + return NS_OK; +} + + +// +// RemoveChromeListeners +// +// Unsubscribe from the various things we've hooked up to the window root, including +// context menus and tooltips for starters. +// +NS_IMETHODIMP +ChromeListener :: RemoveChromeListeners ( ) +{ + if ( mContextMenuListenerInstalled ) + RemoveContextMenuListener(); + + if ( mTooltipListenerInstalled ) + RemoveTooltipListener(); + + // because of a bug in the EventListenerManager with listeners registered to multiple + // IID's, we might not have release all refs when removing the listeners. Clearing + // our ref to the listener will free them, but use a deathgrip to make sure we don't + // go away until we're truly ready. + nsCOMPtr kungFuDeathGrip ( this ); + mEventReceiver = nsnull; + + // it really doesn't matter if these fail... + return NS_OK; + +} // RemoveChromeListeners + + +// +// RemoveContextMenuListener +// +// Unsubscribe from all the various context menu events that we were listening to. +// Bascially, this is just the mouseDown event. +// +NS_IMETHODIMP +ChromeListener :: RemoveContextMenuListener() +{ + if (mEventReceiver) { + // Removing the mouse listener may cause this instance to + // be deleted... So, keep an extra reference to defer destruction.. + nsCOMPtr kungFuDeathGrip(this); + nsIDOMMouseListener *pListener = NS_STATIC_CAST(nsIDOMMouseListener *, this); + nsresult rv = mEventReceiver->RemoveEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseListener)); + if (NS_SUCCEEDED(rv)) + mContextMenuListenerInstalled = PR_FALSE; + } + + return NS_OK; +} + + +// +// RemoveTooltipListener +// +// Unsubscribe from all the various tooltip events that we were listening to +// +NS_IMETHODIMP +ChromeListener :: RemoveTooltipListener() +{ + if (mEventReceiver) { + // Removing the mouse listener may cause this instance to + // be deleted... So, keep an extra reference to defer destruction.. + nsCOMPtr kungFuDeathGrip(this); + nsIDOMMouseListener *pListener = NS_STATIC_CAST(nsIDOMMouseListener *, this); + nsresult rv = mEventReceiver->RemoveEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseListener)); + nsresult rv2 = mEventReceiver->RemoveEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseMotionListener)); + nsresult rv3 = mEventReceiver->RemoveEventListenerByIID(pListener, NS_GET_IID(nsIDOMKeyListener)); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && NS_SUCCEEDED(rv3)) + mTooltipListenerInstalled = PR_FALSE; + } + + return NS_OK; +} + + +// +// KeyDown +// +// When the user starts typing, they generaly don't want to see any messy wax +// builup. Hide the tooltip. +// +nsresult +ChromeListener::KeyDown(nsIDOMEvent* aMouseEvent) +{ + // make sure we're a tooltip. if not, bail. Just to be safe. + if ( ! mTooltipListenerInstalled ) return NS_OK; -} + + return HideTooltip(); + +} // KeyDown -NS_IMETHODIMP nsDocShellTreeOwner::RemoveMouseListener() +// +// KeyUp +// KeyPress +// +// We can ignore these as they are already handled by KeyDown +// +nsresult +ChromeListener::KeyUp(nsIDOMEvent* aMouseEvent) { - NS_ENSURE_TRUE(mWebBrowser, NS_ERROR_FAILURE); + return NS_OK; + +} // KeyUp - // Unsubscribe from mouse events - if (mEventReceiver) - { - // Removing the mouse listener may cause this instance to - // be deleted... So, keep an extra reference to defer destruction.. - nsCOMPtr kungFuDeathGrip(this); - if (mEventReceiver) - { - nsresult rv; - nsIDOMMouseListener *pListener = NS_STATIC_CAST(nsIDOMMouseListener *, this); - rv = mEventReceiver->RemoveEventListenerByIID(pListener, NS_GET_IID(nsIDOMMouseListener)); - if (NS_SUCCEEDED(rv)) - mMouseListenerActive = PR_FALSE; - } - mEventReceiver = nsnull; - } - - return NS_OK; -} - - -//***************************************************************************** -// nsDocShellTreeOwner::nsIDOMMouseListener -//***************************************************************************** - -nsresult nsDocShellTreeOwner::HandleEvent(nsIDOMEvent* aEvent) +nsresult +ChromeListener::KeyPress(nsIDOMEvent* aMouseEvent) { - return NS_OK; -} + return NS_OK; + +} // KeyPress -nsresult nsDocShellTreeOwner::MouseDown(nsIDOMEvent* aMouseEvent) +// +// MouseDown +// +// Do the processing for context menus, if this is a right-mouse click +// +nsresult +ChromeListener::MouseDown(nsIDOMEvent* aMouseEvent) { - // Don't bother going any further if no one is interested in context menu events - nsCOMPtr menuListener(do_QueryInterface(mWebBrowserChrome)); - if (!menuListener) - { - return NS_OK; - } - nsCOMPtr mouseEvent (do_QueryInterface(aMouseEvent)); if (!mouseEvent) - { return NS_OK; - } + // if the mouse goes down for any reason when a tooltip is showing, + // we want to hide it, but continue processing the event. + if ( mShowingTooltip ) + HideTooltip(); + // Test for right mouse button click PRUint16 buttonNumber; nsresult res = mouseEvent->GetButton(&buttonNumber); if (NS_FAILED(res)) - { return res; - } if (buttonNumber != 3) // 3 is the magic number - { return NS_OK; - } nsCOMPtr targetNode; res = aMouseEvent->GetTarget(getter_AddRefs(targetNode)); if (NS_FAILED(res)) - { return res; - } if (!targetNode) - { return NS_ERROR_NULL_POINTER; - } nsCOMPtr targetDOMnode; nsCOMPtr node = do_QueryInterface(targetNode); if (!node) - { return NS_OK; - } // Find the first node to be an element starting with this node and // working up through its parents. @@ -806,35 +974,281 @@ nsresult nsDocShellTreeOwner::MouseDown(nsIDOMEvent* aMouseEvent) } while (node); // Tell the listener all about the event - menuListener->OnShowContextMenu(flags, aMouseEvent, targetDOMnode); + nsCOMPtr menuListener(do_QueryInterface(mWebBrowserChrome)); + if ( menuListener ) + menuListener->OnShowContextMenu(flags, aMouseEvent, targetDOMnode); return NS_OK; -} -nsresult nsDocShellTreeOwner::MouseUp(nsIDOMEvent* aMouseEvent) +} // MouseDown + + +nsresult +ChromeListener::MouseUp(nsIDOMEvent* aMouseEvent) { return NS_OK; } -nsresult nsDocShellTreeOwner::MouseClick(nsIDOMEvent* aMouseEvent) +nsresult +ChromeListener::MouseClick(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +nsresult +ChromeListener::MouseDblClick(nsIDOMEvent* aMouseEvent) +{ + return NS_OK; +} + +nsresult +ChromeListener::MouseOver(nsIDOMEvent* aMouseEvent) { return NS_OK; } -nsresult nsDocShellTreeOwner::MouseDblClick(nsIDOMEvent* aMouseEvent) +// +// MouseOut +// +// If we're responding to tooltips, hide the tip whenever the mouse leaves +// the area it was in. +nsresult +ChromeListener::MouseOut(nsIDOMEvent* aMouseEvent) { - return NS_OK; -} - -nsresult nsDocShellTreeOwner::MouseOver(nsIDOMEvent* aMouseEvent) -{ - return NS_OK; + // make sure we're a tooltip. if not, bail. Just to be safe. + if ( ! mTooltipListenerInstalled ) + return NS_OK; + + return HideTooltip(); } -nsresult nsDocShellTreeOwner::MouseOut(nsIDOMEvent* aMouseEvent) +// +// MouseMove +// +// If we're a tooltip, fire off a timer to see if a tooltip should be shown. If the +// timer fires, we cache the node in |mPossibleTooltipNode|. +// +nsresult +ChromeListener::MouseMove(nsIDOMEvent* aMouseEvent) { - return NS_OK; -} + // make sure we're a tooltip. if not, bail. Just to be safe. + if ( ! mTooltipListenerInstalled ) + return NS_OK; + + nsCOMPtr mouseEvent ( do_QueryInterface(aMouseEvent) ); + if (!mouseEvent) + return NS_OK; + + // stash the coordinates of the event so that we can still get back to it from within the + // 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. + PRInt32 newMouseX, newMouseY; + mouseEvent->GetClientX(&newMouseX); + mouseEvent->GetClientY(&newMouseY); + if ( mMouseClientX == newMouseX && mMouseClientY == newMouseY ) + return NS_OK; + mMouseClientX = newMouseX; mMouseClientY = newMouseY; + + // 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 ) + mTooltipTimer->Cancel(); + + mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1"); + if ( mTooltipTimer ) { + nsCOMPtr eventTarget; + aMouseEvent->GetTarget(getter_AddRefs(eventTarget)); + if ( eventTarget ) + mPossibleTooltipNode = do_QueryInterface(eventTarget); + if ( mPossibleTooltipNode ) { + nsresult rv = mTooltipTimer->Init(sTooltipCallback, this, kTooltipShowTime, NS_PRIORITY_HIGH); + if (NS_FAILED(rv)) + mPossibleTooltipNode = nsnull; + } + } + else + NS_WARNING ( "Could not create a timer for tooltip tracking" ); + + return NS_OK; + +} // MouseMove + + +// +// ShowTooltip +// +// Tell the registered chrome that they should show the tooltip +// +NS_IMETHODIMP +ChromeListener :: ShowTooltip ( PRInt32 inXCoords, PRInt32 inYCoords, const nsAReadableString & inTipText ) +{ + nsresult rv = NS_OK; + + // do the work to call the client + nsCOMPtr tooltipListener ( do_QueryInterface(mWebBrowserChrome) ); + if ( tooltipListener ) { + rv = tooltipListener->OnShowTooltip ( inXCoords, inYCoords, nsPromiseFlatString(inTipText).get() ); + if ( NS_SUCCEEDED(rv) ) + mShowingTooltip = PR_TRUE; + } + + return rv; + +} // ShowTooltip + + +// +// HideTooltip +// +// Tell the registered chrome that they should rollup the tooltip +// NOTE: This routine is safe to call even if the popup is already closed. +// +NS_IMETHODIMP +ChromeListener :: HideTooltip ( ) +{ + nsresult rv = NS_OK; + + // shut down the relevant timers + if ( mTooltipTimer ) { + mTooltipTimer->Cancel(); + mTooltipTimer = nsnull; + // release tooltip target + mPossibleTooltipNode = nsnull; + } + if ( mAutoHideTimer ) { + mAutoHideTimer->Cancel(); + mAutoHideTimer = nsnull; + } + + // if we're showing the tip, tell the chrome to hide it + if ( mShowingTooltip ) { + nsCOMPtr tooltipListener ( do_QueryInterface(mWebBrowserChrome) ); + if ( tooltipListener ) { + rv = tooltipListener->OnHideTooltip ( ); + if ( NS_SUCCEEDED(rv) ) + mShowingTooltip = PR_FALSE; + } + } + + return rv; + +} // HideTooltip + + +// +// FindTitleText +// +// Determine if there is a TITLE attribute. Checks both the XLINK namespace, and no +// namespace. Returns |PR_TRUE| if there is, and sets the text in |outText|. +// +PRBool +ChromeListener :: FindTitleText ( nsIDOMNode* inNode, nsAWritableString & outText ) +{ + PRBool found = PR_FALSE; + + nsCOMPtr current ( inNode ); + while ( !found && current ) { + nsCOMPtr currElement ( do_QueryInterface(current) ); + if ( currElement ) { + // first try the normal title attribute... + currElement->GetAttribute(NS_LITERAL_STRING("title"), outText); + if ( outText.Length() ) + found = PR_TRUE; + else { + // ...ok, that didn't work, try it in the XLink namespace + currElement->GetAttributeNS(NS_LITERAL_STRING("http://www.w3.org/1999/xlink"), NS_LITERAL_STRING("title"), outText); + if ( outText.Length() ) + found = PR_TRUE; + } + } + + // not found here, walk up to the parent and keep trying + if ( !found ) { + nsCOMPtr temp ( current ); + temp->GetParentNode(getter_AddRefs(current)); + } + } // while not found + + return found; + +} // FindTitleText + + +// +// sTooltipCallback +// +// A timer callback, fired when the mouse has hovered inside of a frame for the +// appropriate amount of time. Getting to this point means that we should show the +// tooltip, but only after we determine there is an appropriate TITLE element. +// +// This relies on certain things being cached into the |aChromeListener| object passed to +// us by the timer: +// -- the x/y coordinates of the mouse (mMouseClientY, mMouseClientX) +// -- the dom node the user hovered over (mPossibleTooltipNode) +// +void +ChromeListener :: sTooltipCallback (nsITimer *aTimer, void *aChromeListener) +{ + ChromeListener* self = NS_STATIC_CAST(ChromeListener*, aChromeListener); + if ( self && self->mPossibleTooltipNode ) { + // if there is a TITLE tag, show the tip and fire off a timer to auto-hide it + nsAutoString tooltipText; + if ( self->FindTitleText(self->mPossibleTooltipNode, tooltipText) ) { + self->CreateAutoHideTimer ( ); + self->ShowTooltip ( self->mMouseClientX, self->mMouseClientY, tooltipText ); + } + + // release tooltip target if there is one, NO MATTER WHAT + self->mPossibleTooltipNode = nsnull; + } // if "self" data valid + +} // sTooltipCallback + + +// +// CreateAutoHideTimer +// +// Create a new timer to see if we should auto-hide. It's ok if this fails. +// +void +ChromeListener :: CreateAutoHideTimer ( ) +{ + // just to be anal (er, safe) + if ( mAutoHideTimer ) { + mAutoHideTimer->Cancel(); + mAutoHideTimer = nsnull; + } + + mAutoHideTimer = do_CreateInstance("@mozilla.org/timer;1"); + if ( mAutoHideTimer ) + mAutoHideTimer->Init(sAutoHideCallback, this, kTooltipAutoHideTime, NS_PRIORITY_HIGH); + +} // 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 +ChromeListener :: sAutoHideCallback ( nsITimer *aTimer, void* aListener ) +{ + ChromeListener* self = NS_STATIC_CAST(ChromeListener*, aListener); + if ( self ) + self->HideTooltip(); + + // NOTE: |aTimer| and |self->mAutoHideTimer| are invalid after calling ClosePopup(); + +} // sAutoHideCallback + diff --git a/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.h b/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.h index 6a6305b07c1..239274864a4 100644 --- a/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.h +++ b/mozilla/embedding/browser/webBrowser/nsDocShellTreeOwner.h @@ -37,10 +37,14 @@ #include "nsIDOMDocument.h" #include "nsIChromeEventHandler.h" #include "nsIDOMEventReceiver.h" +#include "nsIDOMKeyListener.h" +#include "nsIDOMMouseMotionListener.h" +#include "nsITimer.h" #include "nsCommandHandler.h" class nsWebBrowser; +class ChromeListener; // {6D10C180-6888-11d4-952B-0020183BF181} #define NS_ICDOCSHELLTREEOWNER_IID \ @@ -63,7 +67,6 @@ class nsDocShellTreeOwner : public nsIDocShellTreeOwner, public nsIBaseWindow, public nsIInterfaceRequestor, public nsIWebProgressListener, - public nsIDOMMouseListener, public nsICDocShellTreeOwner { friend class nsWebBrowser; @@ -77,15 +80,6 @@ public: NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSIWEBPROGRESSLISTENER - // nsIDOMMouseListener - virtual nsresult HandleEvent(nsIDOMEvent* aEvent); - virtual nsresult MouseDown(nsIDOMEvent* aMouseEvent); - virtual nsresult MouseUp(nsIDOMEvent* aMouseEvent); - virtual nsresult MouseClick(nsIDOMEvent* aMouseEvent); - virtual nsresult MouseDblClick(nsIDOMEvent* aMouseEvent); - virtual nsresult MouseOver(nsIDOMEvent* aMouseEvent); - virtual nsresult MouseOut(nsIDOMEvent* aMouseEvent); - protected: nsDocShellTreeOwner(); virtual ~nsDocShellTreeOwner(); @@ -96,9 +90,7 @@ protected: NS_IMETHOD SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner); NS_IMETHOD SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome); - nsCOMPtr mEventReceiver; - NS_IMETHOD AddMouseListener(); - NS_IMETHOD RemoveMouseListener(); + NS_IMETHOD AddChromeListeners(); protected: @@ -111,9 +103,115 @@ protected: nsIWebProgressListener* mOwnerProgressListener; nsIBaseWindow* mOwnerWin; nsIInterfaceRequestor* mOwnerRequestor; - PRBool mMouseListenerActive; + + // the object that listens for chrome events like context menus and tooltips. + // It is a separate object to avoid circular references between |this| + // and the DOM. This is a strong, owning ref. + ChromeListener* mChromeListener; }; + +// +// class ChromeListener +// +// The class that listens to the chrome events and tells the embedding +// chrome to show things like context menus or tooltips, as appropriate. +// Handles registering itself with the DOM with AddChromeListeners() +// and removing itself with RemoveChromeListeners(). +// +// NOTE: Because of a leak/bug in the EventListenerManager having to do with +// a single listener with multiple IID's, we need to addref/release +// this class instead of just owning it and calling delete when we +// know we're done. This also forces us to make RemoveChromeListeners() +// public when it doesn't need to be. This is a reminder to fix when I +// fix the ELM bug. (pinkerton) +// +class ChromeListener : public nsIDOMMouseListener, + public nsIDOMKeyListener, + public nsIDOMMouseMotionListener +{ +public: + NS_DECL_ISUPPORTS + + ChromeListener ( nsWebBrowser* inBrowser, nsIWebBrowserChrome* inChrome ) ; + ~ChromeListener ( ) ; + + // nsIDOMMouseListener + virtual nsresult HandleEvent(nsIDOMEvent* aEvent) { return NS_OK; } + virtual nsresult MouseDown(nsIDOMEvent* aMouseEvent); + virtual nsresult MouseUp(nsIDOMEvent* aMouseEvent); + virtual nsresult MouseClick(nsIDOMEvent* aMouseEvent); + virtual nsresult MouseDblClick(nsIDOMEvent* aMouseEvent); + virtual nsresult MouseOver(nsIDOMEvent* aMouseEvent); + virtual nsresult MouseOut(nsIDOMEvent* aMouseEvent); + + // nsIDOMMouseMotionListener + virtual nsresult MouseMove(nsIDOMEvent* aMouseEvent); + virtual nsresult DragMove(nsIDOMEvent* aMouseEvent) { return NS_OK; }; + + // nsIDOMKeyListener + virtual nsresult KeyDown(nsIDOMEvent* aKeyEvent) ; + virtual nsresult KeyUp(nsIDOMEvent* aKeyEvent) ; + virtual nsresult KeyPress(nsIDOMEvent* aKeyEvent) ; + + // Add/remove the relevant listeners, based on what interfaces + // the embedding chrome implements. + NS_IMETHOD AddChromeListeners(); + NS_IMETHOD RemoveChromeListeners(); + +private: + + NS_IMETHOD AddContextMenuListener(); + NS_IMETHOD AddTooltipListener(); + NS_IMETHOD RemoveContextMenuListener(); + NS_IMETHOD RemoveTooltipListener(); + + nsWebBrowser* mWebBrowser; + nsIWebBrowserChrome* mWebBrowserChrome; + nsCOMPtr mEventReceiver; + + PRPackedBool mContextMenuListenerInstalled; + PRPackedBool mTooltipListenerInstalled; + +private: + + // various delays for tooltips + enum { + kTooltipAutoHideTime = 5000, // 5000ms = 5 seconds + kTooltipShowTime = 500 // 500ms = 0.5 seconds + }; + + NS_IMETHOD ShowTooltip ( PRInt32 inXCoords, PRInt32 inYCoords, const nsAReadableString & inTipText ) ; + NS_IMETHOD HideTooltip ( ) ; + + // Determine if there is a TITLE attribute. Returns |PR_TRUE| if there is, + // and sets the text in |outText|. + PRBool FindTitleText ( nsIDOMNode* inNode, nsAWritableString & outText ) ; + + nsCOMPtr mTooltipTimer; + static void sTooltipCallback ( nsITimer* aTimer, void* aListener ) ; + PRInt32 mMouseClientX, mMouseClientY; // mouse coordinates for tooltip event + PRBool mShowingTooltip; + + // a timer for auto-hiding the tooltip after a certain delay + nsCOMPtr mAutoHideTimer; + static void sAutoHideCallback ( nsITimer* aTimer, void* aListener ) ; + void CreateAutoHideTimer ( ) ; + + // 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. + nsCOMPtr mPossibleTooltipNode; + +}; // ChromeListener + + + + #endif /* nsDocShellTreeOwner_h__ */