From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../tooltiptext/TooltipTextProvider.sys.mjs | 172 +++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs (limited to 'toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs') diff --git a/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs b/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs new file mode 100644 index 0000000000..0ddd594cb6 --- /dev/null +++ b/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs @@ -0,0 +1,172 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export function TooltipTextProvider() {} + +TooltipTextProvider.prototype = { + getNodeText(tipElement, textOut, directionOut) { + // Don't show the tooltip if the tooltip node is a document or browser. + // Caller should ensure the node is in (composed) document. + if ( + !tipElement || + !tipElement.ownerDocument || + tipElement.localName == "browser" + ) { + return false; + } + + var defView = tipElement.ownerGlobal; + // XXX Work around bug 350679: + // "Tooltips can be fired in documents with no view". + if (!defView) { + return false; + } + + const XLinkNS = "http://www.w3.org/1999/xlink"; + const XUL_NS = + "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + + var titleText = null; + var XLinkTitleText = null; + var SVGTitleText = null; + var XULtooltiptextText = null; + var lookingForSVGTitle = true; + var direction = tipElement.ownerDocument.dir; + + // If the element is invalid per HTML5 Forms specifications and has no title, + // show the constraint validation error message. + if ( + (defView.HTMLInputElement.isInstance(tipElement) || + defView.HTMLTextAreaElement.isInstance(tipElement) || + defView.HTMLSelectElement.isInstance(tipElement) || + defView.HTMLButtonElement.isInstance(tipElement)) && + !tipElement.hasAttribute("title") && + (!tipElement.form || !tipElement.form.noValidate) + ) { + // If the element is barred from constraint validation or valid, + // the validation message will be the empty string. + titleText = tipElement.validationMessage || null; + } + + // If the element is an without a title, we should show + // the current file selection. + if ( + !titleText && + defView.HTMLInputElement.isInstance(tipElement) && + tipElement.type == "file" && + !tipElement.hasAttribute("title") + ) { + let files = tipElement.files; + + try { + var bundle = Services.strings.createBundle( + "chrome://global/locale/layout/HtmlForm.properties" + ); + if (!files.length) { + if (tipElement.multiple) { + titleText = bundle.GetStringFromName("NoFilesSelected"); + } else { + titleText = bundle.GetStringFromName("NoFileSelected"); + } + } else { + titleText = files[0].name; + // For UX and performance (jank) reasons we cap the number of + // files that we list in the tooltip to 20 plus a "and xxx more" + // line, or to 21 if exactly 21 files were picked. + const TRUNCATED_FILE_COUNT = 20; + let count = Math.min(files.length, TRUNCATED_FILE_COUNT); + for (let i = 1; i < count; ++i) { + titleText += "\n" + files[i].name; + } + if (files.length == TRUNCATED_FILE_COUNT + 1) { + titleText += "\n" + files[TRUNCATED_FILE_COUNT].name; + } else if (files.length > TRUNCATED_FILE_COUNT + 1) { + const l10n = new Localization( + ["toolkit/global/htmlForm.ftl"], + true + ); + const andXMoreStr = l10n.formatValueSync( + "input-file-and-more-files", + { fileCount: files.length - TRUNCATED_FILE_COUNT } + ); + titleText += "\n" + andXMoreStr; + } + } + } catch (e) {} + } + + // Check texts against null so that title="" can be used to undefine a + // title on a child element. + let usedTipElement = null; + while ( + tipElement && + titleText == null && + XLinkTitleText == null && + SVGTitleText == null && + XULtooltiptextText == null + ) { + if (tipElement.nodeType == defView.Node.ELEMENT_NODE) { + if (tipElement.namespaceURI == XUL_NS) { + XULtooltiptextText = tipElement.hasAttribute("tooltiptext") + ? tipElement.getAttribute("tooltiptext") + : null; + } else if (!defView.SVGElement.isInstance(tipElement)) { + titleText = tipElement.getAttribute("title"); + } + + if ( + (defView.HTMLAnchorElement.isInstance(tipElement) || + defView.HTMLAreaElement.isInstance(tipElement) || + defView.HTMLLinkElement.isInstance(tipElement) || + defView.SVGAElement.isInstance(tipElement)) && + tipElement.href + ) { + XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title"); + } + if ( + lookingForSVGTitle && + (!defView.SVGElement.isInstance(tipElement) || + tipElement.parentNode.nodeType == defView.Node.DOCUMENT_NODE) + ) { + lookingForSVGTitle = false; + } + if (lookingForSVGTitle) { + for (let childNode of tipElement.childNodes) { + if (defView.SVGTitleElement.isInstance(childNode)) { + SVGTitleText = childNode.textContent; + break; + } + } + } + + usedTipElement = tipElement; + } + + tipElement = tipElement.flattenedTreeParentNode; + } + + return [titleText, XLinkTitleText, SVGTitleText, XULtooltiptextText].some( + function (t) { + if (t && /\S/.test(t)) { + // Make CRLF and CR render one line break each. + textOut.value = t.replace(/\r\n?/g, "\n"); + + if (usedTipElement) { + direction = defView + .getComputedStyle(usedTipElement) + .getPropertyValue("direction"); + } + + directionOut.value = direction; + return true; + } + + return false; + } + ); + }, + + classID: Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"), + QueryInterface: ChromeUtils.generateQI(["nsITooltipTextProvider"]), +}; -- cgit v1.2.3