commit 28b770dc0348f7db9eb58e52e69a11635721a864
parent 04c6aa6f2b8297b62ae6c7cb450b8b15a1eba2dd
Author: Lorenz A <me@lorenzackermann.xyz>
Date: Mon, 15 Dec 2025 14:05:34 +0000
Bug 2004254 - [devtools] Turn devtools/client/shared/widgets/tooltip/HTMLTooltip.js into an ES class. r=devtools-reviewers,nchevobbe
Differential Revision: https://phabricator.services.mozilla.com/D276394
Diffstat:
1 file changed, 113 insertions(+), 112 deletions(-)
diff --git a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
@@ -311,109 +311,108 @@ const getRelativeRect = function (node, relativeTo) {
/**
* The HTMLTooltip can display HTML content in a tooltip popup.
- *
- * @param {Document} toolboxDoc
- * The toolbox document to attach the HTMLTooltip popup.
- * @param {object}
- * - {String} className
- * A string separated list of classes to add to the tooltip container
- * element.
- * - {Boolean} consumeOutsideClicks
- * Defaults to true. The tooltip is closed when clicking outside.
- * Should this event be stopped and consumed or not.
- * - {String} id
- * The ID to assign to the tooltip container element.
- * - {Boolean} isMenuTooltip
- * Defaults to false. If the tooltip is a menu then this should be set
- * to true.
- * - {String} type
- * Display type of the tooltip. Possible values: "normal", "arrow", and
- * "doorhanger".
- * - {Boolean} useXulWrapper
- * Defaults to false. If the tooltip is hosted in a XUL document, use a
- * XUL panel in order to use all the screen viewport available.
- * - {Boolean} noAutoHide
- * Defaults to false. If this property is set to false or omitted, the
- * tooltip will automatically disappear after a few seconds. If this
- * attribute is set to true, this will not happen and the tooltip will
- * only hide when the user moves the mouse to another element.
*/
-function HTMLTooltip(
- toolboxDoc,
- {
- className = "",
- consumeOutsideClicks = true,
- id = "",
- isMenuTooltip = false,
- type = "normal",
- useXulWrapper = false,
- noAutoHide = false,
- } = {}
-) {
- EventEmitter.decorate(this);
-
- this.doc = toolboxDoc;
- this.id = id;
- this.className = className;
- this.type = type;
- this.noAutoHide = noAutoHide;
- // consumeOutsideClicks cannot be used if the tooltip is not closed on click
- this.consumeOutsideClicks = this.noAutoHide ? false : consumeOutsideClicks;
- this.isMenuTooltip = isMenuTooltip;
- this.useXulWrapper = this._isXULPopupAvailable() && useXulWrapper;
- this.preferredWidth = "auto";
- this.preferredHeight = "auto";
-
- // The top window is used to attach click event listeners to close the tooltip if the
- // user clicks on the content page.
- this.topWindow = this._getTopWindow();
-
- this._position = null;
-
- this._onClick = this._onClick.bind(this);
- this._onMouseup = this._onMouseup.bind(this);
- this._onXulPanelHidden = this._onXulPanelHidden.bind(this);
-
- this.container = this._createContainer();
- if (this.useXulWrapper) {
- // When using a XUL panel as the wrapper, the actual markup for the tooltip is as
- // follows :
- // <panel> <!-- XUL panel used to position the tooltip anywhere on screen -->
- // <div> <! the actual tooltip-container element -->
- this.xulPanelWrapper = this._createXulPanelWrapper();
- this.doc.documentElement.appendChild(this.xulPanelWrapper);
- this.xulPanelWrapper.appendChild(this.container);
- } else if (this._hasXULRootElement()) {
- this.doc.documentElement.appendChild(this.container);
- } else {
- // In non-XUL context the container is ready to use as is.
- this.doc.body.appendChild(this.container);
+class HTMLTooltip {
+ /**
+ * @param {Document} toolboxDoc
+ * The toolbox document to attach the HTMLTooltip popup.
+ * @param {object} [options={}]
+ * @param {string} [options.className=""]
+ * A string separated list of classes to add to the tooltip container
+ * element.
+ * @param {boolean} [options.consumeOutsideClicks=true]
+ * Defaults to true. The tooltip is closed when clicking outside.
+ * Should this event be stopped and consumed or not.
+ * @param {string} [options.id=""]
+ * The ID to assign to the tooltip container element.
+ * @param {boolean} [options.isMenuTooltip=false]
+ * Defaults to false. If the tooltip is a menu then this should be set
+ * to true.
+ * @param {string} [options.type="normal"]
+ * Display type of the tooltip. Possible values: "normal", "arrow", and
+ * "doorhanger".
+ * @param {boolean} [options.useXulWrapper=false]
+ * Defaults to false. If the tooltip is hosted in a XUL document, use a
+ * XUL panel in order to use all the screen viewport available.
+ * @param {boolean} [options.noAutoHide=false]
+ * Defaults to false. If this property is set to false or omitted, the
+ * tooltip will automatically disappear after a few seconds. If this
+ * attribute is set to true, this will not happen and the tooltip will
+ * only hide when the user moves the mouse to another element.
+ */
+ constructor(
+ toolboxDoc,
+ {
+ className = "",
+ consumeOutsideClicks = true,
+ id = "",
+ isMenuTooltip = false,
+ type = "normal",
+ useXulWrapper = false,
+ noAutoHide = false,
+ } = {}
+ ) {
+ EventEmitter.decorate(this);
+
+ this.doc = toolboxDoc;
+ this.id = id;
+ this.className = className;
+ this.type = type;
+ this.noAutoHide = noAutoHide;
+ // consumeOutsideClicks cannot be used if the tooltip is not closed on click
+ this.consumeOutsideClicks = this.noAutoHide ? false : consumeOutsideClicks;
+ this.isMenuTooltip = isMenuTooltip;
+ this.useXulWrapper = this._isXULPopupAvailable() && useXulWrapper;
+ this.preferredWidth = "auto";
+ this.preferredHeight = "auto";
+
+ // The top window is used to attach click event listeners to close the tooltip if the
+ // user clicks on the content page.
+ this.topWindow = this._getTopWindow();
+
+ this._position = null;
+
+ this._onClick = this._onClick.bind(this);
+ this._onMouseup = this._onMouseup.bind(this);
+ this._onXulPanelHidden = this._onXulPanelHidden.bind(this);
+
+ this.container = this._createContainer();
+ if (this.useXulWrapper) {
+ // When using a XUL panel as the wrapper, the actual markup for the tooltip is as
+ // follows :
+ // <panel> <!-- XUL panel used to position the tooltip anywhere on screen -->
+ // <div> <! the actual tooltip-container element -->
+ this.xulPanelWrapper = this._createXulPanelWrapper();
+ this.doc.documentElement.appendChild(this.xulPanelWrapper);
+ this.xulPanelWrapper.appendChild(this.container);
+ } else if (this._hasXULRootElement()) {
+ this.doc.documentElement.appendChild(this.container);
+ } else {
+ // In non-XUL context the container is ready to use as is.
+ this.doc.body.appendChild(this.container);
+ }
}
-}
-
-module.exports.HTMLTooltip = HTMLTooltip;
-HTMLTooltip.prototype = {
/**
* The tooltip panel is the parentNode of the tooltip content.
*/
get panel() {
return this.container.querySelector(".tooltip-panel");
- },
+ }
/**
* The arrow element. Might be null depending on the tooltip type.
*/
get arrow() {
return this.container.querySelector(".tooltip-arrow");
- },
+ }
/**
* Retrieve the displayed position used for the tooltip. Null if the tooltip is hidden.
*/
get position() {
return this.isVisible() ? this._position : null;
- },
+ }
get toggle() {
if (!this._toggle) {
@@ -421,7 +420,7 @@ HTMLTooltip.prototype = {
}
return this._toggle;
- },
+ }
/**
* Set the preferred width/height of the panel content.
@@ -447,7 +446,7 @@ HTMLTooltip.prototype = {
setContentSize({ width = "auto", height = "auto" } = {}) {
this.preferredWidth = width;
this.preferredHeight = height;
- },
+ }
/**
* Update the HTMLTooltip content with a HTMLFragment using fluent for
@@ -471,7 +470,7 @@ HTMLTooltip.prototype = {
this.doc.l10n.resumeObserving();
this.setContentSize(contentSizeOptions);
- },
+ }
/**
* Show the tooltip next to the provided anchor element, or update the tooltip position
@@ -545,15 +544,15 @@ HTMLTooltip.prototype = {
this.container.classList.add("tooltip-shown");
this.emit("shown");
- },
+ }
startTogglingOnHover(baseNode, targetNodeCb, options) {
this.toggle.start(baseNode, targetNodeCb, options);
- },
+ }
stopTogglingOnHover() {
this.toggle.stop();
- },
+ }
_updateContainerBounds(anchor, { position, x = 0, y = 0 } = {}) {
// Get anchor geometry
@@ -672,7 +671,7 @@ HTMLTooltip.prototype = {
this.panel.scrollTop = currentScrollTop;
return { left, top };
- },
+ }
/**
* Calculate the following boundary rectangles:
@@ -761,7 +760,7 @@ HTMLTooltip.prototype = {
}
return { viewportRect, windowRect };
- },
+ }
_measureContainerSize() {
const xulParent = this.container.parentNode;
@@ -784,7 +783,7 @@ HTMLTooltip.prototype = {
}
return { width, height };
- },
+ }
/**
* Hide the current tooltip. The event "hidden" will be fired when the tooltip
@@ -830,12 +829,12 @@ HTMLTooltip.prototype = {
this._focusedElement.focus();
this._focusedElement = null;
}
- },
+ }
removeEventListeners() {
this.topWindow.removeEventListener("click", this._onClick, true);
this.topWindow.removeEventListener("mouseup", this._onMouseup, true);
- },
+ }
/**
* Check if the tooltip is currently displayed.
@@ -844,7 +843,7 @@ HTMLTooltip.prototype = {
*/
isVisible() {
return this.container.classList.contains("tooltip-visible");
- },
+ }
/**
* Destroy the tooltip instance. Hide the tooltip if displayed, remove the
@@ -861,7 +860,7 @@ HTMLTooltip.prototype = {
this._toggle.destroy();
this._toggle = null;
}
- },
+ }
_createContainer() {
const container = this.doc.createElementNS(XHTML_NS, "div");
@@ -890,7 +889,7 @@ HTMLTooltip.prototype = {
container.appendChild(arrow);
}
return container;
- },
+ }
_onClick(e) {
if (this._isInTooltipContainer(e.target)) {
@@ -902,7 +901,7 @@ HTMLTooltip.prototype = {
e.preventDefault();
e.stopPropagation();
}
- },
+ }
/**
* Hide the tooltip on mouseup rather than on click because the surrounding markup
@@ -916,7 +915,7 @@ HTMLTooltip.prototype = {
}
this.hide({ fromMouseup: true });
- },
+ }
_isInTooltipContainer(node) {
// Check if the target is the tooltip arrow.
@@ -950,13 +949,13 @@ HTMLTooltip.prototype = {
}
return false;
- },
+ }
_onXulPanelHidden() {
if (this.isVisible()) {
this.hide();
}
- },
+ }
/**
* Focus on the first focusable item in the tooltip.
@@ -969,7 +968,7 @@ HTMLTooltip.prototype = {
focusableElement.focus();
}
return !!focusableElement;
- },
+ }
/**
* Focus on the last focusable item in the tooltip.
@@ -984,22 +983,22 @@ HTMLTooltip.prototype = {
focusableElements[focusableElements.length - 1].focus();
}
return focusableElements.length !== 0;
- },
+ }
_getTopWindow() {
return DevToolsUtils.getTopWindow(this.doc.defaultView);
- },
+ }
/**
* Check if the tooltip's owner document has XUL root element.
*/
_hasXULRootElement() {
return this.doc.documentElement.namespaceURI === XUL_NS;
- },
+ }
_isXULPopupAvailable() {
return this.doc.nodePrincipal.isSystemPrincipal;
- },
+ }
_createXulPanelWrapper() {
const panel = this.doc.createXULElement("panel");
@@ -1028,7 +1027,7 @@ HTMLTooltip.prototype = {
panel.setAttribute("role", "presentation");
return panel;
- },
+ }
_showXulWrapperAt(left, top) {
this.xulPanelWrapper.addEventListener(
@@ -1038,7 +1037,7 @@ HTMLTooltip.prototype = {
const onPanelShown = listenOnce(this.xulPanelWrapper, "popupshown");
this.xulPanelWrapper.openPopupAtScreen(left, top, false);
return onPanelShown;
- },
+ }
_moveXulWrapperTo(left, top) {
// FIXME: moveTo should probably account for margins when called from
@@ -1049,7 +1048,7 @@ HTMLTooltip.prototype = {
.marginTop
);
this.xulPanelWrapper.moveTo(left + margin, top + margin);
- },
+ }
_hideXulWrapper() {
this.xulPanelWrapper.removeEventListener(
@@ -1065,7 +1064,7 @@ HTMLTooltip.prototype = {
const onPanelHidden = listenOnce(this.xulPanelWrapper, "popuphidden");
this.xulPanelWrapper.hidePopup();
return onPanelHidden;
- },
+ }
/**
* Convert from coordinates relative to the tooltip's document, to coordinates relative
@@ -1085,5 +1084,7 @@ HTMLTooltip.prototype = {
width,
height,
};
- },
-};
+ }
+}
+
+module.exports.HTMLTooltip = HTMLTooltip;