зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1259834 - Create basic HTML tooltip API;r=bgrins
First implementation of HTML based tooltip to be used in devtools instead of XUL panels. API is similar to the current API of Tooltip.js MozReview-Commit-ID: 8njiKBubLSj --HG-- extra : rebase_source : 930bf7aef48e6c16d7a560d261e2bfd06fe02a63 extra : source : 09874a1e6f2c942a1f9de827fedd14da7e67a6e5
This commit is contained in:
Родитель
7c22dba684
Коммит
b873e263bd
|
@ -421,3 +421,21 @@ function waitForContextMenu(popup, button, onShown, onHidden) {
|
|||
button.ownerDocument.defaultView);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper to push a temporary preference. Wrapper on SpecialPowers
|
||||
* pushPrefEnv that returns a promise resolving when the preferences have been
|
||||
* updated.
|
||||
*
|
||||
* @param {String} preferenceName
|
||||
* The name of the preference to updated
|
||||
* @param {} value
|
||||
* The preference value, type can vary
|
||||
* @return {Promise} resolves when the preferences have been updated
|
||||
*/
|
||||
function pushPref(preferenceName, value) {
|
||||
return new Promise(resolve => {
|
||||
let options = {"set": [[preferenceName, value]]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -129,6 +129,7 @@ devtools.jar:
|
|||
content/shared/widgets/spectrum-frame.xhtml (shared/widgets/spectrum-frame.xhtml)
|
||||
content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
|
||||
content/shared/widgets/cubic-bezier-frame.xhtml (shared/widgets/cubic-bezier-frame.xhtml)
|
||||
content/shared/widgets/tooltip-frame.xhtml (shared/widgets/tooltip-frame.xhtml)
|
||||
content/shared/widgets/cubic-bezier.css (shared/widgets/cubic-bezier.css)
|
||||
content/shared/widgets/mdn-docs-frame.xhtml (shared/widgets/mdn-docs-frame.xhtml)
|
||||
content/shared/widgets/mdn-docs.css (shared/widgets/mdn-docs.css)
|
||||
|
|
|
@ -10,6 +10,7 @@ support-files =
|
|||
browser_devices.json
|
||||
doc_options-view.xul
|
||||
head.js
|
||||
helper_html_tooltip.js
|
||||
html-mdn-css-basic-testing.html
|
||||
html-mdn-css-no-summary.html
|
||||
html-mdn-css-no-summary-or-syntax.html
|
||||
|
@ -111,6 +112,11 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
|||
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
||||
[browser_graphs-16.js]
|
||||
skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts
|
||||
[browser_html_tooltip-01.js]
|
||||
[browser_html_tooltip-02.js]
|
||||
[browser_html_tooltip-03.js]
|
||||
[browser_html_tooltip-04.js]
|
||||
[browser_html_tooltip-05.js]
|
||||
[browser_inplace-editor-01.js]
|
||||
[browser_inplace-editor-02.js]
|
||||
[browser_inplace-editor_maxwidth.js]
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip show & hide methods.
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" flex="1">test1</hbox>
|
||||
<hbox id="box2" flex="1">test2</hbox>
|
||||
<hbox id="box3" flex="1">test3</hbox>
|
||||
<hbox id="box4" flex="1">test4</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "50px";
|
||||
div.style.boxSizing = "border-box";
|
||||
div.textContent = "tooltip";
|
||||
return div;
|
||||
}
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
|
||||
info("Set tooltip content");
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
|
||||
info("Show the tooltip and check the expected events are fired.");
|
||||
|
||||
let shown = 0;
|
||||
tooltip.on("shown", () => shown++);
|
||||
|
||||
let onShown = tooltip.once("shown");
|
||||
tooltip.show(doc.getElementById("box1"));
|
||||
|
||||
yield onShown;
|
||||
is(shown, 1, "Event shown was fired once");
|
||||
|
||||
is(tooltip.isVisible(), true, "Tooltip is visible");
|
||||
|
||||
info("Hide the tooltip and check the expected events are fired.");
|
||||
|
||||
let hidden = 0;
|
||||
tooltip.on("hidden", () => hidden++);
|
||||
|
||||
let onPopupHidden = tooltip.once("hidden");
|
||||
tooltip.hide();
|
||||
|
||||
yield onPopupHidden;
|
||||
is(hidden, 1, "Event hidden was fired once");
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip is closed when clicking outside of its container.
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" flex="1">test1</hbox>
|
||||
<hbox id="box2" flex="1">test2</hbox>
|
||||
<hbox id="box3" flex="1">test3</hbox>
|
||||
<hbox id="box4" flex="1">test4</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
yield testTooltipNotClosingOnInsideClick(doc);
|
||||
yield testConsumeOutsideClicksFalse(doc);
|
||||
yield testConsumeOutsideClicksTrue(doc);
|
||||
});
|
||||
|
||||
function* testTooltipNotClosingOnInsideClick(doc) {
|
||||
info("Test a tooltip is not closed when clicking inside itself");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
|
||||
let onTooltipContainerClick = once(tooltip.container, "click");
|
||||
EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
|
||||
yield onTooltipContainerClick;
|
||||
is(tooltip.isVisible(), true, "Tooltip is still visible");
|
||||
|
||||
tooltip.destroy();
|
||||
}
|
||||
|
||||
function* testConsumeOutsideClicksFalse(doc) {
|
||||
info("Test closing a tooltip via click with consumeOutsideClicks: false");
|
||||
let box4 = doc.getElementById("box4");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false});
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
|
||||
let onBox4Clicked = once(box4, "click");
|
||||
let onHidden = once(tooltip, "hidden");
|
||||
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
|
||||
yield onHidden;
|
||||
yield onBox4Clicked;
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is hidden");
|
||||
|
||||
tooltip.destroy();
|
||||
}
|
||||
|
||||
function* testConsumeOutsideClicksTrue(doc) {
|
||||
info("Test closing a tooltip via click with consumeOutsideClicks: true");
|
||||
let box4 = doc.getElementById("box4");
|
||||
|
||||
// Count clicks on box4
|
||||
let box4clicks = 0;
|
||||
box4.addEventListener("click", () => box4clicks++);
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: true});
|
||||
yield tooltip.setContent(getTooltipContent(doc), 100, 50);
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
|
||||
let onHidden = once(tooltip, "hidden");
|
||||
EventUtils.synthesizeMouseAtCenter(box4, {}, doc.defaultView);
|
||||
yield onHidden;
|
||||
|
||||
is(box4clicks, 0, "box4 catched no click event");
|
||||
is(tooltip.isVisible(), false, "Tooltip is hidden");
|
||||
|
||||
tooltip.destroy();
|
||||
}
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "50px";
|
||||
div.style.boxSizing = "border-box";
|
||||
div.textContent = "tooltip";
|
||||
return div;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip autofocus configuration option.
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" flex="1">test1</hbox>
|
||||
<hbox id="box2" flex="1">test2</hbox>
|
||||
<hbox id="box3" flex="1">test3</hbox>
|
||||
<hbox id="box4" flex="1">
|
||||
<textbox id="box4-input"></textbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
add_task(function* () {
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
yield testTooltipWithAutoFocus(doc);
|
||||
yield testTooltipWithoutAutoFocus(doc);
|
||||
});
|
||||
|
||||
function* testTooltipWithAutoFocus(doc) {
|
||||
info("Test a tooltip with autofocus takes focus when displayed");
|
||||
let textbox = doc.querySelector("textbox");
|
||||
|
||||
info("Focus a XUL textbox");
|
||||
let onInputFocus = once(textbox, "focus");
|
||||
EventUtils.synthesizeMouseAtCenter(textbox, {}, doc.defaultView);
|
||||
yield onInputFocus;
|
||||
|
||||
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {autofocus: true});
|
||||
let tooltipNode = getTooltipContent(doc);
|
||||
yield tooltip.setContent(tooltipNode, 150, 50);
|
||||
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
is(getFocusedDocument(doc), tooltipNode.ownerDocument,
|
||||
"Focus is in the tooltip document");
|
||||
|
||||
yield hideTooltip(tooltip);
|
||||
}
|
||||
|
||||
function* testTooltipWithoutAutoFocus(doc) {
|
||||
info("Test a tooltip can be closed by clicking outside");
|
||||
let textbox = doc.querySelector("textbox");
|
||||
|
||||
info("Focus a XUL textbox");
|
||||
let onInputFocus = once(textbox, "focus");
|
||||
EventUtils.synthesizeMouseAtCenter(textbox, {}, doc.defaultView);
|
||||
yield onInputFocus;
|
||||
|
||||
is(getFocusedDocument(doc), doc, "Focus is in the XUL document");
|
||||
|
||||
let tooltip = new HTMLTooltip({doc}, {autofocus: false});
|
||||
let tooltipNode = getTooltipContent(doc);
|
||||
yield tooltip.setContent(tooltipNode, 150, 50);
|
||||
|
||||
yield showTooltip(tooltip, doc.getElementById("box1"));
|
||||
is(getFocusedDocument(doc), doc, "Focus is still in the XUL document");
|
||||
|
||||
yield hideTooltip(tooltip);
|
||||
}
|
||||
|
||||
function getFocusedDocument(doc) {
|
||||
let activeElement = doc.activeElement;
|
||||
while (activeElement && activeElement.contentDocument) {
|
||||
activeElement = activeElement.contentDocument.activeElement;
|
||||
}
|
||||
return activeElement.ownerDocument;
|
||||
}
|
||||
|
||||
function getTooltipContent(doc) {
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "50px";
|
||||
div.style.boxSizing = "border-box";
|
||||
return div;
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip positioning for a small tooltip element (should aways
|
||||
* find a way to fit).
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox style="height: 10px">spacer</hbox>
|
||||
<hbox id="box1" style="height: 50px">test1</hbox>
|
||||
<hbox id="box2" style="height: 50px">test2</hbox>
|
||||
<hbox flex="1">MIDDLE</hbox>
|
||||
<hbox id="box3" style="height: 50px">test3</hbox>
|
||||
<hbox id="box4" style="height: 50px">test4</hbox>
|
||||
<hbox style="height: 10px">spacer</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLTIP_HEIGHT = 30;
|
||||
const TOOLTIP_WIDTH = 100;
|
||||
|
||||
add_task(function* () {
|
||||
// Force the toolbox to be 400px high;
|
||||
yield pushPref("devtools.toolbox.footer.height", 400);
|
||||
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
info("Create HTML tooltip");
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "100%";
|
||||
yield tooltip.setContent(div, TOOLTIP_WIDTH, TOOLTIP_HEIGHT);
|
||||
|
||||
let box1 = doc.getElementById("box1");
|
||||
let box2 = doc.getElementById("box2");
|
||||
let box3 = doc.getElementById("box3");
|
||||
let box4 = doc.getElementById("box4");
|
||||
let height = TOOLTIP_HEIGHT, width = TOOLTIP_WIDTH;
|
||||
|
||||
// box1: Can only fit below box1
|
||||
info("Display the tooltip on box1.");
|
||||
yield showTooltip(tooltip, box1);
|
||||
let expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box1.");
|
||||
yield showTooltip(tooltip, box1, "top");
|
||||
expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box2: Can fit above or below, will default to bottom, more height
|
||||
// available.
|
||||
info("Try to display the tooltip on box2.");
|
||||
yield showTooltip(tooltip, box2);
|
||||
expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box2.");
|
||||
yield showTooltip(tooltip, box2, "top");
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box3: Can fit above or below, will default to top, more height available.
|
||||
info("Try to display the tooltip on box3.");
|
||||
yield showTooltip(tooltip, box3);
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box3.");
|
||||
yield showTooltip(tooltip, box3, "bottom");
|
||||
expectedTooltipGeometry = {position: "bottom", height, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box4: Can only fit above box4
|
||||
info("Display the tooltip on box4.");
|
||||
yield showTooltip(tooltip, box4);
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box4.");
|
||||
yield showTooltip(tooltip, box4, "bottom");
|
||||
expectedTooltipGeometry = {position: "top", height, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
});
|
|
@ -0,0 +1,108 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* import-globals-from helper_html_tooltip.js */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test the HTMLTooltip positioning for a huge tooltip element (can not fit in
|
||||
* the viewport).
|
||||
*/
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const TEST_URI = `data:text/xml;charset=UTF-8,<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/global.css"?>
|
||||
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="Tooltip test">
|
||||
<vbox flex="1">
|
||||
<hbox id="box1" style="height: 50px">test1</hbox>
|
||||
<hbox id="box2" style="height: 50px">test2</hbox>
|
||||
<hbox id="box3" style="height: 50px">test3</hbox>
|
||||
<hbox id="box4" style="height: 50px">test4</hbox>
|
||||
</vbox>
|
||||
</window>`;
|
||||
|
||||
const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
|
||||
loadHelperScript("helper_html_tooltip.js");
|
||||
|
||||
const TOOLTIP_HEIGHT = 200;
|
||||
const TOOLTIP_WIDTH = 200;
|
||||
|
||||
add_task(function* () {
|
||||
// Force the toolbox to be 200px high;
|
||||
yield pushPref("devtools.toolbox.footer.height", 200);
|
||||
|
||||
yield addTab("about:blank");
|
||||
let [,, doc] = yield createHost("bottom", TEST_URI);
|
||||
|
||||
info("Create HTML tooltip");
|
||||
let tooltip = new HTMLTooltip({doc}, {});
|
||||
let div = doc.createElementNS(HTML_NS, "div");
|
||||
div.style.height = "100%";
|
||||
yield tooltip.setContent(div, TOOLTIP_WIDTH, TOOLTIP_HEIGHT);
|
||||
|
||||
let box1 = doc.getElementById("box1");
|
||||
let box2 = doc.getElementById("box2");
|
||||
let box3 = doc.getElementById("box3");
|
||||
let box4 = doc.getElementById("box4");
|
||||
let width = TOOLTIP_WIDTH;
|
||||
|
||||
// box1: Can not fit above or below box1, default to bottom with a reduced
|
||||
// height of 150px.
|
||||
info("Display the tooltip on box1.");
|
||||
yield showTooltip(tooltip, box1);
|
||||
let expectedTooltipGeometry = {position: "bottom", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box1.");
|
||||
yield showTooltip(tooltip, box1, "top");
|
||||
expectedTooltipGeometry = {position: "bottom", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box1, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box2: Can not fit above or below box2, default to bottom with a reduced
|
||||
// height of 100px.
|
||||
info("Try to display the tooltip on box2.");
|
||||
yield showTooltip(tooltip, box2);
|
||||
expectedTooltipGeometry = {position: "bottom", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on top of box2.");
|
||||
yield showTooltip(tooltip, box2, "top");
|
||||
expectedTooltipGeometry = {position: "bottom", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box2, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box3: Can not fit above or below box3, default to top with a reduced height
|
||||
// of 100px.
|
||||
info("Try to display the tooltip on box3.");
|
||||
yield showTooltip(tooltip, box3);
|
||||
expectedTooltipGeometry = {position: "top", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box3.");
|
||||
yield showTooltip(tooltip, box3, "bottom");
|
||||
expectedTooltipGeometry = {position: "top", height: 100, width};
|
||||
checkTooltipGeometry(tooltip, box3, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
// box4: Can not fit above or below box4, default to top with a reduced height
|
||||
// of 150px.
|
||||
info("Display the tooltip on box4.");
|
||||
yield showTooltip(tooltip, box4);
|
||||
expectedTooltipGeometry = {position: "top", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
info("Try to display the tooltip on bottom of box4.");
|
||||
yield showTooltip(tooltip, box4, "bottom");
|
||||
expectedTooltipGeometry = {position: "top", height: 150, width};
|
||||
checkTooltipGeometry(tooltip, box4, expectedTooltipGeometry);
|
||||
yield hideTooltip(tooltip);
|
||||
|
||||
is(tooltip.isVisible(), false, "Tooltip is not visible");
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Helper methods for the HTMLTooltip integration tests.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Display an existing HTMLTooltip on an anchor. Returns a promise that will
|
||||
* resolve when the tooltip "shown" event has been fired.
|
||||
*
|
||||
* @param {HTMLTooltip} tooltip
|
||||
* The tooltip instance to display
|
||||
* @param {Node} anchor
|
||||
* The anchor that should be used to display the tooltip
|
||||
* @param {String} position
|
||||
* The preferred display position ("top", "bottom")
|
||||
* @return {Promise} promise that resolves when the "shown" event is fired
|
||||
*/
|
||||
function showTooltip(tooltip, anchor, position) {
|
||||
let onShown = tooltip.once("shown");
|
||||
tooltip.show(anchor, {position});
|
||||
return onShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide an existing HTMLTooltip. Returns a promise that will resolve when the
|
||||
* tooltip "hidden" event has been fired.
|
||||
*
|
||||
* @param {HTMLTooltip} tooltip
|
||||
* The tooltip instance to hide
|
||||
* @return {Promise} promise that resolves when the "hidden" event is fired
|
||||
*/
|
||||
function hideTooltip(tooltip) {
|
||||
let onPopupHidden = tooltip.once("hidden");
|
||||
tooltip.hide();
|
||||
return onPopupHidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test helper designed to check that a tooltip is displayed at the expected
|
||||
* position relative to an anchor, given a set of expectations.
|
||||
*
|
||||
* @param {HTMLTooltip} tooltip
|
||||
* The HTMLTooltip instance to check
|
||||
* @param {Node} anchor
|
||||
* The tooltip's anchor
|
||||
* @param {Object} expected
|
||||
* - {String} position : "top" or "bottom"
|
||||
* - {Boolean} leftAligned
|
||||
* - {Number} width: expected tooltip width
|
||||
* - {Number} height: expected tooltip height
|
||||
*/
|
||||
function checkTooltipGeometry(tooltip, anchor,
|
||||
{position, leftAligned = true, height, width} = {}) {
|
||||
info("Check the tooltip geometry matches expected position and dimensions");
|
||||
let tooltipRect = tooltip.container.getBoundingClientRect();
|
||||
let anchorRect = anchor.getBoundingClientRect();
|
||||
|
||||
if (position === "top") {
|
||||
is(tooltipRect.bottom, anchorRect.top, "Tooltip is above the anchor");
|
||||
} else if (position === "bottom") {
|
||||
is(tooltipRect.top, anchorRect.bottom, "Tooltip is below the anchor");
|
||||
} else {
|
||||
ok(false, "Invalid position provided to checkTooltipGeometry");
|
||||
}
|
||||
|
||||
if (leftAligned) {
|
||||
is(tooltipRect.left, anchorRect.left,
|
||||
"Tooltip left-aligned with the anchor");
|
||||
}
|
||||
|
||||
is(tooltipRect.height, height, "Tooltip has the expected height");
|
||||
is(tooltipRect.width, width, "Tooltip has the expected width");
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const XHTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const IFRAME_URL = "chrome://devtools/content/shared/widgets/tooltip-frame.xhtml";
|
||||
const IFRAME_CONTAINER_ID = "tooltip-iframe-container";
|
||||
|
||||
/**
|
||||
* The HTMLTooltip can display HTML content in a tooltip popup.
|
||||
*
|
||||
* @param {Toolbox} toolbox
|
||||
* The devtools toolbox, needed to get the devtools main window.
|
||||
* @param {Object}
|
||||
* - {String} type
|
||||
* Display type of the tooltip. Possible values: "normal"
|
||||
* - {Boolean} autofocus
|
||||
* Defaults to true. Should the tooltip be focused when opening it.
|
||||
* - {Boolean} consumeOutsideClicks
|
||||
* Defaults to true. The tooltip is closed when clicking outside.
|
||||
* Should this event be stopped and consumed or not.
|
||||
*/
|
||||
function HTMLTooltip(toolbox,
|
||||
{type = "normal", autofocus = true, consumeOutsideClicks = true} = {}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.document = toolbox.doc;
|
||||
this.type = type;
|
||||
this.autofocus = autofocus;
|
||||
this.consumeOutsideClicks = consumeOutsideClicks;
|
||||
|
||||
// Use the topmost window to listen for click events to close the tooltip
|
||||
this.topWindow = this.document.defaultView.top;
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
|
||||
this.container = this._createContainer();
|
||||
|
||||
// Promise that will resolve when the container can be filled with content.
|
||||
this.containerReady = new Promise(resolve => {
|
||||
if (this._isXUL()) {
|
||||
// In XUL context, load a placeholder document in the iframe container.
|
||||
let onLoad = () => {
|
||||
this.container.removeEventListener("load", onLoad, true);
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.container.addEventListener("load", onLoad, true);
|
||||
this.container.setAttribute("src", IFRAME_URL);
|
||||
} else {
|
||||
// In non-XUL context the container is ready to use as is.
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.HTMLTooltip = HTMLTooltip;
|
||||
|
||||
HTMLTooltip.prototype = {
|
||||
position: {
|
||||
TOP: "top",
|
||||
BOTTOM: "bottom",
|
||||
},
|
||||
|
||||
get parent() {
|
||||
if (this._isXUL()) {
|
||||
// In XUL context, we are wrapping the HTML content in an iframe.
|
||||
let win = this.container.contentWindow.wrappedJSObject;
|
||||
return win.document.getElementById(IFRAME_CONTAINER_ID);
|
||||
}
|
||||
return this.container;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the tooltip content element. The preferred width/height should also be
|
||||
* specified here.
|
||||
*
|
||||
* @param {Element} content
|
||||
* The tooltip content, should be a HTML element.
|
||||
* @param {Number} width
|
||||
* Preferred width for the tooltip container
|
||||
* @param {Number} height
|
||||
* Preferred height for the tooltip container
|
||||
* @return {Promise} a promise that will resolve when the content has been
|
||||
* added in the tooltip container.
|
||||
*/
|
||||
setContent: function (content, width, height) {
|
||||
this.preferredWidth = width;
|
||||
this.preferredHeight = height;
|
||||
|
||||
return this.containerReady.then(() => {
|
||||
this.parent.innerHTML = "";
|
||||
this.parent.appendChild(content);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the tooltip next to the provided anchor element. A preferred position
|
||||
* can be set. The event "shown" will be fired after the tooltip is displayed.
|
||||
*
|
||||
* @param {Element} anchor
|
||||
* The reference element with which the tooltip should be aligned
|
||||
* @param {Object}
|
||||
* - {String} position: optional, possible values: top|bottom
|
||||
* If layout permits, the tooltip will be displayed on top/bottom
|
||||
* of the anchor. If ommitted, the tooltip will be displayed where
|
||||
* more space is available.
|
||||
*/
|
||||
show: function (anchor, {position} = {}) {
|
||||
this.containerReady.then(() => {
|
||||
let {top, left, width, height} = this._findBestPosition(anchor, position);
|
||||
|
||||
if (this._isXUL()) {
|
||||
this.container.setAttribute("width", width);
|
||||
this.container.setAttribute("height", height);
|
||||
} else {
|
||||
this.container.style.width = width + "px";
|
||||
this.container.style.height = height + "px";
|
||||
}
|
||||
|
||||
this.container.style.top = top + "px";
|
||||
this.container.style.left = left + "px";
|
||||
this.container.style.display = "block";
|
||||
|
||||
if (this.autofocus) {
|
||||
this.container.focus();
|
||||
}
|
||||
|
||||
this.attachEventsTimer = this.document.defaultView.setTimeout(() => {
|
||||
this.topWindow.addEventListener("click", this._onClick, true);
|
||||
this.emit("shown");
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the current tooltip. The event "hidden" will be fired when the tooltip
|
||||
* is hidden.
|
||||
*/
|
||||
hide: function () {
|
||||
this.document.defaultView.clearTimeout(this.attachEventsTimer);
|
||||
|
||||
if (this.isVisible()) {
|
||||
this.topWindow.removeEventListener("click", this._onClick, true);
|
||||
this.container.style.display = "none";
|
||||
this.emit("hidden");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the tooltip is currently displayed.
|
||||
* @return {Boolean} true if the tooltip is visible
|
||||
*/
|
||||
isVisible: function () {
|
||||
let win = this.document.defaultView;
|
||||
return win.getComputedStyle(this.container).display != "none";
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the tooltip instance. Hide the tooltip if displayed, remove the
|
||||
* tooltip container from the document.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.hide();
|
||||
this.container.remove();
|
||||
},
|
||||
|
||||
_createContainer: function () {
|
||||
let container;
|
||||
if (this._isXUL()) {
|
||||
container = this.document.createElementNS(XHTML_NS, "iframe");
|
||||
container.classList.add("devtools-tooltip-iframe");
|
||||
this.document.querySelector("window").appendChild(container);
|
||||
} else {
|
||||
container = this.document.createElementNS(XHTML_NS, "div");
|
||||
this.document.body.appendChild(container);
|
||||
}
|
||||
|
||||
container.classList.add("theme-body");
|
||||
container.classList.add("devtools-htmltooltip-container");
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
_onClick: function (e) {
|
||||
if (this._isInTooltipContainer(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hide();
|
||||
if (this.consumeOutsideClicks) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
|
||||
_isInTooltipContainer: function (node) {
|
||||
let contentWindow = this.parent.ownerDocument.defaultView;
|
||||
let win = node.ownerDocument.defaultView;
|
||||
|
||||
if (win === contentWindow) {
|
||||
// If node is in the same window as the tooltip, check if the tooltip
|
||||
// parent contains node.
|
||||
return this.parent.contains(node);
|
||||
}
|
||||
|
||||
// Otherwise check if the node window is in the tooltip window.
|
||||
while (win.parent && win.parent != win) {
|
||||
win = win.parent;
|
||||
if (win === contentWindow) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_findBestPosition: function (anchor, position) {
|
||||
let top, left;
|
||||
let {TOP, BOTTOM} = this.position;
|
||||
|
||||
let {left: anchorLeft, top: anchorTop, height: anchorHeight}
|
||||
= this._getRelativeRect(anchor, this.document);
|
||||
|
||||
let {bottom: docBottom, right: docRight} =
|
||||
this.document.documentElement.getBoundingClientRect();
|
||||
|
||||
let height = this.preferredHeight;
|
||||
// Check if the popup can fit above the anchor.
|
||||
let availableTop = anchorTop;
|
||||
let fitsAbove = availableTop >= height;
|
||||
// Check if the popup can fit below the anchor.
|
||||
let availableBelow = docBottom - (anchorTop + anchorHeight);
|
||||
let fitsBelow = availableBelow >= height;
|
||||
|
||||
let isPositionSuitable = (fitsAbove && position === TOP)
|
||||
|| (fitsBelow && position === BOTTOM);
|
||||
if (!isPositionSuitable) {
|
||||
// If the preferred position does not fit the preferred height,
|
||||
// pick the position offering the most height.
|
||||
position = availableTop > availableBelow ? TOP : BOTTOM;
|
||||
}
|
||||
|
||||
// Calculate height, capped by the maximum height available.
|
||||
height = Math.min(height, Math.max(availableTop, availableBelow));
|
||||
top = position === TOP ? anchorTop - height : anchorTop + anchorHeight;
|
||||
|
||||
let availableWidth = docRight;
|
||||
let width = Math.min(this.preferredWidth, availableWidth);
|
||||
|
||||
// By default, align the tooltip's left edge with the anchor left edge.
|
||||
if (anchorLeft + width <= docRight) {
|
||||
left = anchorLeft;
|
||||
} else {
|
||||
// If the tooltip cannot fit, shift to the left just enough to fit.
|
||||
left = docRight - width;
|
||||
}
|
||||
|
||||
return {top, left, width, height};
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the bounding client rectangle for a given node, relative to a custom
|
||||
* reference element (instead of the default for getBoundingClientRect which
|
||||
* is always the element's ownerDocument).
|
||||
*/
|
||||
_getRelativeRect: function (node, relativeTo) {
|
||||
// Width and Height can be taken from the rect.
|
||||
let {width, height} = node.getBoundingClientRect();
|
||||
|
||||
// Find the smallest top/left coordinates from all quads.
|
||||
let top = Infinity, left = Infinity;
|
||||
let quads = node.getBoxQuads({relativeTo: relativeTo});
|
||||
for (let quad of quads) {
|
||||
top = Math.min(top, quad.bounds.top);
|
||||
left = Math.min(left, quad.bounds.left);
|
||||
}
|
||||
|
||||
// Compute right and bottom coordinates using the rest of the data.
|
||||
let right = left + width;
|
||||
let bottom = top + height;
|
||||
|
||||
return {top, right, bottom, left, width, height};
|
||||
},
|
||||
|
||||
_isXUL: function () {
|
||||
return this.document.documentElement.namespaceURI === XUL_NS;
|
||||
},
|
||||
};
|
|
@ -20,6 +20,7 @@ DevToolsModules(
|
|||
'FlameGraph.js',
|
||||
'Graphs.js',
|
||||
'GraphsWorker.js',
|
||||
'HTMLTooltip.js',
|
||||
'LineGraphWidget.js',
|
||||
'MdnDocsWidget.js',
|
||||
'MountainGraphWidget.js',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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/. -->
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<style>
|
||||
html, body, #tooltip-iframe-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body role="application" class="theme-body">
|
||||
<div id="tooltip-iframe-container"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -246,6 +246,12 @@
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
.devtools-htmltooltip-container {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* links to source code, like displaying `myfile.js:45` */
|
||||
|
||||
.devtools-source-link {
|
||||
|
|
Загрузка…
Ссылка в новой задаче