Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "nsXULTooltipListener.h" |
michael@0 | 7 | |
michael@0 | 8 | #include "nsIDOMMouseEvent.h" |
michael@0 | 9 | #include "nsIDOMXULDocument.h" |
michael@0 | 10 | #include "nsIDOMXULElement.h" |
michael@0 | 11 | #include "nsIDocument.h" |
michael@0 | 12 | #include "nsGkAtoms.h" |
michael@0 | 13 | #include "nsIPopupBoxObject.h" |
michael@0 | 14 | #include "nsMenuPopupFrame.h" |
michael@0 | 15 | #include "nsIServiceManager.h" |
michael@0 | 16 | #include "nsIDragService.h" |
michael@0 | 17 | #include "nsIDragSession.h" |
michael@0 | 18 | #ifdef MOZ_XUL |
michael@0 | 19 | #include "nsITreeView.h" |
michael@0 | 20 | #endif |
michael@0 | 21 | #include "nsIScriptContext.h" |
michael@0 | 22 | #include "nsPIDOMWindow.h" |
michael@0 | 23 | #ifdef MOZ_XUL |
michael@0 | 24 | #include "nsXULPopupManager.h" |
michael@0 | 25 | #endif |
michael@0 | 26 | #include "nsIRootBox.h" |
michael@0 | 27 | #include "nsIBoxObject.h" |
michael@0 | 28 | #include "mozilla/Preferences.h" |
michael@0 | 29 | #include "mozilla/LookAndFeel.h" |
michael@0 | 30 | #include "mozilla/dom/Element.h" |
michael@0 | 31 | #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() |
michael@0 | 32 | |
michael@0 | 33 | using namespace mozilla; |
michael@0 | 34 | using namespace mozilla::dom; |
michael@0 | 35 | |
michael@0 | 36 | nsXULTooltipListener* nsXULTooltipListener::mInstance = nullptr; |
michael@0 | 37 | |
michael@0 | 38 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 39 | //// nsISupports |
michael@0 | 40 | |
michael@0 | 41 | nsXULTooltipListener::nsXULTooltipListener() |
michael@0 | 42 | : mMouseScreenX(0) |
michael@0 | 43 | , mMouseScreenY(0) |
michael@0 | 44 | , mTooltipShownOnce(false) |
michael@0 | 45 | #ifdef MOZ_XUL |
michael@0 | 46 | , mIsSourceTree(false) |
michael@0 | 47 | , mNeedTitletip(false) |
michael@0 | 48 | , mLastTreeRow(-1) |
michael@0 | 49 | #endif |
michael@0 | 50 | { |
michael@0 | 51 | if (sTooltipListenerCount++ == 0) { |
michael@0 | 52 | // register the callback so we get notified of updates |
michael@0 | 53 | Preferences::RegisterCallback(ToolbarTipsPrefChanged, |
michael@0 | 54 | "browser.chrome.toolbar_tips"); |
michael@0 | 55 | |
michael@0 | 56 | // Call the pref callback to initialize our state. |
michael@0 | 57 | ToolbarTipsPrefChanged("browser.chrome.toolbar_tips", nullptr); |
michael@0 | 58 | } |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | nsXULTooltipListener::~nsXULTooltipListener() |
michael@0 | 62 | { |
michael@0 | 63 | if (nsXULTooltipListener::mInstance == this) { |
michael@0 | 64 | ClearTooltipCache(); |
michael@0 | 65 | } |
michael@0 | 66 | HideTooltip(); |
michael@0 | 67 | |
michael@0 | 68 | if (--sTooltipListenerCount == 0) { |
michael@0 | 69 | // Unregister our pref observer |
michael@0 | 70 | Preferences::UnregisterCallback(ToolbarTipsPrefChanged, |
michael@0 | 71 | "browser.chrome.toolbar_tips"); |
michael@0 | 72 | } |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | NS_IMPL_ISUPPORTS(nsXULTooltipListener, nsIDOMEventListener) |
michael@0 | 76 | |
michael@0 | 77 | void |
michael@0 | 78 | nsXULTooltipListener::MouseOut(nsIDOMEvent* aEvent) |
michael@0 | 79 | { |
michael@0 | 80 | // reset flag so that tooltip will display on the next MouseMove |
michael@0 | 81 | mTooltipShownOnce = false; |
michael@0 | 82 | |
michael@0 | 83 | // if the timer is running and no tooltip is shown, we |
michael@0 | 84 | // have to cancel the timer here so that it doesn't |
michael@0 | 85 | // show the tooltip if we move the mouse out of the window |
michael@0 | 86 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 87 | if (mTooltipTimer && !currentTooltip) { |
michael@0 | 88 | mTooltipTimer->Cancel(); |
michael@0 | 89 | mTooltipTimer = nullptr; |
michael@0 | 90 | return; |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | #ifdef DEBUG_crap |
michael@0 | 94 | if (mNeedTitletip) |
michael@0 | 95 | return; |
michael@0 | 96 | #endif |
michael@0 | 97 | |
michael@0 | 98 | #ifdef MOZ_XUL |
michael@0 | 99 | // check to see if the mouse left the targetNode, and if so, |
michael@0 | 100 | // hide the tooltip |
michael@0 | 101 | if (currentTooltip) { |
michael@0 | 102 | // which node did the mouse leave? |
michael@0 | 103 | nsCOMPtr<nsIDOMNode> targetNode = do_QueryInterface( |
michael@0 | 104 | aEvent->InternalDOMEvent()->GetTarget()); |
michael@0 | 105 | |
michael@0 | 106 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
michael@0 | 107 | if (pm) { |
michael@0 | 108 | nsCOMPtr<nsIDOMNode> tooltipNode = |
michael@0 | 109 | pm->GetLastTriggerTooltipNode(currentTooltip->GetCurrentDoc()); |
michael@0 | 110 | if (tooltipNode == targetNode) { |
michael@0 | 111 | // if the target node is the current tooltip target node, the mouse |
michael@0 | 112 | // left the node the tooltip appeared on, so close the tooltip. |
michael@0 | 113 | HideTooltip(); |
michael@0 | 114 | // reset special tree tracking |
michael@0 | 115 | if (mIsSourceTree) { |
michael@0 | 116 | mLastTreeRow = -1; |
michael@0 | 117 | mLastTreeCol = nullptr; |
michael@0 | 118 | } |
michael@0 | 119 | } |
michael@0 | 120 | } |
michael@0 | 121 | } |
michael@0 | 122 | #endif |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | void |
michael@0 | 126 | nsXULTooltipListener::MouseMove(nsIDOMEvent* aEvent) |
michael@0 | 127 | { |
michael@0 | 128 | if (!sShowTooltips) |
michael@0 | 129 | return; |
michael@0 | 130 | |
michael@0 | 131 | // stash the coordinates of the event so that we can still get back to it from within the |
michael@0 | 132 | // timer callback. On win32, we'll get a MouseMove event even when a popup goes away -- |
michael@0 | 133 | // even when the mouse doesn't change position! To get around this, we make sure the |
michael@0 | 134 | // mouse has really moved before proceeding. |
michael@0 | 135 | nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aEvent)); |
michael@0 | 136 | if (!mouseEvent) |
michael@0 | 137 | return; |
michael@0 | 138 | int32_t newMouseX, newMouseY; |
michael@0 | 139 | mouseEvent->GetScreenX(&newMouseX); |
michael@0 | 140 | mouseEvent->GetScreenY(&newMouseY); |
michael@0 | 141 | |
michael@0 | 142 | // filter out false win32 MouseMove event |
michael@0 | 143 | if (mMouseScreenX == newMouseX && mMouseScreenY == newMouseY) |
michael@0 | 144 | return; |
michael@0 | 145 | |
michael@0 | 146 | // filter out minor movements due to crappy optical mice and shaky hands |
michael@0 | 147 | // to prevent tooltips from hiding prematurely. |
michael@0 | 148 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 149 | |
michael@0 | 150 | if ((currentTooltip) && |
michael@0 | 151 | (abs(mMouseScreenX - newMouseX) <= kTooltipMouseMoveTolerance) && |
michael@0 | 152 | (abs(mMouseScreenY - newMouseY) <= kTooltipMouseMoveTolerance)) |
michael@0 | 153 | return; |
michael@0 | 154 | mMouseScreenX = newMouseX; |
michael@0 | 155 | mMouseScreenY = newMouseY; |
michael@0 | 156 | |
michael@0 | 157 | nsCOMPtr<nsIContent> sourceContent = do_QueryInterface( |
michael@0 | 158 | aEvent->InternalDOMEvent()->GetCurrentTarget()); |
michael@0 | 159 | mSourceNode = do_GetWeakReference(sourceContent); |
michael@0 | 160 | #ifdef MOZ_XUL |
michael@0 | 161 | mIsSourceTree = sourceContent->Tag() == nsGkAtoms::treechildren; |
michael@0 | 162 | if (mIsSourceTree) |
michael@0 | 163 | CheckTreeBodyMove(mouseEvent); |
michael@0 | 164 | #endif |
michael@0 | 165 | |
michael@0 | 166 | // as the mouse moves, we want to make sure we reset the timer to show it, |
michael@0 | 167 | // so that the delay is from when the mouse stops moving, not when it enters |
michael@0 | 168 | // the node. |
michael@0 | 169 | KillTooltipTimer(); |
michael@0 | 170 | |
michael@0 | 171 | // If the mouse moves while the tooltip is up, hide it. If nothing is |
michael@0 | 172 | // showing and the tooltip hasn't been displayed since the mouse entered |
michael@0 | 173 | // the node, then start the timer to show the tooltip. |
michael@0 | 174 | if (!currentTooltip && !mTooltipShownOnce) { |
michael@0 | 175 | nsCOMPtr<EventTarget> eventTarget = aEvent->InternalDOMEvent()->GetTarget(); |
michael@0 | 176 | |
michael@0 | 177 | // don't show tooltips attached to elements outside of a menu popup |
michael@0 | 178 | // when hovering over an element inside it. The popupsinherittooltip |
michael@0 | 179 | // attribute may be used to disable this behaviour, which is useful for |
michael@0 | 180 | // large menu hierarchies such as bookmarks. |
michael@0 | 181 | if (!sourceContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::popupsinherittooltip, |
michael@0 | 182 | nsGkAtoms::_true, eCaseMatters)) { |
michael@0 | 183 | nsCOMPtr<nsIContent> targetContent = do_QueryInterface(eventTarget); |
michael@0 | 184 | while (targetContent && targetContent != sourceContent) { |
michael@0 | 185 | nsIAtom* tag = targetContent->Tag(); |
michael@0 | 186 | if (targetContent->GetNameSpaceID() == kNameSpaceID_XUL && |
michael@0 | 187 | (tag == nsGkAtoms::menupopup || |
michael@0 | 188 | tag == nsGkAtoms::panel || |
michael@0 | 189 | tag == nsGkAtoms::tooltip)) { |
michael@0 | 190 | mSourceNode = nullptr; |
michael@0 | 191 | return; |
michael@0 | 192 | } |
michael@0 | 193 | |
michael@0 | 194 | targetContent = targetContent->GetParent(); |
michael@0 | 195 | } |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1"); |
michael@0 | 199 | if (mTooltipTimer) { |
michael@0 | 200 | mTargetNode = do_GetWeakReference(eventTarget); |
michael@0 | 201 | if (mTargetNode) { |
michael@0 | 202 | nsresult rv = |
michael@0 | 203 | mTooltipTimer->InitWithFuncCallback(sTooltipCallback, this, |
michael@0 | 204 | LookAndFeel::GetInt(LookAndFeel::eIntID_TooltipDelay, 500), |
michael@0 | 205 | nsITimer::TYPE_ONE_SHOT); |
michael@0 | 206 | if (NS_FAILED(rv)) { |
michael@0 | 207 | mTargetNode = nullptr; |
michael@0 | 208 | mSourceNode = nullptr; |
michael@0 | 209 | } |
michael@0 | 210 | } |
michael@0 | 211 | } |
michael@0 | 212 | return; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | #ifdef MOZ_XUL |
michael@0 | 216 | if (mIsSourceTree) |
michael@0 | 217 | return; |
michael@0 | 218 | #endif |
michael@0 | 219 | |
michael@0 | 220 | HideTooltip(); |
michael@0 | 221 | // set a flag so that the tooltip is only displayed once until the mouse |
michael@0 | 222 | // leaves the node |
michael@0 | 223 | mTooltipShownOnce = true; |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | NS_IMETHODIMP |
michael@0 | 227 | nsXULTooltipListener::HandleEvent(nsIDOMEvent* aEvent) |
michael@0 | 228 | { |
michael@0 | 229 | nsAutoString type; |
michael@0 | 230 | aEvent->GetType(type); |
michael@0 | 231 | if (type.EqualsLiteral("DOMMouseScroll") || |
michael@0 | 232 | type.EqualsLiteral("keydown") || |
michael@0 | 233 | type.EqualsLiteral("mousedown") || |
michael@0 | 234 | type.EqualsLiteral("mouseup") || |
michael@0 | 235 | type.EqualsLiteral("dragstart")) { |
michael@0 | 236 | HideTooltip(); |
michael@0 | 237 | return NS_OK; |
michael@0 | 238 | } |
michael@0 | 239 | |
michael@0 | 240 | if (type.EqualsLiteral("popuphiding")) { |
michael@0 | 241 | DestroyTooltip(); |
michael@0 | 242 | return NS_OK; |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | // Note that mousemove, mouseover and mouseout might be |
michael@0 | 246 | // fired even during dragging due to widget's bug. |
michael@0 | 247 | nsCOMPtr<nsIDragService> dragService = |
michael@0 | 248 | do_GetService("@mozilla.org/widget/dragservice;1"); |
michael@0 | 249 | NS_ENSURE_TRUE(dragService, NS_OK); |
michael@0 | 250 | nsCOMPtr<nsIDragSession> dragSession; |
michael@0 | 251 | dragService->GetCurrentSession(getter_AddRefs(dragSession)); |
michael@0 | 252 | if (dragSession) { |
michael@0 | 253 | return NS_OK; |
michael@0 | 254 | } |
michael@0 | 255 | |
michael@0 | 256 | // Not dragging. |
michael@0 | 257 | |
michael@0 | 258 | if (type.EqualsLiteral("mousemove")) { |
michael@0 | 259 | MouseMove(aEvent); |
michael@0 | 260 | return NS_OK; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | if (type.EqualsLiteral("mouseout")) { |
michael@0 | 264 | MouseOut(aEvent); |
michael@0 | 265 | return NS_OK; |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | return NS_OK; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 272 | //// nsXULTooltipListener |
michael@0 | 273 | |
michael@0 | 274 | // static |
michael@0 | 275 | void |
michael@0 | 276 | nsXULTooltipListener::ToolbarTipsPrefChanged(const char *aPref, |
michael@0 | 277 | void *aClosure) |
michael@0 | 278 | { |
michael@0 | 279 | sShowTooltips = |
michael@0 | 280 | Preferences::GetBool("browser.chrome.toolbar_tips", sShowTooltips); |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | ////////////////////////////////////////////////////////////////////////// |
michael@0 | 284 | //// nsXULTooltipListener |
michael@0 | 285 | |
michael@0 | 286 | bool nsXULTooltipListener::sShowTooltips = false; |
michael@0 | 287 | uint32_t nsXULTooltipListener::sTooltipListenerCount = 0; |
michael@0 | 288 | |
michael@0 | 289 | nsresult |
michael@0 | 290 | nsXULTooltipListener::AddTooltipSupport(nsIContent* aNode) |
michael@0 | 291 | { |
michael@0 | 292 | if (!aNode) |
michael@0 | 293 | return NS_ERROR_NULL_POINTER; |
michael@0 | 294 | |
michael@0 | 295 | aNode->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), this, |
michael@0 | 296 | false, false); |
michael@0 | 297 | aNode->AddSystemEventListener(NS_LITERAL_STRING("mousemove"), this, |
michael@0 | 298 | false, false); |
michael@0 | 299 | aNode->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), this, |
michael@0 | 300 | false, false); |
michael@0 | 301 | aNode->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), this, |
michael@0 | 302 | false, false); |
michael@0 | 303 | aNode->AddSystemEventListener(NS_LITERAL_STRING("dragstart"), this, |
michael@0 | 304 | true, false); |
michael@0 | 305 | |
michael@0 | 306 | return NS_OK; |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | nsresult |
michael@0 | 310 | nsXULTooltipListener::RemoveTooltipSupport(nsIContent* aNode) |
michael@0 | 311 | { |
michael@0 | 312 | if (!aNode) |
michael@0 | 313 | return NS_ERROR_NULL_POINTER; |
michael@0 | 314 | |
michael@0 | 315 | aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), this, false); |
michael@0 | 316 | aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"), this, false); |
michael@0 | 317 | aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), this, false); |
michael@0 | 318 | aNode->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), this, false); |
michael@0 | 319 | aNode->RemoveSystemEventListener(NS_LITERAL_STRING("dragstart"), this, true); |
michael@0 | 320 | |
michael@0 | 321 | return NS_OK; |
michael@0 | 322 | } |
michael@0 | 323 | |
michael@0 | 324 | #ifdef MOZ_XUL |
michael@0 | 325 | void |
michael@0 | 326 | nsXULTooltipListener::CheckTreeBodyMove(nsIDOMMouseEvent* aMouseEvent) |
michael@0 | 327 | { |
michael@0 | 328 | nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode); |
michael@0 | 329 | if (!sourceNode) |
michael@0 | 330 | return; |
michael@0 | 331 | |
michael@0 | 332 | // get the boxObject of the documentElement of the document the tree is in |
michael@0 | 333 | nsCOMPtr<nsIBoxObject> bx; |
michael@0 | 334 | nsIDocument* doc = sourceNode->GetDocument(); |
michael@0 | 335 | if (doc) { |
michael@0 | 336 | ErrorResult ignored; |
michael@0 | 337 | bx = doc->GetBoxObjectFor(doc->GetRootElement(), ignored); |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | nsCOMPtr<nsITreeBoxObject> obx; |
michael@0 | 341 | GetSourceTreeBoxObject(getter_AddRefs(obx)); |
michael@0 | 342 | if (bx && obx) { |
michael@0 | 343 | int32_t x, y; |
michael@0 | 344 | aMouseEvent->GetScreenX(&x); |
michael@0 | 345 | aMouseEvent->GetScreenY(&y); |
michael@0 | 346 | |
michael@0 | 347 | int32_t row; |
michael@0 | 348 | nsCOMPtr<nsITreeColumn> col; |
michael@0 | 349 | nsAutoCString obj; |
michael@0 | 350 | |
michael@0 | 351 | // subtract off the documentElement's boxObject |
michael@0 | 352 | int32_t boxX, boxY; |
michael@0 | 353 | bx->GetScreenX(&boxX); |
michael@0 | 354 | bx->GetScreenY(&boxY); |
michael@0 | 355 | x -= boxX; |
michael@0 | 356 | y -= boxY; |
michael@0 | 357 | |
michael@0 | 358 | obx->GetCellAt(x, y, &row, getter_AddRefs(col), obj); |
michael@0 | 359 | |
michael@0 | 360 | // determine if we are going to need a titletip |
michael@0 | 361 | // XXX check the disabletitletips attribute on the tree content |
michael@0 | 362 | mNeedTitletip = false; |
michael@0 | 363 | if (row >= 0 && obj.EqualsLiteral("text")) { |
michael@0 | 364 | obx->IsCellCropped(row, col, &mNeedTitletip); |
michael@0 | 365 | } |
michael@0 | 366 | |
michael@0 | 367 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 368 | if (currentTooltip && (row != mLastTreeRow || col != mLastTreeCol)) { |
michael@0 | 369 | HideTooltip(); |
michael@0 | 370 | } |
michael@0 | 371 | |
michael@0 | 372 | mLastTreeRow = row; |
michael@0 | 373 | mLastTreeCol = col; |
michael@0 | 374 | } |
michael@0 | 375 | } |
michael@0 | 376 | #endif |
michael@0 | 377 | |
michael@0 | 378 | nsresult |
michael@0 | 379 | nsXULTooltipListener::ShowTooltip() |
michael@0 | 380 | { |
michael@0 | 381 | nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode); |
michael@0 | 382 | |
michael@0 | 383 | // get the tooltip content designated for the target node |
michael@0 | 384 | nsCOMPtr<nsIContent> tooltipNode; |
michael@0 | 385 | GetTooltipFor(sourceNode, getter_AddRefs(tooltipNode)); |
michael@0 | 386 | if (!tooltipNode || sourceNode == tooltipNode) |
michael@0 | 387 | return NS_ERROR_FAILURE; // the target node doesn't need a tooltip |
michael@0 | 388 | |
michael@0 | 389 | // set the node in the document that triggered the tooltip and show it |
michael@0 | 390 | nsCOMPtr<nsIDOMXULDocument> xulDoc(do_QueryInterface(tooltipNode->GetDocument())); |
michael@0 | 391 | if (xulDoc) { |
michael@0 | 392 | // Make sure the target node is still attached to some document. |
michael@0 | 393 | // It might have been deleted. |
michael@0 | 394 | if (sourceNode->GetDocument()) { |
michael@0 | 395 | #ifdef MOZ_XUL |
michael@0 | 396 | if (!mIsSourceTree) { |
michael@0 | 397 | mLastTreeRow = -1; |
michael@0 | 398 | mLastTreeCol = nullptr; |
michael@0 | 399 | } |
michael@0 | 400 | #endif |
michael@0 | 401 | |
michael@0 | 402 | mCurrentTooltip = do_GetWeakReference(tooltipNode); |
michael@0 | 403 | LaunchTooltip(); |
michael@0 | 404 | mTargetNode = nullptr; |
michael@0 | 405 | |
michael@0 | 406 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 407 | if (!currentTooltip) |
michael@0 | 408 | return NS_OK; |
michael@0 | 409 | |
michael@0 | 410 | // listen for popuphidden on the tooltip node, so that we can |
michael@0 | 411 | // be sure DestroyPopup is called even if someone else closes the tooltip |
michael@0 | 412 | currentTooltip->AddSystemEventListener(NS_LITERAL_STRING("popuphiding"), |
michael@0 | 413 | this, false, false); |
michael@0 | 414 | |
michael@0 | 415 | // listen for mousedown, mouseup, keydown, and DOMMouseScroll events at document level |
michael@0 | 416 | nsIDocument* doc = sourceNode->GetDocument(); |
michael@0 | 417 | if (doc) { |
michael@0 | 418 | // Probably, we should listen to untrusted events for hiding tooltips |
michael@0 | 419 | // on content since tooltips might disturb something of web |
michael@0 | 420 | // applications. If we don't specify the aWantsUntrusted of |
michael@0 | 421 | // AddSystemEventListener(), the event target sets it to TRUE if the |
michael@0 | 422 | // target is in content. |
michael@0 | 423 | doc->AddSystemEventListener(NS_LITERAL_STRING("DOMMouseScroll"), |
michael@0 | 424 | this, true); |
michael@0 | 425 | doc->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), |
michael@0 | 426 | this, true); |
michael@0 | 427 | doc->AddSystemEventListener(NS_LITERAL_STRING("mouseup"), |
michael@0 | 428 | this, true); |
michael@0 | 429 | doc->AddSystemEventListener(NS_LITERAL_STRING("keydown"), |
michael@0 | 430 | this, true); |
michael@0 | 431 | } |
michael@0 | 432 | mSourceNode = nullptr; |
michael@0 | 433 | } |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | return NS_OK; |
michael@0 | 437 | } |
michael@0 | 438 | |
michael@0 | 439 | #ifdef MOZ_XUL |
michael@0 | 440 | // XXX: "This stuff inside DEBUG_crap could be used to make tree tooltips work |
michael@0 | 441 | // in the future." |
michael@0 | 442 | #ifdef DEBUG_crap |
michael@0 | 443 | static void |
michael@0 | 444 | GetTreeCellCoords(nsITreeBoxObject* aTreeBox, nsIContent* aSourceNode, |
michael@0 | 445 | int32_t aRow, nsITreeColumn* aCol, int32_t* aX, int32_t* aY) |
michael@0 | 446 | { |
michael@0 | 447 | int32_t junk; |
michael@0 | 448 | aTreeBox->GetCoordsForCellItem(aRow, aCol, EmptyCString(), aX, aY, &junk, &junk); |
michael@0 | 449 | nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aSourceNode)); |
michael@0 | 450 | nsCOMPtr<nsIBoxObject> bx; |
michael@0 | 451 | xulEl->GetBoxObject(getter_AddRefs(bx)); |
michael@0 | 452 | int32_t myX, myY; |
michael@0 | 453 | bx->GetX(&myX); |
michael@0 | 454 | bx->GetY(&myY); |
michael@0 | 455 | *aX += myX; |
michael@0 | 456 | *aY += myY; |
michael@0 | 457 | } |
michael@0 | 458 | #endif |
michael@0 | 459 | |
michael@0 | 460 | static void |
michael@0 | 461 | SetTitletipLabel(nsITreeBoxObject* aTreeBox, nsIContent* aTooltip, |
michael@0 | 462 | int32_t aRow, nsITreeColumn* aCol) |
michael@0 | 463 | { |
michael@0 | 464 | nsCOMPtr<nsITreeView> view; |
michael@0 | 465 | aTreeBox->GetView(getter_AddRefs(view)); |
michael@0 | 466 | if (view) { |
michael@0 | 467 | nsAutoString label; |
michael@0 | 468 | #ifdef DEBUG |
michael@0 | 469 | nsresult rv = |
michael@0 | 470 | #endif |
michael@0 | 471 | view->GetCellText(aRow, aCol, label); |
michael@0 | 472 | NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Couldn't get the cell text!"); |
michael@0 | 473 | aTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::label, label, true); |
michael@0 | 474 | } |
michael@0 | 475 | } |
michael@0 | 476 | #endif |
michael@0 | 477 | |
michael@0 | 478 | void |
michael@0 | 479 | nsXULTooltipListener::LaunchTooltip() |
michael@0 | 480 | { |
michael@0 | 481 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 482 | if (!currentTooltip) |
michael@0 | 483 | return; |
michael@0 | 484 | |
michael@0 | 485 | #ifdef MOZ_XUL |
michael@0 | 486 | if (mIsSourceTree && mNeedTitletip) { |
michael@0 | 487 | nsCOMPtr<nsITreeBoxObject> obx; |
michael@0 | 488 | GetSourceTreeBoxObject(getter_AddRefs(obx)); |
michael@0 | 489 | |
michael@0 | 490 | SetTitletipLabel(obx, currentTooltip, mLastTreeRow, mLastTreeCol); |
michael@0 | 491 | if (!(currentTooltip = do_QueryReferent(mCurrentTooltip))) { |
michael@0 | 492 | // Because of mutation events, currentTooltip can be null. |
michael@0 | 493 | return; |
michael@0 | 494 | } |
michael@0 | 495 | currentTooltip->SetAttr(kNameSpaceID_None, nsGkAtoms::titletip, NS_LITERAL_STRING("true"), true); |
michael@0 | 496 | } else { |
michael@0 | 497 | currentTooltip->UnsetAttr(kNameSpaceID_None, nsGkAtoms::titletip, true); |
michael@0 | 498 | } |
michael@0 | 499 | if (!(currentTooltip = do_QueryReferent(mCurrentTooltip))) { |
michael@0 | 500 | // Because of mutation events, currentTooltip can be null. |
michael@0 | 501 | return; |
michael@0 | 502 | } |
michael@0 | 503 | |
michael@0 | 504 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
michael@0 | 505 | if (pm) { |
michael@0 | 506 | nsCOMPtr<nsIContent> target = do_QueryReferent(mTargetNode); |
michael@0 | 507 | pm->ShowTooltipAtScreen(currentTooltip, target, mMouseScreenX, mMouseScreenY); |
michael@0 | 508 | |
michael@0 | 509 | // Clear the current tooltip if the popup was not opened successfully. |
michael@0 | 510 | if (!pm->IsPopupOpen(currentTooltip)) |
michael@0 | 511 | mCurrentTooltip = nullptr; |
michael@0 | 512 | } |
michael@0 | 513 | #endif |
michael@0 | 514 | |
michael@0 | 515 | } |
michael@0 | 516 | |
michael@0 | 517 | nsresult |
michael@0 | 518 | nsXULTooltipListener::HideTooltip() |
michael@0 | 519 | { |
michael@0 | 520 | #ifdef MOZ_XUL |
michael@0 | 521 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 522 | if (currentTooltip) { |
michael@0 | 523 | nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); |
michael@0 | 524 | if (pm) |
michael@0 | 525 | pm->HidePopup(currentTooltip, false, false, false, false); |
michael@0 | 526 | } |
michael@0 | 527 | #endif |
michael@0 | 528 | |
michael@0 | 529 | DestroyTooltip(); |
michael@0 | 530 | return NS_OK; |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | static void |
michael@0 | 534 | GetImmediateChild(nsIContent* aContent, nsIAtom *aTag, nsIContent** aResult) |
michael@0 | 535 | { |
michael@0 | 536 | *aResult = nullptr; |
michael@0 | 537 | uint32_t childCount = aContent->GetChildCount(); |
michael@0 | 538 | for (uint32_t i = 0; i < childCount; i++) { |
michael@0 | 539 | nsIContent *child = aContent->GetChildAt(i); |
michael@0 | 540 | |
michael@0 | 541 | if (child->Tag() == aTag) { |
michael@0 | 542 | *aResult = child; |
michael@0 | 543 | NS_ADDREF(*aResult); |
michael@0 | 544 | return; |
michael@0 | 545 | } |
michael@0 | 546 | } |
michael@0 | 547 | |
michael@0 | 548 | return; |
michael@0 | 549 | } |
michael@0 | 550 | |
michael@0 | 551 | nsresult |
michael@0 | 552 | nsXULTooltipListener::FindTooltip(nsIContent* aTarget, nsIContent** aTooltip) |
michael@0 | 553 | { |
michael@0 | 554 | if (!aTarget) |
michael@0 | 555 | return NS_ERROR_NULL_POINTER; |
michael@0 | 556 | |
michael@0 | 557 | // before we go on, make sure that target node still has a window |
michael@0 | 558 | nsIDocument *document = aTarget->GetDocument(); |
michael@0 | 559 | if (!document) { |
michael@0 | 560 | NS_WARNING("Unable to retrieve the tooltip node document."); |
michael@0 | 561 | return NS_ERROR_FAILURE; |
michael@0 | 562 | } |
michael@0 | 563 | nsPIDOMWindow *window = document->GetWindow(); |
michael@0 | 564 | if (!window) { |
michael@0 | 565 | return NS_OK; |
michael@0 | 566 | } |
michael@0 | 567 | |
michael@0 | 568 | bool closed; |
michael@0 | 569 | window->GetClosed(&closed); |
michael@0 | 570 | |
michael@0 | 571 | if (closed) { |
michael@0 | 572 | return NS_OK; |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | nsAutoString tooltipText; |
michael@0 | 576 | aTarget->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, tooltipText); |
michael@0 | 577 | if (!tooltipText.IsEmpty()) { |
michael@0 | 578 | // specifying tooltiptext means we will always use the default tooltip |
michael@0 | 579 | nsIRootBox* rootBox = nsIRootBox::GetRootBox(document->GetShell()); |
michael@0 | 580 | NS_ENSURE_STATE(rootBox); |
michael@0 | 581 | *aTooltip = rootBox->GetDefaultTooltip(); |
michael@0 | 582 | if (*aTooltip) { |
michael@0 | 583 | NS_ADDREF(*aTooltip); |
michael@0 | 584 | (*aTooltip)->SetAttr(kNameSpaceID_None, nsGkAtoms::label, tooltipText, true); |
michael@0 | 585 | } |
michael@0 | 586 | return NS_OK; |
michael@0 | 587 | } |
michael@0 | 588 | |
michael@0 | 589 | nsAutoString tooltipId; |
michael@0 | 590 | aTarget->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltip, tooltipId); |
michael@0 | 591 | |
michael@0 | 592 | // if tooltip == _child, look for first <tooltip> child |
michael@0 | 593 | if (tooltipId.EqualsLiteral("_child")) { |
michael@0 | 594 | GetImmediateChild(aTarget, nsGkAtoms::tooltip, aTooltip); |
michael@0 | 595 | return NS_OK; |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | if (!tooltipId.IsEmpty()) { |
michael@0 | 599 | // tooltip must be an id, use getElementById to find it |
michael@0 | 600 | nsCOMPtr<nsIContent> tooltipEl = document->GetElementById(tooltipId); |
michael@0 | 601 | |
michael@0 | 602 | if (tooltipEl) { |
michael@0 | 603 | #ifdef MOZ_XUL |
michael@0 | 604 | mNeedTitletip = false; |
michael@0 | 605 | #endif |
michael@0 | 606 | tooltipEl.forget(aTooltip); |
michael@0 | 607 | return NS_OK; |
michael@0 | 608 | } |
michael@0 | 609 | } |
michael@0 | 610 | |
michael@0 | 611 | #ifdef MOZ_XUL |
michael@0 | 612 | // titletips should just use the default tooltip |
michael@0 | 613 | if (mIsSourceTree && mNeedTitletip) { |
michael@0 | 614 | nsIRootBox* rootBox = nsIRootBox::GetRootBox(document->GetShell()); |
michael@0 | 615 | NS_ENSURE_STATE(rootBox); |
michael@0 | 616 | NS_IF_ADDREF(*aTooltip = rootBox->GetDefaultTooltip()); |
michael@0 | 617 | } |
michael@0 | 618 | #endif |
michael@0 | 619 | |
michael@0 | 620 | return NS_OK; |
michael@0 | 621 | } |
michael@0 | 622 | |
michael@0 | 623 | |
michael@0 | 624 | nsresult |
michael@0 | 625 | nsXULTooltipListener::GetTooltipFor(nsIContent* aTarget, nsIContent** aTooltip) |
michael@0 | 626 | { |
michael@0 | 627 | *aTooltip = nullptr; |
michael@0 | 628 | nsCOMPtr<nsIContent> tooltip; |
michael@0 | 629 | nsresult rv = FindTooltip(aTarget, getter_AddRefs(tooltip)); |
michael@0 | 630 | if (NS_FAILED(rv) || !tooltip) { |
michael@0 | 631 | return rv; |
michael@0 | 632 | } |
michael@0 | 633 | |
michael@0 | 634 | #ifdef MOZ_XUL |
michael@0 | 635 | // Submenus can't be used as tooltips, see bug 288763. |
michael@0 | 636 | nsIContent* parent = tooltip->GetParent(); |
michael@0 | 637 | if (parent) { |
michael@0 | 638 | nsMenuFrame* menu = do_QueryFrame(parent->GetPrimaryFrame()); |
michael@0 | 639 | if (menu) { |
michael@0 | 640 | NS_WARNING("Menu cannot be used as a tooltip"); |
michael@0 | 641 | return NS_ERROR_FAILURE; |
michael@0 | 642 | } |
michael@0 | 643 | } |
michael@0 | 644 | #endif |
michael@0 | 645 | |
michael@0 | 646 | tooltip.swap(*aTooltip); |
michael@0 | 647 | return rv; |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | nsresult |
michael@0 | 651 | nsXULTooltipListener::DestroyTooltip() |
michael@0 | 652 | { |
michael@0 | 653 | nsCOMPtr<nsIDOMEventListener> kungFuDeathGrip(this); |
michael@0 | 654 | nsCOMPtr<nsIContent> currentTooltip = do_QueryReferent(mCurrentTooltip); |
michael@0 | 655 | if (currentTooltip) { |
michael@0 | 656 | // release tooltip before removing listener to prevent our destructor from |
michael@0 | 657 | // being called recursively (bug 120863) |
michael@0 | 658 | mCurrentTooltip = nullptr; |
michael@0 | 659 | |
michael@0 | 660 | // clear out the tooltip node on the document |
michael@0 | 661 | nsCOMPtr<nsIDocument> doc = currentTooltip->GetDocument(); |
michael@0 | 662 | if (doc) { |
michael@0 | 663 | // remove the mousedown and keydown listener from document |
michael@0 | 664 | doc->RemoveSystemEventListener(NS_LITERAL_STRING("DOMMouseScroll"), this, |
michael@0 | 665 | true); |
michael@0 | 666 | doc->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), this, |
michael@0 | 667 | true); |
michael@0 | 668 | doc->RemoveSystemEventListener(NS_LITERAL_STRING("mouseup"), this, true); |
michael@0 | 669 | doc->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), this, true); |
michael@0 | 670 | } |
michael@0 | 671 | |
michael@0 | 672 | // remove the popuphidden listener from tooltip |
michael@0 | 673 | currentTooltip->RemoveSystemEventListener(NS_LITERAL_STRING("popuphiding"), this, false); |
michael@0 | 674 | } |
michael@0 | 675 | |
michael@0 | 676 | // kill any ongoing timers |
michael@0 | 677 | KillTooltipTimer(); |
michael@0 | 678 | mSourceNode = nullptr; |
michael@0 | 679 | #ifdef MOZ_XUL |
michael@0 | 680 | mLastTreeCol = nullptr; |
michael@0 | 681 | #endif |
michael@0 | 682 | |
michael@0 | 683 | return NS_OK; |
michael@0 | 684 | } |
michael@0 | 685 | |
michael@0 | 686 | void |
michael@0 | 687 | nsXULTooltipListener::KillTooltipTimer() |
michael@0 | 688 | { |
michael@0 | 689 | if (mTooltipTimer) { |
michael@0 | 690 | mTooltipTimer->Cancel(); |
michael@0 | 691 | mTooltipTimer = nullptr; |
michael@0 | 692 | mTargetNode = nullptr; |
michael@0 | 693 | } |
michael@0 | 694 | } |
michael@0 | 695 | |
michael@0 | 696 | void |
michael@0 | 697 | nsXULTooltipListener::sTooltipCallback(nsITimer *aTimer, void *aListener) |
michael@0 | 698 | { |
michael@0 | 699 | nsRefPtr<nsXULTooltipListener> instance = mInstance; |
michael@0 | 700 | if (instance) |
michael@0 | 701 | instance->ShowTooltip(); |
michael@0 | 702 | } |
michael@0 | 703 | |
michael@0 | 704 | #ifdef MOZ_XUL |
michael@0 | 705 | nsresult |
michael@0 | 706 | nsXULTooltipListener::GetSourceTreeBoxObject(nsITreeBoxObject** aBoxObject) |
michael@0 | 707 | { |
michael@0 | 708 | *aBoxObject = nullptr; |
michael@0 | 709 | |
michael@0 | 710 | nsCOMPtr<nsIContent> sourceNode = do_QueryReferent(mSourceNode); |
michael@0 | 711 | if (mIsSourceTree && sourceNode) { |
michael@0 | 712 | nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(sourceNode->GetParent())); |
michael@0 | 713 | if (xulEl) { |
michael@0 | 714 | nsCOMPtr<nsIBoxObject> bx; |
michael@0 | 715 | xulEl->GetBoxObject(getter_AddRefs(bx)); |
michael@0 | 716 | nsCOMPtr<nsITreeBoxObject> obx(do_QueryInterface(bx)); |
michael@0 | 717 | if (obx) { |
michael@0 | 718 | *aBoxObject = obx; |
michael@0 | 719 | NS_ADDREF(*aBoxObject); |
michael@0 | 720 | return NS_OK; |
michael@0 | 721 | } |
michael@0 | 722 | } |
michael@0 | 723 | } |
michael@0 | 724 | return NS_ERROR_FAILURE; |
michael@0 | 725 | } |
michael@0 | 726 | #endif |