/* 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/. */ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); function TooltipTextProvider() {} TooltipTextProvider.prototype = { getNodeText(tipElement, textOut, directionOut) { // Don't show the tooltip if the tooltip node is a document, browser, or disconnected. if (!tipElement || !tipElement.ownerDocument || tipElement.localName == "browser" || (tipElement.ownerDocument.compareDocumentPosition(tipElement) & tipElement.ownerDocument.DOCUMENT_POSITION_DISCONNECTED)) { return false; } var defView = tipElement.ownerDocument.defaultView; // 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 XULNS = "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 ((tipElement instanceof defView.HTMLInputElement || tipElement instanceof defView.HTMLTextAreaElement || tipElement instanceof defView.HTMLSelectElement || tipElement instanceof defView.HTMLButtonElement) && !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 <input type='file'> without a title, we should show // the current file selection. if (!titleText && tipElement instanceof defView.HTMLInputElement && 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 == 0) { 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) { let xmoreStr = bundle.GetStringFromName("AndNMoreFiles"); let xmoreNum = files.length - TRUNCATED_FILE_COUNT; let tmp = {}; Cu.import("resource://gre/modules/PluralForm.jsm", tmp); let andXMoreStr = tmp.PluralForm.get(xmoreNum, xmoreStr).replace("#1", xmoreNum); titleText += "\n" + andXMoreStr; } } } catch (e) {} } // Check texts against null so that title="" can be used to undefine a // title on a child element. while (tipElement && (titleText == null) && (XLinkTitleText == null) && (SVGTitleText == null) && (XULtooltiptextText == null)) { if (tipElement.nodeType == defView.Node.ELEMENT_NODE) { if (tipElement.namespaceURI == XULNS) XULtooltiptextText = tipElement.getAttribute("tooltiptext"); else if (!(tipElement instanceof defView.SVGElement)) titleText = tipElement.getAttribute("title"); if ((tipElement instanceof defView.HTMLAnchorElement || tipElement instanceof defView.HTMLAreaElement || tipElement instanceof defView.HTMLLinkElement || tipElement instanceof defView.SVGAElement) && tipElement.href) { XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title"); } if (lookingForSVGTitle && (!(tipElement instanceof defView.SVGElement) || tipElement.parentNode.nodeType == defView.Node.DOCUMENT_NODE)) { lookingForSVGTitle = false; } if (lookingForSVGTitle) { for (let childNode of tipElement.childNodes) { if (childNode instanceof defView.SVGTitleElement) { SVGTitleText = childNode.textContent; break; } } } direction = defView.getComputedStyle(tipElement, "") .getPropertyValue("direction"); } tipElement = tipElement.parentNode; } 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'); directionOut.value = direction; return true; } return false; }); }, classID : Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsITooltipTextProvider]), }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TooltipTextProvider]);