1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 export function TooltipTextProvider() {}
7 function getFileInputTitleText(tipElement) {
8 let files = tipElement.files;
9 let bundle = Services.strings.createBundle(
10 "chrome://global/locale/layout/HtmlForm.properties"
13 return bundle.GetStringFromName(
14 tipElement.multiple ? "NoFilesSelected" : "NoFileSelected"
17 let titleText = files[0].name;
18 // For UX and performance (jank) reasons we cap the number of
19 // files that we list in the tooltip to 20 plus a "and xxx more"
20 // line, or to 21 if exactly 21 files were picked.
21 const TRUNCATED_FILE_COUNT = 20;
22 let count = Math.min(files.length, TRUNCATED_FILE_COUNT);
23 for (let i = 1; i < count; ++i) {
24 titleText += "\n" + files[i].name;
26 if (files.length == TRUNCATED_FILE_COUNT + 1) {
27 titleText += "\n" + files[TRUNCATED_FILE_COUNT].name;
28 } else if (files.length > TRUNCATED_FILE_COUNT + 1) {
29 const l10n = new Localization(["toolkit/global/htmlForm.ftl"], true);
30 const andXMoreStr = l10n.formatValueSync("input-file-and-more-files", {
31 fileCount: files.length - TRUNCATED_FILE_COUNT,
33 titleText += "\n" + andXMoreStr;
38 TooltipTextProvider.prototype = {
39 getNodeText(tipElement, textOut, directionOut) {
40 // Don't show the tooltip if the tooltip node is a document or browser.
41 // Caller should ensure the node is in (composed) document.
44 !tipElement.ownerDocument ||
45 tipElement.localName == "browser"
50 var defView = tipElement.ownerGlobal;
51 // XXX Work around bug 350679:
52 // "Tooltips can be fired in documents with no view".
57 const XLinkNS = "http://www.w3.org/1999/xlink";
59 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
62 var XLinkTitleText = null;
63 var lookingForSVGTitle = true;
64 var direction = tipElement.ownerDocument.dir;
66 for (; tipElement; tipElement = tipElement.flattenedTreeParentNode) {
67 if (tipElement.nodeType != defView.Node.ELEMENT_NODE) {
70 if (tipElement.namespaceURI == XUL_NS) {
71 lookingForSVGTitle = false;
72 titleText = tipElement.getAttribute("tooltiptext");
73 } else if (!defView.SVGElement.isInstance(tipElement)) {
74 lookingForSVGTitle = false;
75 titleText = tipElement.getAttribute("title");
79 (defView.HTMLAnchorElement.isInstance(tipElement) ||
80 defView.HTMLAreaElement.isInstance(tipElement) ||
81 defView.HTMLLinkElement.isInstance(tipElement) ||
82 defView.SVGAElement.isInstance(tipElement)) &&
85 XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
88 // If the element is invalid per HTML5 Forms specifications and has no title,
89 // show the constraint validation error message.
92 (defView.HTMLInputElement.isInstance(tipElement) ||
93 defView.HTMLTextAreaElement.isInstance(tipElement) ||
94 defView.HTMLSelectElement.isInstance(tipElement) ||
95 defView.HTMLButtonElement.isInstance(tipElement)) &&
96 !tipElement.form?.noValidate
98 // If the element is barred from constraint validation or valid,
99 // the validation message will be the empty string.
100 titleText = tipElement.validationMessage || null;
103 // If the element is an <input type='file'> without a title, we should show
104 // the current file selection.
107 defView.HTMLInputElement.isInstance(tipElement) &&
108 tipElement.type == "file"
111 titleText = getFileInputTitleText(tipElement);
116 lookingForSVGTitle &&
117 tipElement.parentNode.nodeType != defView.Node.DOCUMENT_NODE
119 for (let childNode of tipElement.childNodes) {
120 if (defView.SVGTitleElement.isInstance(childNode)) {
121 titleText = childNode.textContent;
127 // Check texts against null so that title="" can be used to undefine a
128 // title on a child element.
129 if (titleText != null || XLinkTitleText != null) {
134 return [titleText, XLinkTitleText].some(function (t) {
135 if (t && /\S/.test(t)) {
136 // Make CRLF and CR render one line break each.
137 textOut.value = t.replace(/\r\n?/g, "\n");
141 .getComputedStyle(tipElement)
142 .getPropertyValue("direction");
145 directionOut.value = direction;
153 classID: Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"),
154 QueryInterface: ChromeUtils.generateQI(["nsITooltipTextProvider"]),