Bug 1460691 - Support tooltips in top level non-XUL windows. r=enndeakin+6102,mats+6102
authorBrendan Dahl <brendan.dahl@gmail.com>
Tue, 03 Jul 2018 14:20:43 -0700
changeset 426583 b2d89deed396
parent 426582 a35b188d0e44
child 426584 7b32e48fadc5
push id34276
push userncsoregi@mozilla.com
push dateSat, 14 Jul 2018 09:41:08 +0000
treeherdermozilla-central@04dd259d71db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenndeakin
bugs1460691
milestone63.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 1460691 - Support tooltips in top level non-XUL windows. r=enndeakin+6102,mats+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',
 ]
 
--- 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;
@@ -1122,24 +1125,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,