--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -8,16 +8,18 @@ support-files =
doc_filter-editor-01.html
doc_html_tooltip-02.xul
doc_html_tooltip-03.xul
doc_html_tooltip-04.xul
doc_html_tooltip-05.xul
doc_html_tooltip.xul
doc_html_tooltip_arrow-01.xul
doc_html_tooltip_arrow-02.xul
+ doc_html_tooltip_doorhanger-01.xul
+ doc_html_tooltip_doorhanger-02.xul
doc_html_tooltip_hover.xul
doc_html_tooltip_rtl.xul
doc_inplace-editor_autocomplete_offset.xul
doc_layoutHelpers-getBoxQuads.html
doc_layoutHelpers.html
doc_options-view.xul
doc_spectrum.html
doc_tableWidget_basic.html
@@ -133,16 +135,18 @@ skip-if = e10s # Bug 1221911, bug 122228
[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_html_tooltip_arrow-01.js]
[browser_html_tooltip_arrow-02.js]
[browser_html_tooltip_consecutive-show.js]
+[browser_html_tooltip_doorhanger-01.js]
+[browser_html_tooltip_doorhanger-02.js]
[browser_html_tooltip_height-auto.js]
[browser_html_tooltip_hover.js]
[browser_html_tooltip_offset.js]
[browser_html_tooltip_rtl.js]
[browser_html_tooltip_variable-height.js]
[browser_html_tooltip_width-auto.js]
[browser_html_tooltip_xul-wrapper.js]
[browser_html_tooltip_zoom.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_doorhanger-01.js
@@ -0,0 +1,91 @@
+/* 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 "doorhanger" type's hang direction. It should hang
+ * towards the middle of the screen.
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip_doorhanger-01.xul";
+
+const {HTMLTooltip} =
+ require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+let useXulWrapper;
+
+add_task(async function() {
+ // Force the toolbox to be 200px high;
+ await pushPref("devtools.toolbox.footer.height", 200);
+
+ await addTab("about:blank");
+ const [,, doc] = await createHost("bottom", TEST_URI);
+
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ await runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ await runTests(doc);
+});
+
+async function runTests(doc) {
+ info("Create HTML tooltip");
+ const tooltip = new HTMLTooltip(doc, {type: "doorhanger", useXulWrapper});
+ const div = doc.createElementNS(HTML_NS, "div");
+ div.style.width = "200px";
+ div.style.height = "35px";
+ tooltip.setContent(div);
+
+ const docBounds = doc.documentElement.getBoundingClientRect();
+
+ const elements = [...doc.querySelectorAll(".anchor")];
+ for (const el of elements) {
+ info("Display the tooltip on an anchor.");
+ await showTooltip(tooltip, el);
+
+ const arrow = tooltip.arrow;
+ ok(arrow, "Tooltip has an arrow");
+
+ // Get the geometry of the anchor, the tooltip panel & arrow.
+ const anchorBounds = el.getBoxQuads({ relativeTo: doc })[0].getBounds();
+ const panelBounds =
+ tooltip.panel.getBoxQuads({ relativeTo: doc })[0].getBounds();
+ const arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
+
+ // Work out which side of the view the anchor is on.
+ const center = bounds => bounds.left + bounds.width / 2;
+ const anchorSide =
+ center(anchorBounds) < center(docBounds)
+ ? "left"
+ : "right";
+
+ // Work out which direction the doorhanger hangs.
+ //
+ // We can do that just by checking which edge of the panel the center of the
+ // arrow is closer to.
+ const panelDirection =
+ center(arrowBounds) - panelBounds.left <
+ panelBounds.right - center(arrowBounds)
+ ? "right"
+ : "left";
+
+ const params =
+ `document: ${docBounds.left}<->${docBounds.right}, ` +
+ `anchor: ${anchorBounds.left}<->${anchorBounds.right}, ` +
+ `panel: ${panelBounds.left}<->${panelBounds.right}, ` +
+ `anchor side: ${anchorSide}, ` +
+ `panel direction: ${panelDirection}`;
+ ok(anchorSide !== panelDirection,
+ `Doorhanger hangs towards center (${params})`);
+
+ await hideTooltip(tooltip);
+ }
+
+ tooltip.destroy();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_html_tooltip_doorhanger-02.js
@@ -0,0 +1,72 @@
+/* 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 "doorhanger" type's arrow tip is precisely centered on
+ * the anchor when the anchor is small.
+ */
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const TEST_URI = CHROME_URL_ROOT + "doc_html_tooltip_doorhanger-02.xul";
+
+const {HTMLTooltip} =
+ require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+loadHelperScript("helper_html_tooltip.js");
+
+let useXulWrapper;
+
+add_task(async function() {
+ // Force the toolbox to be 200px high;
+ await pushPref("devtools.toolbox.footer.height", 200);
+
+ await addTab("about:blank");
+ const [,, doc] = await createHost("bottom", TEST_URI);
+
+ info("Run tests for a Tooltip without using a XUL panel");
+ useXulWrapper = false;
+ await runTests(doc);
+
+ info("Run tests for a Tooltip with a XUL panel");
+ useXulWrapper = true;
+ await runTests(doc);
+});
+
+async function runTests(doc) {
+ info("Create HTML tooltip");
+ const tooltip = new HTMLTooltip(doc, {type: "doorhanger", useXulWrapper});
+ const div = doc.createElementNS(HTML_NS, "div");
+ div.style.width = "200px";
+ div.style.height = "35px";
+ tooltip.setContent(div);
+
+ const elements = [...doc.querySelectorAll(".anchor")];
+ for (const el of elements) {
+ info("Display the tooltip on an anchor.");
+ await showTooltip(tooltip, el);
+
+ const arrow = tooltip.arrow;
+ ok(arrow, "Tooltip has an arrow");
+
+ // Get the geometry of the anchor and arrow.
+ const anchorBounds = el.getBoxQuads({ relativeTo: doc })[0].getBounds();
+ const arrowBounds = arrow.getBoxQuads({ relativeTo: doc })[0].getBounds();
+
+ // Compare the centers
+ const center = bounds => bounds.left + bounds.width / 2;
+ const delta = Math.abs(center(anchorBounds) - center(arrowBounds));
+ const describeBounds = bounds =>
+ `${bounds.left}<--[${center(bounds)}]-->${bounds.right}`;
+ const params =
+ `anchor: ${describeBounds(anchorBounds)}, ` +
+ `arrow: ${describeBounds(arrowBounds)}`;
+ ok(delta < 1,
+ `Arrow center is roughly aligned with anchor center (${params})`);
+
+ await hideTooltip(tooltip);
+ }
+
+ tooltip.destroy();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/doc_html_tooltip_doorhanger-01.xul
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
+<window class="theme-light"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tooltip test">
+
+<vbox flex="1" style="position: relative">
+ <!-- Left edge -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ top: 0; left: 0;">
+ </html:div>
+
+ <!-- Not left edge but still left of center plus RTL direction (which should
+ no affect the hang direction) -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ top: 0; left: 25px; direction: rtl">
+ </html:div>
+
+ <!-- Wide but still left of center -->
+ <html:div class="anchor"
+ style="width:80%; height: 10px; position: absolute; background: red;
+ top: 0; left: 50px;">
+ </html:div>
+
+ <!-- Right edge -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ bottom: 0; right: 0;">
+ </html:div>
+
+ <!-- Not right edge but still right of center plus RTL direction (which should
+ no affect the hang direction) -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ bottom: 0; right: 25px; direction: rtl">
+ </html:div>
+
+ <!-- Wide but still right of center -->
+ <html:div class="anchor"
+ style="width:80%; height: 10px; position: absolute; background: red;
+ bottom: 0; right: 50px;">
+ </html:div>
+ </vbox>
+</window>
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/doc_html_tooltip_doorhanger-02.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://devtools/skin/light-theme.css"?>
+<window class="theme-light"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="Tooltip test">
+
+<vbox flex="1" style="position: relative">
+ <!-- Towards the left -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ top: 0; left: 25px;">
+ </html:div>
+
+ <!-- Towards the left with RTL direction -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ top: 0; left: 50px; direction: rtl;">
+ </html:div>
+
+ <!-- Towards the right -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ bottom: 0; right: 25px;">
+ </html:div>
+
+ <!-- Towards the right with RTL direction -->
+ <html:div class="anchor"
+ style="width:10px; height: 10px; position: absolute; background: red;
+ bottom: 0; right: 50px; direction: rtl;">
+ </html:div>
+ </vbox>
+</window>
--- a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
@@ -19,34 +19,51 @@ const POSITION = {
BOTTOM: "bottom",
};
module.exports.POSITION = POSITION;
const TYPE = {
NORMAL: "normal",
ARROW: "arrow",
+ DOORHANGER: "doorhanger",
};
module.exports.TYPE = TYPE;
-const ARROW_WIDTH = 32;
+const ARROW_WIDTH = {
+ "normal": 0,
+ "arrow": 32,
+ // This is the value calculated for the .tooltip-arrow element in tooltip.css
+ // which includes the arrow width (20px) plus the extra margin added so that
+ // the drop shadow is not cropped (2px each side).
+ "doorhanger": 24,
+};
-// Default offset between the tooltip's left edge and the tooltip arrow.
-const ARROW_OFFSET = 20;
+const ARROW_OFFSET = {
+ "normal": 0,
+ // Default offset between the tooltip's edge and the tooltip arrow.
+ "arrow": 20,
+ // Match other Firefox menus which use 10px from edge (but subtract the 2px
+ // margin included in the ARROW_WIDTH above).
+ "doorhanger": 8,
+};
const EXTRA_HEIGHT = {
"normal": 0,
// The arrow is 16px tall, but merges on 3px with the panel border
"arrow": 13,
+ // The doorhanger arrow is 10px tall, but merges on 1px with the panel border
+ "doorhanger": 9,
};
const EXTRA_BORDER = {
"normal": 0,
"arrow": 3,
+ "doorhanger": 0,
};
/**
* Calculate the vertical position & offsets to use for the tooltip. Will attempt to
* respect the provided height and position preferences, unless the available height
* prevents this.
*
* @param {DOMRect} anchorRect
@@ -115,39 +132,72 @@ const calculateVerticalPosition = (
* available width prevents this.
*
* @param {DOMRect} anchorRect
* Bounding rectangle for the anchor, relative to the tooltip document.
* @param {DOMRect} viewportRect
* Bounding rectangle for the viewport. top/left can be different from
* 0 if some space should not be used by tooltips (for instance OS
* toolbars, taskbars etc.).
+ * @param {DOMRect} windowRect
+ * Bounding rectangle for the window. Used to determine which direction
+ * doorhangers should hang.
* @param {Number} width
* Preferred width for the tooltip.
* @param {String} type
* The tooltip type (e.g. "arrow").
* @param {Number} offset
* Horizontal offset in pixels.
+ * @param {Number} borderRadius
+ * The border radius of the panel. This is added to ARROW_OFFSET to
+ * calculate the distance from the edge of the tooltip to the start
+ * of arrow. It is separate from ARROW_OFFSET since it will vary by
+ * platform.
* @param {Boolean} isRtl
* If the anchor is in RTL, the tooltip should be aligned to the right.
* @return {Object}
* - {Number} left: the left offset for the tooltip.
* - {Number} width: the width to use for the tooltip container.
* - {Number} arrowLeft: the left offset to use for the arrow element.
*/
const calculateHorizontalPosition = (
anchorRect,
viewportRect,
+ windowRect,
width,
type,
offset,
+ borderRadius,
isRtl
) => {
// Which direction should the tooltip go?
- const hangDirection = isRtl ? "left" : "right";
+ //
+ // For tooltips we follow the writing direction but for doorhangers the
+ // guidelines[1] say that,
+ //
+ // "Doorhangers opening on the right side of the view show the directional
+ // arrow on the right.
+ //
+ // Doorhangers opening on the left side of the view show the directional
+ // arrow on the left.
+ //
+ // Never place the directional arrow at the center of doorhangers."
+ //
+ // [1] https://design.firefox.com/photon/components/doorhangers.html#directional-arrow
+ //
+ // So for those we need to check if the anchor is more right or left.
+ let hangDirection;
+ if (type === TYPE.DOORHANGER) {
+ const anchorCenter = anchorRect.left + anchorRect.width / 2;
+ const viewCenter = windowRect.left + windowRect.width / 2;
+ hangDirection = anchorCenter >= viewCenter ? "left" : "right";
+ } else {
+ hangDirection = isRtl ? "left" : "right";
+ }
+
const anchorWidth = anchorRect.width;
// Calculate logical start of anchor relative to the viewport.
const anchorStart =
hangDirection === "right"
? anchorRect.left - viewportRect.left
: viewportRect.right - anchorRect.right;
@@ -155,37 +205,39 @@ const calculateHorizontalPosition = (
const tooltipWidth = Math.min(width, viewportRect.width);
// Calculate tooltip start.
let tooltipStart = anchorStart + offset;
tooltipStart = Math.min(tooltipStart, viewportRect.width - tooltipWidth);
tooltipStart = Math.max(0, tooltipStart);
// Calculate arrow start (tooltip's start might be updated)
- const arrowWidth = type === TYPE.ARROW ? ARROW_WIDTH : 0;
+ const arrowWidth = ARROW_WIDTH[type];
let arrowStart;
- // Arrow style tooltips may need to be shifted
- if (type === TYPE.ARROW) {
+ // Arrow and doorhanger style tooltips may need to be shifted
+ if (type === TYPE.ARROW || type === TYPE.DOORHANGER) {
+ const arrowOffset = ARROW_OFFSET[type] + borderRadius;
+
// Where will the point of the arrow be if we apply the standard offset?
- const arrowCenter = tooltipStart + ARROW_OFFSET + arrowWidth / 2;
+ const arrowCenter = tooltipStart + arrowOffset + arrowWidth / 2;
// How does that compare to the center of the anchor?
const anchorCenter = anchorStart + anchorWidth / 2;
// If the anchor is too narrow, align the arrow and the anchor center.
if (arrowCenter > anchorCenter) {
tooltipStart = Math.max(0, tooltipStart - (arrowCenter - anchorCenter));
}
// Arrow's start offset relative to the anchor.
- arrowStart = Math.min(ARROW_OFFSET, (anchorWidth - arrowWidth) / 2) | 0;
+ arrowStart = Math.min(arrowOffset, (anchorWidth - arrowWidth) / 2) | 0;
// Translate the coordinate to tooltip container
arrowStart += anchorStart - tooltipStart;
// Make sure the arrow remains in the tooltip container.
- arrowStart = Math.min(arrowStart, tooltipWidth - arrowWidth);
- arrowStart = Math.max(arrowStart, 0);
+ arrowStart = Math.min(arrowStart, tooltipWidth - arrowWidth - borderRadius);
+ arrowStart = Math.max(arrowStart, borderRadius);
}
// Convert from logical coordinates to physical
const left =
hangDirection === "right"
? viewportRect.left + tooltipStart
: viewportRect.right - tooltipStart - tooltipWidth;
const arrowLeft =
@@ -227,17 +279,18 @@ const getRelativeRect = function(node, r
/**
* The HTMLTooltip can display HTML content in a tooltip popup.
*
* @param {Document} toolboxDoc
* The toolbox document to attach the HTMLTooltip popup.
* @param {Object}
* - {String} type
- * Display type of the tooltip. Possible values: "normal", "arrow"
+ * Display type of the tooltip. Possible values: "normal", "arrow", and
+ * "doorhanger".
* - {Boolean} autofocus
* Defaults to false. 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.
* - {Boolean} useXulWrapper
* Defaults to false. If the tooltip is hosted in a XUL document, use a XUL panel
* in order to use all the screen viewport available.
@@ -370,18 +423,17 @@ HTMLTooltip.prototype = {
*/
async show(anchor, {position, x = 0, y = 0} = {}) {
// Get anchor geometry
let anchorRect = getRelativeRect(anchor, this.doc);
if (this.useXulWrapper) {
anchorRect = this._convertToScreenRect(anchorRect);
}
- // Get viewport size
- const viewportRect = this._getViewportRect();
+ const { viewportRect, windowRect } = this._getBoundingRects();
// Calculate the horizonal position and width
let preferredWidth;
// Record the height too since it might save us from having to look it up
// later.
let measuredHeight;
if (this.preferredWidth === "auto") {
// Reset any styles that constrain the dimensions we want to calculate.
@@ -394,43 +446,79 @@ HTMLTooltip.prototype = {
height: measuredHeight,
} = this._measureContainerSize());
} else {
const themeWidth = 2 * EXTRA_BORDER[this.type];
preferredWidth = this.preferredWidth + themeWidth;
}
const anchorWin = anchor.ownerDocument.defaultView;
- const isRtl = anchorWin.getComputedStyle(anchor).direction === "rtl";
+ const anchorCS = anchorWin.getComputedStyle(anchor);
+ const isRtl = anchorCS.direction === "rtl";
+
+ let borderRadius = 0;
+ if (this.type === TYPE.DOORHANGER) {
+ borderRadius = parseFloat(
+ anchorCS.getPropertyValue("--theme-arrowpanel-border-radius")
+ );
+ if (Number.isNaN(borderRadius)) {
+ borderRadius = 0;
+ }
+ }
+
const {left, width, arrowLeft} = calculateHorizontalPosition(
- anchorRect, viewportRect, preferredWidth, this.type, x, isRtl);
+ anchorRect,
+ viewportRect,
+ windowRect,
+ preferredWidth,
+ this.type,
+ x,
+ borderRadius,
+ isRtl
+ );
// If we constrained the width, then any measured height we have is no
// longer valid.
if (measuredHeight && width !== preferredWidth) {
measuredHeight = undefined;
}
// Apply width and arrow positioning
this.container.style.width = width + "px";
- if (this.type === TYPE.ARROW) {
+ if (this.type === TYPE.ARROW || this.type === TYPE.DOORHANGER) {
this.arrow.style.left = arrowLeft + "px";
}
+ // Work out how much vertical margin we have.
+ //
+ // This relies on us having set either .tooltip-top or .tooltip-bottom
+ // and on the margins for both being symmetrical. Fortunately the call to
+ // _measureContainerSize above will set .tooltip-top for us and it also
+ // assumes these styles are symmetrical so this should be ok.
+ const panelWindow = this.panel.ownerDocument.defaultView;
+ const panelComputedStyle = panelWindow.getComputedStyle(this.panel);
+ const verticalMargin =
+ parseFloat(panelComputedStyle.marginTop) +
+ parseFloat(panelComputedStyle.marginBottom);
+
// Calculate the vertical position and height
let preferredHeight;
if (this.preferredHeight === "auto") {
if (measuredHeight) {
this.container.style.height = "auto";
preferredHeight = measuredHeight;
} else {
({ height: preferredHeight } = this._measureContainerSize());
}
+ preferredHeight += verticalMargin;
} else {
- const themeHeight = EXTRA_HEIGHT[this.type] + 2 * EXTRA_BORDER[this.type];
+ const themeHeight =
+ EXTRA_HEIGHT[this.type] +
+ verticalMargin +
+ 2 * EXTRA_BORDER[this.type];
preferredHeight = this.preferredHeight + themeHeight;
}
const {top, height, computedPosition} =
calculateVerticalPosition(anchorRect, viewportRect, preferredHeight, position, y);
this._position = computedPosition;
const isTop = computedPosition === POSITION.TOP;
@@ -462,42 +550,85 @@ HTMLTooltip.prototype = {
// Update the top window reference each time in case the host changes.
this.topWindow = this._getTopWindow();
this.topWindow.addEventListener("click", this._onClick, true);
this.emit("shown");
}, 0);
},
/**
- * Calculate the rect of the viewport that limits the tooltip dimensions. When using a
- * XUL panel wrapper, the viewport will be able to use the whole screen (excluding space
- * reserved by the OS for toolbars etc.). Otherwise, the viewport is limited to the
- * tooltip's document.
+ * Calculate the following boundary rectangles:
+ *
+ * - Viewport rect: This is the region that limits the tooltip dimensions.
+ * When using a XUL panel wrapper, the tooltip will be able to use the whole
+ * screen (excluding space reserved by the OS for toolbars etc.) and hence
+ * the result will be in screen coordinates.
+ * Otherwise, the tooltip is limited to the tooltip's document.
*
- * @return {Object} DOMRect-like object with the Number properties: top, right, bottom,
- * left, width, height
+ * - Window rect: This is the bounds of the view in which the tooltip is
+ * presented. It is reported in the same coordinates as the viewport
+ * rect and is used for determining in which direction a doorhanger-type
+ * tooltip should "hang".
+ * When using the XUL panel wrapper this will be the dimensions of the
+ * window in screen coordinates. Otherwise it will be the same as the
+ * viewport rect.
+ *
+ * @return {Object} An object with the following properties
+ * viewportRect {Object} DOMRect-like object with the Number
+ * properties: top, right, bottom, left, width, height
+ * representing the viewport rect.
+ * windowRect {Object} DOMRect-like object with the Number
+ * properties: top, right, bottom, left, width, height
+ * representing the viewport rect.
*/
- _getViewportRect: function() {
+ _getBoundingRects: function() {
+ let viewportRect;
+ let windowRect;
+
if (this.useXulWrapper) {
- // availLeft/Top are the coordinates first pixel available on the screen for
- // applications (excluding space dedicated for OS toolbars, menus etc...)
- // availWidth/Height are the dimensions available to applications excluding all
- // the OS reserved space
- const {availLeft, availTop, availHeight, availWidth} = this.doc.defaultView.screen;
- return {
+ // availLeft/Top are the coordinates first pixel available on the screen
+ // for applications (excluding space dedicated for OS toolbars, menus
+ // etc...)
+ // availWidth/Height are the dimensions available to applications
+ // excluding all the OS reserved space
+ const {
+ availLeft,
+ availTop,
+ availHeight,
+ availWidth,
+ } = this.doc.defaultView.screen;
+ viewportRect = {
top: availTop,
right: availLeft + availWidth,
bottom: availTop + availHeight,
left: availLeft,
width: availWidth,
height: availHeight,
};
+
+ const {
+ screenX,
+ screenY,
+ outerWidth,
+ outerHeight,
+ } = this.doc.defaultView;
+ windowRect = {
+ top: screenY,
+ right: screenX + outerWidth,
+ bottom: screenY + outerHeight,
+ left: screenX,
+ width: outerWidth,
+ height: outerHeight,
+ };
+ } else {
+ viewportRect = windowRect =
+ this.doc.documentElement.getBoundingClientRect();
}
- return this.doc.documentElement.getBoundingClientRect();
+ return { viewportRect, windowRect };
},
_measureContainerSize: function() {
const xulParent = this.container.parentNode;
if (this.useXulWrapper && !this.isVisible()) {
// Move the container out of the XUL Panel to measure it.
this.doc.documentElement.appendChild(this.container);
}
@@ -568,17 +699,17 @@ HTMLTooltip.prototype = {
_createContainer: function() {
const container = this.doc.createElementNS(XHTML_NS, "div");
container.setAttribute("type", this.type);
container.classList.add("tooltip-container");
let html = '<div class="tooltip-filler"></div>';
html += '<div class="tooltip-panel"></div>';
- if (this.type === TYPE.ARROW) {
+ if (this.type === TYPE.ARROW || this.type === TYPE.DOORHANGER) {
html += '<div class="tooltip-arrow"></div>';
}
// eslint-disable-next-line no-unsanitized/property
container.innerHTML = html;
return container;
},
_onClick: function(e) {
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -257,16 +257,222 @@
transform: rotate(225deg);
}
.tooltip-top .tooltip-arrow:before {
margin-top: -12px;
transform: rotate(45deg);
}
+/* Tooltip : doorhanger style */
+
+:root {
+ --theme-arrowpanel-border-radius: 0px;
+}
+:root[platform="mac"] {
+ --theme-arrowpanel-border-radius: 3.5px;
+}
+
+.tooltip-container[type="doorhanger"] > .tooltip-panel {
+ padding: 4px 0;
+ color: var(--theme-arrowpanel-color);
+ margin: 4px;
+ max-width: 320px;
+}
+
+.tooltip-container[type="doorhanger"] > .tooltip-panel,
+.tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
+ background: var(--theme-arrowpanel-background);
+ border: 1px solid var(--theme-arrowpanel-border-color);
+ border-radius: var(--theme-arrowpanel-border-radius);
+ box-shadow: 0 0 4px hsla(210,4%,10%,.2);
+}
+
+:root[platform="mac"] .tooltip-container[type="doorhanger"] > .tooltip-panel,
+:root[platform="mac"] .tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
+ box-shadow: none;
+ /*
+ * The above should be:
+ *
+ * box-shadow: 0 0 0 1px var(--theme-arrowpanel-border-color);
+ *
+ * but although that gives the right emphasis to the border it makes the
+ * platform shadow much too dark.
+ */
+}
+
+:root[platform="mac"].theme-light .tooltip-container[type="doorhanger"] > .tooltip-panel,
+:root[platform="mac"].theme-light .tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
+ border: none;
+}
+
+.tooltip-container[type="doorhanger"] > .tooltip-arrow {
+ /* Desired width of the arrow */
+ --arrow-width: 20px;
+
+ /* Amount of room to allow for the shadow. Should be about half the radius. */
+ --shadow-radius: 4px;
+ --shadow-margin: calc(var(--shadow-radius) / 2);
+
+ /*
+ * Crop the arrow region to show half the arrow plus allow room for margins.
+ *
+ * The ARROW_WIDTH in HTMLTooltip.js needs to match the following value.
+ */
+ width: calc(var(--arrow-width) + 2 * var(--shadow-margin));
+ height: calc(var(--arrow-width) / 2 + var(--shadow-margin));
+}
+
+.tooltip-container[type="doorhanger"] > .tooltip-arrow::before {
+ /* Make sure the border is included in the size */
+ box-sizing: border-box;
+
+ /* Don't inherit any rounded corners. */
+ border-radius: 0;
+
+ /*
+ * When the box is rotated, it should have width <arrow-width>.
+ * That makes the length of one side of the box equal to:
+ *
+ * (<arrow-width> / 2) / sin 45
+ */
+ --sin-45: 0.707106781;
+ --square-side: calc(var(--arrow-width) / 2 / var(--sin-45));
+ width: var(--square-side);
+ height: var(--square-side);
+
+ /*
+ * The rotated square will overshoot the left side
+ * and need to be shifted in by:
+ *
+ * (<arrow-width> - square side) / 2
+ *
+ * But we also want to shift it in so that the box-shadow
+ * is not clipped when we clip the parent so we add
+ * a suitable margin for that.
+ */
+ --overhang: calc((var(--arrow-width) - var(--square-side)) / 2);
+ margin-left: calc(var(--overhang) + var(--shadow-margin));
+}
+
+.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-panel {
+ /* Drop the margin between the doorhanger and the arrow. */
+ margin-bottom: 0;
+}
+
+.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-panel {
+ /* Drop the margin between the doorhanger and the arrow. */
+ margin-top: 0;
+}
+
+.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-arrow {
+ /* Overlap the arrow with the 1px border of the doorhanger */
+ margin-top: -1px;
+}
+
+.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-arrow {
+ /* Overlap the arrow with the 1px border of the doorhanger */
+ margin-bottom: -1px;
+}
+
+.tooltip-container[type="doorhanger"].tooltip-top > .tooltip-arrow::before {
+ /* Show only the bottom half of the box */
+ margin-top: calc(var(--square-side) / -2);
+}
+
+.tooltip-container[type="doorhanger"].tooltip-bottom > .tooltip-arrow::before {
+ /* Shift the rotated box in so that it is not clipped */
+ margin-top: calc(var(--overhang) + var(--shadow-margin));
+}
+
+.tooltip-container[type="doorhanger"] .tooltip-panel ul {
+ /* Override the display: -moz-box declaration in minimal-xul.css
+ * or else menu items won't stack. */
+ display: block;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command {
+ display: flex;
+ align-items: baseline;
+ margin: 0;
+ padding: 4px 12px;
+ outline: none;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > button.command:-moz-any([role="menuitem"],[role="menuitemcheckbox"]) {
+ -moz-appearance: none;
+ border: none;
+ color: var(--theme-arrowpanel-color);
+ background-color: transparent;
+ text-align: start;
+ width: 100%;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command:not(:-moz-any([disabled],[open],:active)):-moz-any(:hover,:focus) {
+ background-color: var(--theme-arrowpanel-dimmed);
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command:-moz-focusring::-moz-focus-inner {
+ border-color: transparent;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command:not([disabled]):-moz-any([open],:hover:active) {
+ background-color: var(--theme-arrowpanel-dimmed-further);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.03) inset;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command[aria-checked="true"] {
+ list-style-image: none;
+ -moz-context-properties: fill;
+ fill: currentColor;
+ background: url(chrome://browser/skin/check.svg) no-repeat transparent;
+ background-size: 11px 11px;
+ background-position: center left 7px;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command[aria-checked="true"]:-moz-locale-dir(rtl) {
+ background-position: center right 7px;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command > .label {
+ flex: 1;
+ padding-inline-start: 16px;
+ font: menu;
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command.iconic > .label::before {
+ content: " ";
+ display: inline-block;
+ margin-inline-end: 8px;
+ width: 16px;
+ height: 16px;
+ vertical-align: top;
+ -moz-context-properties: fill;
+ fill: currentColor;
+ /*
+ * The icons in the sidebar menu have opacity: 0.8 here, but those in the
+ * hamburger menu don't. For now we match the hamburger menu styling,
+ * especially because the 80% opacity makes the icons look dull in dark mode.
+ */
+}
+
+.tooltip-container[type="doorhanger"] .menuitem > .command > .accelerator {
+ margin-inline-start: 10px;
+ color: var(--theme-arrowpanel-disabled-color);
+ font: message-box;
+}
+
+.tooltip-container[type="doorhanger"] hr {
+ display: block;
+ border: none;
+ border-top: 1px solid var(--theme-arrowpanel-separator);
+ margin: 6px 0;
+ padding: 0;
+}
+
/* Tooltip: Events */
.event-header {
display: flex;
align-items: center;
cursor: pointer;
overflow: hidden;
}
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -88,24 +88,45 @@
/* Icon filters */
--theme-icon-checked-filter: url(chrome://devtools/skin/images/filters.svg#icon-checked-light);
/* Tooltips */
--theme-tooltip-border: #d9e1e8;
--theme-tooltip-background: rgba(255, 255, 255, .9);
--theme-tooltip-shadow: rgba(155, 155, 155, 0.26);
+ /* Doorhangers */
+ /* These colors are based on the colors used for doorhangers elsewhere in
+ * Firefox. */
+ --theme-arrowpanel-background: white;
+ --theme-arrowpanel-color: -moz-fieldText;
+ --theme-arrowpanel-border-color: var(--grey-90-a20);
+ --theme-arrowpanel-separator: var(--grey-90-a20);
+ --theme-arrowpanel-dimmed: hsla(0,0%,80%,.3);
+ --theme-arrowpanel-dimmed-further: hsla(0,0%,80%,.45);
+ --theme-arrowpanel-disabled-color: GrayText;
+
/* Command line */
--theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme);
--theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#light-theme-focus);
--theme-codemirror-gutter-background: #f4f4f4;
--theme-messageCloseButtonFilter: invert(0);
}
+/*
+ * For doorhangers elsewhere in Fireflox, Mac uses a fixed color different to
+ * -moz-fieldText and a slightly lighter border color (presumably since it
+ * combines with the platform shadow).
+ */
+:root[platform="mac"].theme-light {
+ --theme-arrowpanel-color: rgb(26,26,26);
+ --theme-arrowpanel-border-color: hsla(210,4%,10%,.05);
+}
+
:root.theme-dark {
--theme-body-background: var(--grey-80);
--theme-sidebar-background: #1B1B1D;
--theme-contrast-background: #ffb35b;
/* Toolbar */
--theme-tab-toolbar-background: var(--grey-90);
--theme-toolbar-background: #1B1B1D;
@@ -175,16 +196,27 @@
/* Icon filters */
--theme-icon-checked-filter: url(chrome://devtools/skin/images/filters.svg#icon-checked-dark);
/* Tooltips */
--theme-tooltip-border: #434850;
--theme-tooltip-background: rgba(19, 28, 38, .9);
--theme-tooltip-shadow: rgba(25, 25, 25, 0.76);
+ /* Doorhangers */
+ /* These colors are based on the colors used for doorhangers elsewhere in
+ * Firefox. */
+ --theme-arrowpanel-background: var(--grey-60);
+ --theme-arrowpanel-color: rgb(249,249,250);
+ --theme-arrowpanel-border-color: #27272b;
+ --theme-arrowpanel-separator: rgba(249,249,250,.1);
+ --theme-arrowpanel-dimmed: rgba(249,249,250,.1);
+ --theme-arrowpanel-dimmed-further: rgba(249,249,250,.15);
+ --theme-arrowpanel-disabled-color: rgba(249,249,250,.5);
+
/* Command line */
--theme-command-line-image: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme);
--theme-command-line-image-focus: url(chrome://devtools/skin/images/commandline-icon.svg#dark-theme-focus);
--theme-codemirror-gutter-background: #262b37;
--theme-messageCloseButtonFilter: invert(1);
}
@@ -247,10 +279,11 @@
--grey-40: #b1b1b3;
--grey-50: #737373;
--grey-60: #4a4a4f;
--grey-60-a50: rgba(74, 74, 79, 0.5);
--grey-70: #38383d;
--grey-80: #2a2a2e;
--grey-90: #0c0c0d;
--grey-90-a10: rgba(12, 12, 13, 0.1);
+ --grey-90-a20: rgba(12, 12, 13, 0.2);
--grey-90-a80: rgba(12, 12, 13, 0.8);
}