summaryrefslogtreecommitdiffstats
path: root/toolkit/components/tooltiptext
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/tooltiptext
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/tooltiptext')
-rw-r--r--toolkit/components/tooltiptext/TooltipTextProvider.sys.mjs172
-rw-r--r--toolkit/components/tooltiptext/components.conf14
-rw-r--r--toolkit/components/tooltiptext/moz.build18
-rw-r--r--toolkit/components/tooltiptext/tests/browser.toml16
-rw-r--r--toolkit/components/tooltiptext/tests/browser_bug329212.js48
-rw-r--r--toolkit/components/tooltiptext/tests/browser_bug331772_xul_tooltiptext_in_html.js30
-rw-r--r--toolkit/components/tooltiptext/tests/browser_bug561623.js33
-rw-r--r--toolkit/components/tooltiptext/tests/browser_bug581947.js107
-rw-r--r--toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js131
-rw-r--r--toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js166
-rw-r--r--toolkit/components/tooltiptext/tests/title_test.svg59
-rw-r--r--toolkit/components/tooltiptext/tests/xul_tooltiptext.xhtml12
12 files changed, 806 insertions, 0 deletions
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 <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) {}
+ }
+
+ // 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"]),
+};
diff --git a/toolkit/components/tooltiptext/components.conf b/toolkit/components/tooltiptext/components.conf
new file mode 100644
index 0000000000..3f2e6b17d3
--- /dev/null
+++ b/toolkit/components/tooltiptext/components.conf
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{f376627f-0bbc-47b8-887e-fc92574cc91f}',
+ 'contract_ids': ['@mozilla.org/embedcomp/default-tooltiptextprovider;1'],
+ 'esModule': 'resource://gre/modules/TooltipTextProvider.sys.mjs',
+ 'constructor': 'TooltipTextProvider',
+ },
+]
diff --git a/toolkit/components/tooltiptext/moz.build b/toolkit/components/tooltiptext/moz.build
new file mode 100644
index 0000000000..e8cfe40620
--- /dev/null
+++ b/toolkit/components/tooltiptext/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += ["tests/browser.toml"]
+
+EXTRA_JS_MODULES += [
+ "TooltipTextProvider.sys.mjs",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "General")
diff --git a/toolkit/components/tooltiptext/tests/browser.toml b/toolkit/components/tooltiptext/tests/browser.toml
new file mode 100644
index 0000000000..189f880be2
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser.toml
@@ -0,0 +1,16 @@
+[DEFAULT]
+
+["browser_bug329212.js"]
+support-files = ["title_test.svg"]
+
+["browser_bug331772_xul_tooltiptext_in_html.js"]
+support-files = ["xul_tooltiptext.xhtml"]
+
+["browser_bug561623.js"]
+
+["browser_bug581947.js"]
+
+["browser_input_file_tooltips.js"]
+skip-if = ["os == 'win' && os_version == '10.0'"] # Permafail on Win 10 (bug 1400368)
+
+["browser_shadow_dom_tooltip.js"]
diff --git a/toolkit/components/tooltiptext/tests/browser_bug329212.js b/toolkit/components/tooltiptext/tests/browser_bug329212.js
new file mode 100644
index 0000000000..d669e60c26
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_bug329212.js
@@ -0,0 +1,48 @@
+"use strict";
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/toolkit/components/tooltiptext/tests/title_test.svg",
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [""], function () {
+ let tttp = Cc[
+ "@mozilla.org/embedcomp/default-tooltiptextprovider;1"
+ ].getService(Ci.nsITooltipTextProvider);
+ function checkElement(id, expectedTooltipText) {
+ let el = content.document.getElementById(id);
+ let textObj = {};
+ let shouldHaveTooltip = expectedTooltipText !== null;
+ is(
+ tttp.getNodeText(el, textObj, {}),
+ shouldHaveTooltip,
+ "element " +
+ id +
+ " should " +
+ (shouldHaveTooltip ? "" : "not ") +
+ "have a tooltip"
+ );
+ if (shouldHaveTooltip) {
+ is(
+ textObj.value,
+ expectedTooltipText,
+ "element " + id + " should have the right tooltip text"
+ );
+ }
+ }
+ checkElement("svg1", "This is a non-root SVG element title");
+ checkElement("text1", "\n\n\n This is a title\n\n ");
+ checkElement("text2", null);
+ checkElement("text3", null);
+ checkElement("link1", "\n This is a title\n ");
+ checkElement("text4", "\n This is a title\n ");
+ checkElement("link2", null);
+ checkElement("link3", "This is an xlink:title attribute");
+ checkElement("link4", "This is an xlink:title attribute");
+ checkElement("text5", null);
+ });
+ }
+ );
+});
diff --git a/toolkit/components/tooltiptext/tests/browser_bug331772_xul_tooltiptext_in_html.js b/toolkit/components/tooltiptext/tests/browser_bug331772_xul_tooltiptext_in_html.js
new file mode 100644
index 0000000000..61c50e5422
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_bug331772_xul_tooltiptext_in_html.js
@@ -0,0 +1,30 @@
+/**
+ * Tests that the tooltiptext attribute is used for XUL elements in an HTML doc.
+ */
+add_task(async function () {
+ await SpecialPowers.pushPermissions([
+ { type: "allowXULXBL", allow: true, context: "http://mochi.test:8888" },
+ ]);
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "http://mochi.test:8888/browser/toolkit/components/tooltiptext/tests/xul_tooltiptext.xhtml",
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [""], function () {
+ let textObj = {};
+ let tttp = Cc[
+ "@mozilla.org/embedcomp/default-tooltiptextprovider;1"
+ ].getService(Ci.nsITooltipTextProvider);
+ let xulToolbarButton =
+ content.document.getElementById("xulToolbarButton");
+ ok(
+ tttp.getNodeText(xulToolbarButton, textObj, {}),
+ "should get tooltiptext"
+ );
+ is(textObj.value, "XUL tooltiptext");
+ });
+ }
+ );
+});
diff --git a/toolkit/components/tooltiptext/tests/browser_bug561623.js b/toolkit/components/tooltiptext/tests/browser_bug561623.js
new file mode 100644
index 0000000000..93f68d307f
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_bug561623.js
@@ -0,0 +1,33 @@
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "data:text/html,<!DOCTYPE html><html><body><input id='i'></body></html>",
+ },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [""], function () {
+ let tttp = Cc[
+ "@mozilla.org/embedcomp/default-tooltiptextprovider;1"
+ ].getService(Ci.nsITooltipTextProvider);
+ let i = content.document.getElementById("i");
+
+ ok(
+ !tttp.getNodeText(i, {}, {}),
+ "No tooltip should be shown when @title is null"
+ );
+
+ i.title = "foo";
+ ok(
+ tttp.getNodeText(i, {}, {}),
+ "A tooltip should be shown when @title is not the empty string"
+ );
+
+ i.pattern = "bar";
+ ok(
+ tttp.getNodeText(i, {}, {}),
+ "A tooltip should be shown when @title is not the empty string"
+ );
+ });
+ }
+ );
+});
diff --git a/toolkit/components/tooltiptext/tests/browser_bug581947.js b/toolkit/components/tooltiptext/tests/browser_bug581947.js
new file mode 100644
index 0000000000..6e5eb9ea14
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_bug581947.js
@@ -0,0 +1,107 @@
+function check(aBrowser, aElementName, aBarred, aType) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [[aElementName, aBarred, aType]],
+ async function ([aElementName, aBarred, aType]) {
+ let e = content.document.createElement(aElementName);
+ let contentElement = content.document.getElementById("content");
+ contentElement.appendChild(e);
+
+ if (aType) {
+ e.type = aType;
+ }
+
+ let tttp = Cc[
+ "@mozilla.org/embedcomp/default-tooltiptextprovider;1"
+ ].getService(Ci.nsITooltipTextProvider);
+ ok(
+ !tttp.getNodeText(e, {}, {}),
+ "No tooltip should be shown when the element is valid"
+ );
+
+ e.setCustomValidity("foo");
+ if (aBarred) {
+ ok(
+ !tttp.getNodeText(e, {}, {}),
+ "No tooltip should be shown when the element is barred from constraint validation"
+ );
+ } else {
+ ok(
+ tttp.getNodeText(e, {}, {}),
+ e.tagName + " A tooltip should be shown when the element isn't valid"
+ );
+ }
+
+ e.setAttribute("title", "");
+ ok(
+ !tttp.getNodeText(e, {}, {}),
+ "No tooltip should be shown if the title attribute is set"
+ );
+
+ e.removeAttribute("title");
+ contentElement.setAttribute("novalidate", "");
+ ok(
+ !tttp.getNodeText(e, {}, {}),
+ "No tooltip should be shown if the novalidate attribute is set on the form owner"
+ );
+ contentElement.removeAttribute("novalidate");
+
+ e.remove();
+ }
+ );
+}
+
+function todo_check(aBrowser, aElementName, aBarred) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [[aElementName, aBarred]],
+ async function ([aElementName, aBarred]) {
+ let e = content.document.createElement(aElementName);
+ let contentElement = content.document.getElementById("content");
+ contentElement.appendChild(e);
+
+ let caught = false;
+ try {
+ e.setCustomValidity("foo");
+ } catch (e) {
+ caught = true;
+ }
+
+ todo(!caught, "setCustomValidity should exist for " + aElementName);
+
+ e.remove();
+ }
+ );
+}
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "data:text/html,<!DOCTYPE html><html><body><form id='content'></form></body></html>",
+ },
+ async function (browser) {
+ let testData = [
+ /* element name, barred */
+ ["input", false, null],
+ ["textarea", false, null],
+ ["button", true, "button"],
+ ["button", false, "submit"],
+ ["select", false, null],
+ ["output", true, null],
+ ["fieldset", true, null],
+ ["object", true, null],
+ ];
+
+ for (let data of testData) {
+ await check(browser, data[0], data[1], data[2]);
+ }
+
+ let todo_testData = [["keygen", "false"]];
+
+ for (let data of todo_testData) {
+ await todo_check(browser, data[0], data[1]);
+ }
+ }
+ );
+});
diff --git a/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
new file mode 100644
index 0000000000..7d6c5043c4
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_input_file_tooltips.js
@@ -0,0 +1,131 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+let tempFile;
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] });
+ tempFile = createTempFile();
+ registerCleanupFunction(function () {
+ tempFile.remove(true);
+ });
+});
+
+add_task(async function test_singlefile_selected() {
+ await do_test({ value: true, result: "testfile_bug1251809" });
+});
+
+add_task(async function test_title_set() {
+ await do_test({ title: "foo", result: "foo" });
+});
+
+add_task(async function test_nofile_selected() {
+ await do_test({ result: "No file selected." });
+});
+
+add_task(async function test_multipleset_nofile_selected() {
+ await do_test({ multiple: true, result: "No files selected." });
+});
+
+add_task(async function test_requiredset() {
+ await do_test({ required: true, result: "Please select a file." });
+});
+
+async function do_test(test) {
+ info(`starting test ${JSON.stringify(test)}`);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Moving mouse out of the way.");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(
+ tab.linkedBrowser,
+ 300,
+ 300
+ );
+
+ info("creating input field");
+ await SpecialPowers.spawn(tab.linkedBrowser, [test], async function (test) {
+ let doc = content.document;
+ let input = doc.createElement("input");
+ doc.body.appendChild(input);
+ input.id = "test_input";
+ input.setAttribute("style", "position: absolute; top: 0; left: 0;");
+ input.type = "file";
+ if (test.title) {
+ input.setAttribute("title", test.title);
+ }
+ if (test.multiple) {
+ input.multiple = true;
+ }
+ if (test.required) {
+ input.required = true;
+ }
+ });
+
+ if (test.value) {
+ info("Creating mock filepicker to select files");
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window);
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ MockFilePicker.displayDirectory = FileUtils.getDir("TmpD", []);
+ MockFilePicker.setFiles([tempFile]);
+ MockFilePicker.afterOpenCallback = MockFilePicker.cleanup;
+
+ try {
+ // Open the File Picker dialog (MockFilePicker) to select
+ // the files for the test.
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#test_input",
+ {},
+ tab.linkedBrowser
+ );
+ info("Waiting for the input to have the requisite files");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let input = content.document.querySelector("#test_input");
+ await ContentTaskUtils.waitForCondition(
+ () => input.files.length,
+ "The input should have at least one file selected"
+ );
+ info(`The input has ${input.files.length} file(s) selected.`);
+ });
+ } catch (e) {}
+ } else {
+ info("No real file selection required.");
+ }
+
+ 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(tab.linkedBrowser, 50, 5);
+ info("Waiting");
+ await new Promise(resolve => setTimeout(resolve, 400));
+ info("Second mouse move");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
+ info("Waiting for tooltip to open");
+ let tooltip = await awaitTooltipOpen;
+
+ is(
+ tooltip.getAttribute("label"),
+ test.result,
+ "tooltip label should match expectation"
+ );
+
+ info("Closing tab");
+ BrowserTestUtils.removeTab(tab);
+}
+
+function createTempFile() {
+ let file = FileUtils.getDir("TmpD", []);
+ file.append("testfile_bug1251809");
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ return file;
+}
diff --git a/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js b/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js
new file mode 100644
index 0000000000..50386e07e2
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/browser_shadow_dom_tooltip.js
@@ -0,0 +1,166 @@
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] });
+});
+
+add_task(async function test_title_in_shadow_dom() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Moving mouse out of the way.");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(
+ tab.linkedBrowser,
+ 300,
+ 300
+ );
+
+ info("creating host");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let doc = content.document;
+ let host = doc.createElement("div");
+ doc.body.appendChild(host);
+ host.setAttribute("style", "position: absolute; top: 0; left: 0;");
+ var sr = host.attachShadow({ mode: "closed" });
+ sr.innerHTML =
+ "<div title='shadow' style='width: 200px; height: 200px;'>shadow</div>";
+ });
+
+ 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(tab.linkedBrowser, 50, 5);
+ info("Waiting");
+ await new Promise(resolve => setTimeout(resolve, 400));
+ info("Second mouse move");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
+ info("Waiting for tooltip to open");
+ let tooltip = await awaitTooltipOpen;
+
+ is(
+ tooltip.getAttribute("label"),
+ "shadow",
+ "tooltip label should match expectation"
+ );
+
+ info("Closing tab");
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_title_in_light_dom() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Moving mouse out of the way.");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(
+ tab.linkedBrowser,
+ 300,
+ 300
+ );
+
+ info("creating host");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let doc = content.document;
+ let host = doc.createElement("div");
+ host.title = "light";
+ doc.body.appendChild(host);
+ host.setAttribute("style", "position: absolute; top: 0; left: 0;");
+ var sr = host.attachShadow({ mode: "closed" });
+ sr.innerHTML = "<div style='width: 200px; height: 200px;'>shadow</div>";
+ });
+
+ 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(tab.linkedBrowser, 50, 5);
+ info("Waiting");
+ await new Promise(resolve => setTimeout(resolve, 400));
+ info("Second mouse move");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
+ info("Waiting for tooltip to open");
+ let tooltip = await awaitTooltipOpen;
+
+ is(
+ tooltip.getAttribute("label"),
+ "light",
+ "tooltip label should match expectation"
+ );
+
+ info("Closing tab");
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_title_through_slot() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ info("Moving mouse out of the way.");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(
+ tab.linkedBrowser,
+ 300,
+ 300
+ );
+
+ info("creating host");
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let doc = content.document;
+ let host = doc.createElement("div");
+ host.title = "light";
+ host.innerHTML = "<div style='width: 200px; height: 200px;'>light</div>";
+ doc.body.appendChild(host);
+ host.setAttribute("style", "position: absolute; top: 0; left: 0;");
+ var sr = host.attachShadow({ mode: "closed" });
+ sr.innerHTML =
+ "<div title='shadow' style='width: 200px; height: 200px;'><slot></slot></div>";
+ });
+
+ 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(tab.linkedBrowser, 50, 5);
+ info("Waiting");
+ await new Promise(resolve => setTimeout(resolve, 400));
+ info("Second mouse move");
+ await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
+ info("Waiting for tooltip to open");
+ let tooltip = await awaitTooltipOpen;
+
+ is(
+ tooltip.getAttribute("label"),
+ "shadow",
+ "tooltip label should match expectation"
+ );
+
+ info("Closing tab");
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/tooltiptext/tests/title_test.svg b/toolkit/components/tooltiptext/tests/title_test.svg
new file mode 100644
index 0000000000..80390a3cca
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/title_test.svg
@@ -0,0 +1,59 @@
+<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title>This is a root SVG element's title</title>
+ <foreignObject>
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <svg xmlns="http://www.w3.org/2000/svg" id="svg1">
+ <title>This is a non-root SVG element title</title>
+ </svg>
+ </body>
+ </html>
+ </foreignObject>
+ <text id="text1" x="10px" y="32px" font-size="24px">
+ This contains only &lt;title&gt;
+ <title>
+
+
+ This is a title
+
+ </title>
+ </text>
+ <text id="text2" x="10px" y="96px" font-size="24px">
+ This contains only &lt;desc&gt;
+ <desc>This is a desc</desc>
+ </text>
+ <text id="text3" x="10px" y="128px" font-size="24px" title="ignored for SVG">
+ This contains nothing.
+ </text>
+ <a id="link1" href="#">
+ This link contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ <text id="text4" x="10px" y="192px" font-size="24px">
+ </text>
+ </a>
+ <a id="link2" href="#">
+ <text x="10px" y="192px" font-size="24px">
+ This text contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ </text>
+ </a>
+ <a id="link3" href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="224px" font-size="24px">
+ This link contains &lt;title&gt; &amp; xlink:title attr.
+ <title>This is a title</title>
+ </text>
+ </a>
+ <a id="link4" href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="256px" font-size="24px">
+ This link contains xlink:title attr.
+ </text>
+ </a>
+ <text id="text5" x="10px" y="160px" font-size="24px"
+ xlink:title="This is an xlink:title attribute but it isn't on a link" >
+ This contains nothing.
+ </text>
+</svg>
diff --git a/toolkit/components/tooltiptext/tests/xul_tooltiptext.xhtml b/toolkit/components/tooltiptext/tests/xul_tooltiptext.xhtml
new file mode 100644
index 0000000000..8288ffc5fc
--- /dev/null
+++ b/toolkit/components/tooltiptext/tests/xul_tooltiptext.xhtml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <xul:toolbox xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <toolbar>
+ <toolbarbutton id="xulToolbarButton"
+ tooltiptext="XUL tooltiptext"
+ title="XUL title"/>
+ </toolbar>
+ </xul:toolbox>
+</html>