diff options
Diffstat (limited to 'toolkit/components/tooltiptext')
5 files changed, 178 insertions, 123 deletions
diff --git a/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs b/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs index 0ddd594cb6..96a3e8dd5f 100644 --- a/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs +++ b/toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs @@ -4,6 +4,37 @@ export function TooltipTextProvider() {} +function getFileInputTitleText(tipElement) { + let files = tipElement.files; + let bundle = Services.strings.createBundle( + "chrome://global/locale/layout/HtmlForm.properties" + ); + if (!files.length) { + return bundle.GetStringFromName( + tipElement.multiple ? "NoFilesSelected" : "NoFileSelected" + ); + } + let 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; + } + return titleText; +} + TooltipTextProvider.prototype = { getNodeText(tipElement, textOut, directionOut) { // Don't show the tooltip if the tooltip node is a document or browser. @@ -29,142 +60,98 @@ TooltipTextProvider.prototype = { 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; - } + for (; tipElement; tipElement = tipElement.flattenedTreeParentNode) { + if (tipElement.nodeType != defView.Node.ELEMENT_NODE) { + continue; + } + if (tipElement.namespaceURI == XUL_NS) { + lookingForSVGTitle = false; + // NOTE: getAttribute behaves differently for XUL so we can't rely on + // it returning null, see bug 232598. + titleText = tipElement.hasAttribute("tooltiptext") + ? tipElement.getAttribute("tooltiptext") + : null; + } else if (!defView.SVGElement.isInstance(tipElement)) { + lookingForSVGTitle = false; + titleText = tipElement.getAttribute("title"); + } - // If the element is an <input type='file'> 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) {} - } + if ( + (defView.HTMLAnchorElement.isInstance(tipElement) || + defView.HTMLAreaElement.isInstance(tipElement) || + defView.HTMLLinkElement.isInstance(tipElement) || + defView.SVGAElement.isInstance(tipElement)) && + tipElement.href + ) { + XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title"); + } - // 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 the element is invalid per HTML5 Forms specifications and has no title, + // show the constraint validation error message. + if ( + titleText == null && + (defView.HTMLInputElement.isInstance(tipElement) || + defView.HTMLTextAreaElement.isInstance(tipElement) || + defView.HTMLSelectElement.isInstance(tipElement) || + defView.HTMLButtonElement.isInstance(tipElement)) && + !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 ( - (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; - } + // If the element is an <input type='file'> without a title, we should show + // the current file selection. + if ( + titleText == null && + defView.HTMLInputElement.isInstance(tipElement) && + tipElement.type == "file" + ) { + try { + titleText = getFileInputTitleText(tipElement); + } catch (ex) {} + } + + if ( + lookingForSVGTitle && + tipElement.parentNode.nodeType != defView.Node.DOCUMENT_NODE + ) { + for (let childNode of tipElement.childNodes) { + if (defView.SVGTitleElement.isInstance(childNode)) { + titleText = childNode.textContent; + break; } } - - usedTipElement = tipElement; } - tipElement = tipElement.flattenedTreeParentNode; + // Check texts against null so that title="" can be used to undefine a + // title on a child element. + if (titleText != null || XLinkTitleText != null) { + break; + } } - 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"); - } + return [titleText, XLinkTitleText].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; + if (tipElement) { + direction = defView + .getComputedStyle(tipElement) + .getPropertyValue("direction"); } - return false; + directionOut.value = direction; + return true; } - ); + + return false; + }); }, classID: Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"), diff --git a/toolkit/components/tooltiptext/tests/browser.toml b/toolkit/components/tooltiptext/tests/browser.toml index 189f880be2..d03716e683 100644 --- a/toolkit/components/tooltiptext/tests/browser.toml +++ b/toolkit/components/tooltiptext/tests/browser.toml @@ -13,4 +13,6 @@ support-files = ["xul_tooltiptext.xhtml"] ["browser_input_file_tooltips.js"] skip-if = ["os == 'win' && os_version == '10.0'"] # Permafail on Win 10 (bug 1400368) +["browser_nac_tooltip.js"] + ["browser_shadow_dom_tooltip.js"] diff --git a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js index 7d6c5043c4..2f1385f37f 100644 --- a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js +++ b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js @@ -2,7 +2,7 @@ let tempFile; add_setup(async function () { - await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] }); + await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] }); tempFile = createTempFile(); registerCleanupFunction(function () { tempFile.remove(true); @@ -63,7 +63,7 @@ async function do_test(test) { if (test.value) { info("Creating mock filepicker to select files"); let MockFilePicker = SpecialPowers.MockFilePicker; - MockFilePicker.init(window); + MockFilePicker.init(window.browsingContext); MockFilePicker.returnValue = MockFilePicker.returnOK; MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", []); MockFilePicker.setFiles([tempFile]); diff --git a/toolkit/components/tooltiptext/tests/browser_nac_tooltip.js b/toolkit/components/tooltiptext/tests/browser_nac_tooltip.js new file mode 100644 index 0000000000..449c8b9da7 --- /dev/null +++ b/toolkit/components/tooltiptext/tests/browser_nac_tooltip.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +"use strict"; + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] }); +}); + +add_task(async function () { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "data:text/html,<!DOCTYPE html>", + }, + async function (browser) { + info("Moving mouse out of the way."); + await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 300, 300); + + await SpecialPowers.spawn(browser, [], function () { + let widget = content.document.insertAnonymousContent(); + widget.root.innerHTML = `<button style="pointer-events: auto; position: absolute; width: 200px; height: 200px;" title="foo">bar</button>`; + let tttp = Cc[ + "@mozilla.org/embedcomp/default-tooltiptextprovider;1" + ].getService(Ci.nsITooltipTextProvider); + + let text = {}; + let dir = {}; + ok( + tttp.getNodeText(widget.root.querySelector("button"), text, dir), + "A tooltip should be shown for NAC" + ); + is(text.value, "foo", "Tooltip text should be correct"); + }); + + let awaitTooltipOpen = new Promise(resolve => { + let tooltipId = Services.appinfo.browserTabsRemoteAutostart + ? "remoteBrowserTooltip" + : "aHTMLTooltip"; + let tooltip = document.getElementById(tooltipId); + tooltip.addEventListener( + "popupshown", + function (event) { + resolve(event.target); + }, + { once: true } + ); + }); + + info("Initial mouse move"); + await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 50, 5); + info("Waiting"); + await new Promise(resolve => setTimeout(resolve, 400)); + info("Second mouse move"); + await EventUtils.synthesizeAndWaitNativeMouseMove(browser, 70, 5); + info("Waiting for tooltip to open"); + let tooltip = await awaitTooltipOpen; + is( + tooltip.getAttribute("label"), + "foo", + "tooltip label should match expectation" + ); + } + ); +}); diff --git a/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js b/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js index 50386e07e2..4ceb918da1 100644 --- a/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js +++ b/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js @@ -1,7 +1,7 @@ /* eslint-disable mozilla/no-arbitrary-setTimeout */ add_setup(async function () { - await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] }); + await SpecialPowers.pushPrefEnv({ set: [["ui.tooltip.delay_ms", 0]] }); }); add_task(async function test_title_in_shadow_dom() { |