gecko-dev/toolkit/components/tooltiptext/TooltipTextProvider.js
Gijs Kruitbosch a59db0da4f Bug 1236991 - part 2: implement a default tooltiptextprovider in toolkit, r=enndeakin
MozReview-Commit-ID: LqtibkGoDjQ

--HG--
rename : toolkit/content/widgets/popup.xml => toolkit/components/tooltiptext/TooltipTextProvider.js
extra : rebase_source : a5ee0b204ed501871d1f19cbee52a58d5af89c65
extra : histedit_source : 7247b93e1fa7ab1acc72182df7b890c888046e1e
2016-04-12 21:50:47 +01:00

149 lines
5.8 KiB
JavaScript

/* 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]);