Bug 1460691 - Support tooltips in top level non-XUL windows. r?enndeakin+6102 draft
authorBrendan Dahl <brendan.dahl@gmail.com>
Tue, 03 Jul 2018 14:20:43 -0700
changeset 817556 4a18449a17533233b288966aca4c853d5e25ca96
parent 814904 fa376bf17cc95539f5e37186977d760296fb5093
push id116107
push userbmo:bdahl@mozilla.com
push dateThu, 12 Jul 2018 21:40:19 +0000
reviewersenndeakin
bugs1460691
milestone63.0a1
Bug 1460691 - Support tooltips in top level non-XUL windows. r?enndeakin+6102 Add an anonymous XUL tooltip node to toplevel non-XUL windows. Setup a nsXULTooltipListener on non-XUL nsXULWindows. Make nsXULTooltipListener always use the default tooltip in the non-XUL case. MozReview-Commit-ID: Koe5m8PwMQM
layout/generic/nsCanvasFrame.cpp
layout/generic/nsCanvasFrame.h
layout/xul/moz.build
layout/xul/nsXULTooltipListener.cpp
xpfe/appshell/nsXULWindow.cpp
xpfe/appshell/nsXULWindow.h
--- a/layout/generic/nsCanvasFrame.cpp
+++ b/layout/generic/nsCanvasFrame.cpp
@@ -125,44 +125,64 @@ nsCanvasFrame::CreateAnonymousContent(ns
   RefPtr<AccessibleCaretEventHub> eventHub =
     PresContext()->GetPresShell()->GetAccessibleCaretEventHub();
   if (eventHub) {
     // AccessibleCaret will insert anonymous caret elements.
     eventHub->Init();
   }
 
   // Create a popupgroup element for chrome privileged top level non-XUL
-  // documents to support context menus.
+  // documents to support context menus and tooltips.
   if (PresContext()->IsChrome() && PresContext()->IsRoot() &&
       doc->AllowXULXBL() && !doc->IsXULDocument()) {
     nsNodeInfoManager* nodeInfoManager = doc->NodeInfoManager();
     RefPtr<NodeInfo> nodeInfo =
       nodeInfoManager->GetNodeInfo(nsGkAtoms::popupgroup,
                                    nullptr, kNameSpaceID_XUL,
                                    nsINode::ELEMENT_NODE);
 
     rv = NS_NewXULElement(getter_AddRefs(mPopupgroupContent),
                           nodeInfo.forget(), dom::NOT_FROM_PARSER);
     NS_ENSURE_SUCCESS(rv, rv);
 
     aElements.AppendElement(mPopupgroupContent);
+
+    nodeInfo = nodeInfoManager->GetNodeInfo(nsGkAtoms::tooltip, nullptr,
+                                          kNameSpaceID_XUL,
+                                          nsINode::ELEMENT_NODE);
+
+    rv = NS_NewXULElement(getter_AddRefs(mTooltipContent), nodeInfo.forget(),
+                          dom::NOT_FROM_PARSER);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_default,
+                             NS_LITERAL_STRING("true"), false);
+    // Set the page attribute so the XBL binding will find the text for the
+    // tooltip from the currently hovered element.
+    mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::page,
+                             NS_LITERAL_STRING("true"), false);
+
+    aElements.AppendElement(mTooltipContent);
   }
 
   return NS_OK;
 }
 
 void
 nsCanvasFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, uint32_t aFilter)
 {
   if (mCustomContentContainer) {
     aElements.AppendElement(mCustomContentContainer);
   }
   if (mPopupgroupContent) {
     aElements.AppendElement(mPopupgroupContent);
   }
+  if (mTooltipContent) {
+    aElements.AppendElement(mTooltipContent);
+  }
 }
 
 void
 nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
 {
   nsIScrollableFrame* sf =
     PresContext()->GetPresShell()->GetRootScrollFrameAsScrollable();
   if (sf) {
@@ -183,16 +203,19 @@ nsCanvasFrame::DestroyFrom(nsIFrame* aDe
       nsCOMPtr<nsINode> clonedElement = content->GetContentNode()->CloneNode(true, rv);
       content->SetContentNode(clonedElement->AsElement());
     }
   }
   aPostDestroyData.AddAnonymousContent(mCustomContentContainer.forget());
   if (mPopupgroupContent) {
     aPostDestroyData.AddAnonymousContent(mPopupgroupContent.forget());
   }
+  if (mTooltipContent) {
+    aPostDestroyData.AddAnonymousContent(mTooltipContent.forget());
+  }
 
   MOZ_ASSERT(!mPopupSetFrame ||
              nsLayoutUtils::IsProperAncestorFrame(this, mPopupSetFrame),
              "Someone forgot to clear popup set frame");
   nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
 void
@@ -302,25 +325,25 @@ nsCanvasFrame::SetPopupSetFrame(nsPopupS
   MOZ_ASSERT(!aPopupSet || !mPopupSetFrame,
              "Popup set is already defined! Only 1 allowed.");
   mPopupSetFrame = aPopupSet;
 }
 
 Element*
 nsCanvasFrame::GetDefaultTooltip()
 {
-  NS_WARNING("GetDefaultTooltip not implemented");
-  return nullptr;
+  return mTooltipContent;
 }
 
 void
 nsCanvasFrame::SetDefaultTooltip(Element* aTooltip)
 {
-  NS_WARNING("SetDefaultTooltip not implemented");
-  return;
+  MOZ_ASSERT(!aTooltip || aTooltip == mTooltipContent,
+             "Default tooltip should be anonymous content tooltip.");
+  mTooltipContent = aTooltip;
 }
 
 void
 nsDisplayCanvasBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
                                       gfxContext* aCtx)
 {
   nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
   nsPoint offset = ToReferenceFrame();
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -130,16 +130,17 @@ protected:
   bool                      mDoPaintFocus;
   bool                      mAddedScrollPositionListener;
 
   nsCOMPtr<mozilla::dom::Element> mCustomContentContainer;
 
 private:
   nsPopupSetFrame* mPopupSetFrame;
   nsCOMPtr<mozilla::dom::Element> mPopupgroupContent;
+  nsCOMPtr<mozilla::dom::Element> mTooltipContent;
 };
 
 /**
  * Override nsDisplayBackground methods so that we pass aBGClipRect to
  * PaintBackground, covering the whole overflow area.
  * We can also paint an "extra background color" behind the normal
  * background.
  */
--- a/layout/xul/moz.build
+++ b/layout/xul/moz.build
@@ -23,16 +23,17 @@ XPIDL_SOURCES += [
 XPIDL_MODULE = 'layout_xul'
 
 EXPORTS += [
     'nsBox.h',
     'nsIScrollbarMediator.h',
     'nsPIBoxObject.h',
     'nsPIListBoxObject.h',
     'nsXULPopupManager.h',
+    'nsXULTooltipListener.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'BoxObject.h',
     'ListBoxObject.h',
     'MenuBoxObject.h',
     'ScrollBoxObject.h',
 ]
--- a/layout/xul/nsXULTooltipListener.cpp
+++ b/layout/xul/nsXULTooltipListener.cpp
@@ -395,17 +395,17 @@ nsXULTooltipListener::ShowTooltip()
   // get the tooltip content designated for the target node
   nsCOMPtr<nsIContent> tooltipNode;
   GetTooltipFor(sourceNode, getter_AddRefs(tooltipNode));
   if (!tooltipNode || sourceNode == tooltipNode)
     return NS_ERROR_FAILURE; // the target node doesn't need a tooltip
 
   // set the node in the document that triggered the tooltip and show it
   if (tooltipNode->GetComposedDoc() &&
-      tooltipNode->GetComposedDoc()->IsXULDocument()) {
+      nsContentUtils::IsChromeDoc(tooltipNode->GetComposedDoc())) {
     // Make sure the target node is still attached to some document.
     // It might have been deleted.
     if (sourceNode->IsInComposedDoc()) {
 #ifdef MOZ_XUL
       if (!mIsSourceTree) {
         mLastTreeRow = -1;
         mLastTreeCol = nullptr;
       }
@@ -573,16 +573,28 @@ nsXULTooltipListener::FindTooltip(nsICon
   if (!window) {
     return NS_OK;
   }
 
   if (window->Closed()) {
     return NS_OK;
   }
 
+  // non-XUL documents should just use the default tooltip
+  if (!document->IsXULDocument()) {
+    nsIPopupContainer* popupContainer =
+      nsIPopupContainer::GetPopupContainer(document->GetShell());
+    NS_ENSURE_STATE(popupContainer);
+    if (RefPtr<Element> tooltip = popupContainer->GetDefaultTooltip()) {
+      tooltip.forget(aTooltip);
+      return NS_OK;
+    }
+    return NS_ERROR_FAILURE;
+  }
+
   nsAutoString tooltipText;
   if (aTarget->IsElement()) {
     aTarget->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, tooltipText);
   }
   if (!tooltipText.IsEmpty()) {
     // specifying tooltiptext means we will always use the default tooltip
     nsIPopupContainer* popupContainer =
       nsIPopupContainer::GetPopupContainer(document->GetShell());
--- a/xpfe/appshell/nsXULWindow.cpp
+++ b/xpfe/appshell/nsXULWindow.cpp
@@ -44,16 +44,17 @@
 #include "nsAppShellCID.h"
 #include "nsReadableUtils.h"
 #include "nsStyleConsts.h"
 #include "nsPresContext.h"
 #include "nsContentUtils.h"
 #include "nsWebShellWindow.h" // get rid of this one, too...
 #include "nsGlobalWindow.h"
 #include "XULDocument.h"
+#include "nsXULTooltipListener.h"
 
 #include "prenv.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/BarProps.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Event.h"
@@ -512,16 +513,18 @@ NS_IMETHODIMP nsXULWindow::Destroy()
         parent->GetMainWidget(getter_AddRefs(parentWidget));
         if (parentWidget)
           parentWidget->PlaceBehind(eZPlacementTop, 0, true);
       }
     }
   }
 #endif
 
+  RemoveTooltipSupport();
+
   mDOMWindow = nullptr;
   if (mDocShell) {
     nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell));
     shellAsWin->Destroy();
     mDocShell = nullptr; // this can cause reentrancy of this function
   }
 
   mPrimaryContentShell = nullptr;
@@ -1121,24 +1124,69 @@ void nsXULWindow::OnChromeLoaded()
     mChromeLoaded = true;
     ApplyChromeFlags();
     SyncAttributesToWidget();
     if (mWindow) {
       SizeShell();
       if (mShowAfterLoad) {
         SetVisibility(true);
       }
+      AddTooltipSupport();
     }
     // At this point the window may have been closed already during Show() or
     // SyncAttributesToWidget(), so nsXULWindow::Destroy may already have been
     // called. Take care!
   }
   mPersistentAttributesMask |= PAD_POSITION | PAD_SIZE | PAD_MISC;
 }
 
+bool
+nsXULWindow::NeedsTooltipListener()
+{
+  nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement();
+  if (!docShellElement || docShellElement->IsXULElement()) {
+    // Tooltips in XUL are handled by each element.
+    return false;
+  }
+  // All other non-XUL document types need a tooltip listener.
+  return true;
+}
+
+void
+nsXULWindow::AddTooltipSupport()
+{
+  if (!NeedsTooltipListener()) {
+    return;
+  }
+  nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
+  if (!listener) {
+    return;
+  }
+
+  nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement();
+  MOZ_ASSERT(docShellElement);
+  listener->AddTooltipSupport(docShellElement);
+}
+
+void
+nsXULWindow::RemoveTooltipSupport()
+{
+  if (!NeedsTooltipListener()) {
+    return;
+  }
+  nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
+  if (!listener) {
+    return;
+  }
+
+  nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement();
+  MOZ_ASSERT(docShellElement);
+  listener->RemoveTooltipSupport(docShellElement);
+}
+
 // If aSpecWidth and/or aSpecHeight are > 0, we will use these CSS px sizes
 // to fit to the screen when staggering windows; if they're negative,
 // we use the window's current size instead.
 bool nsXULWindow::LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight)
 {
   bool     gotPosition = false;
 
   // if we're the hidden window, don't try to validate our size/position. We're
--- a/xpfe/appshell/nsXULWindow.h
+++ b/xpfe/appshell/nsXULWindow.h
@@ -38,16 +38,17 @@
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 class nsAtom;
+class nsXULTooltipListener;
 
 // nsXULWindow
 
 #define NS_XULWINDOW_IMPL_CID                         \
 { /* 8eaec2f3-ed02-4be2-8e0f-342798477298 */          \
      0x8eaec2f3,                                      \
      0xed02,                                          \
      0x4be2,                                          \
@@ -103,16 +104,20 @@ protected:
                         int32_t aSpecWidth, int32_t aSpecHeight);
    bool       LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight);
    bool       LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight);
    void       SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight);
    bool       LoadMiscPersistentAttributesFromXUL();
    void       SyncAttributesToWidget();
    NS_IMETHOD SavePersistentAttributes();
 
+   bool NeedsTooltipListener();
+   void AddTooltipSupport();
+   void RemoveTooltipSupport();
+
    NS_IMETHOD GetWindowDOMWindow(mozIDOMWindowProxy** aDOMWindow);
    mozilla::dom::Element* GetWindowDOMElement() const;
 
    // See nsIDocShellTreeOwner for docs on next two methods
    nsresult ContentShellAdded(nsIDocShellTreeItem* aContentShell,
                               bool aPrimary);
    nsresult ContentShellRemoved(nsIDocShellTreeItem* aContentShell);
    NS_IMETHOD GetPrimaryContentSize(int32_t* aWidth,