diff --git a/devtools/client/shared/test/browser.ini b/devtools/client/shared/test/browser.ini index 66cee4e6ba96..a9900190db4e 100644 --- a/devtools/client/shared/test/browser.ini +++ b/devtools/client/shared/test/browser.ini @@ -13,6 +13,8 @@ support-files = 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 @@ -138,6 +140,8 @@ skip-if = e10s # Bug 1221911, bug 1222289, frequent e10s timeouts [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] diff --git a/devtools/client/shared/test/browser_html_tooltip_doorhanger-01.js b/devtools/client/shared/test/browser_html_tooltip_doorhanger-01.js new file mode 100644 index 000000000000..23afab2b7f10 --- /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(); +} diff --git a/devtools/client/shared/test/browser_html_tooltip_doorhanger-02.js b/devtools/client/shared/test/browser_html_tooltip_doorhanger-02.js new file mode 100644 index 000000000000..c3addb6d5b1d --- /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(); +} diff --git a/devtools/client/shared/test/doc_html_tooltip_doorhanger-01.xul b/devtools/client/shared/test/doc_html_tooltip_doorhanger-01.xul new file mode 100644 index 000000000000..2759a0b5a761 --- /dev/null +++ b/devtools/client/shared/test/doc_html_tooltip_doorhanger-01.xul @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devtools/client/shared/test/doc_html_tooltip_doorhanger-02.xul b/devtools/client/shared/test/doc_html_tooltip_doorhanger-02.xul new file mode 100644 index 000000000000..d76fe6775c7d --- /dev/null +++ b/devtools/client/shared/test/doc_html_tooltip_doorhanger-02.xul @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js index d08a215218f4..ad80d42469d3 100644 --- a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js +++ b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js @@ -24,24 +24,41 @@ 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, }; /** @@ -120,12 +137,20 @@ const calculateVerticalPosition = ( * 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} @@ -136,13 +161,38 @@ const calculateVerticalPosition = ( 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. @@ -160,12 +210,14 @@ const calculateHorizontalPosition = ( 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; @@ -175,12 +227,12 @@ const calculateHorizontalPosition = ( 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 @@ -232,7 +284,8 @@ const getRelativeRect = function(node, relativeTo) { * 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 @@ -375,8 +428,7 @@ HTMLTooltip.prototype = { anchorRect = this._convertToScreenRect(anchorRect); } - // Get viewport size - const viewportRect = this._getViewportRect(); + const { viewportRect, windowRect } = this._getBoundingRects(); // Calculate the horizonal position and width let preferredWidth; @@ -399,9 +451,29 @@ HTMLTooltip.prototype = { } 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. @@ -411,10 +483,22 @@ HTMLTooltip.prototype = { // 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") { @@ -424,8 +508,12 @@ HTMLTooltip.prototype = { } 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; } @@ -467,22 +555,47 @@ HTMLTooltip.prototype = { }, /** - * 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: * - * @return {Object} DOMRect-like object with the Number properties: top, right, bottom, - * left, width, height + * - 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. + * + * - 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, @@ -490,9 +603,27 @@ HTMLTooltip.prototype = { 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() { @@ -573,7 +704,7 @@ HTMLTooltip.prototype = { let html = '
'; html += '
'; - if (this.type === TYPE.ARROW) { + if (this.type === TYPE.ARROW || this.type === TYPE.DOORHANGER) { html += '
'; } // eslint-disable-next-line no-unsanitized/property diff --git a/devtools/client/themes/tooltips.css b/devtools/client/themes/tooltips.css index e9abb27d4a06..7c5596512a84 100644 --- a/devtools/client/themes/tooltips.css +++ b/devtools/client/themes/tooltips.css @@ -262,6 +262,212 @@ 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 . + * That makes the length of one side of the box equal to: + * + * ( / 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: + * + * ( - 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 { diff --git a/devtools/client/themes/variables.css b/devtools/client/themes/variables.css index 0a8d621eceb1..bc29f08813f1 100644 --- a/devtools/client/themes/variables.css +++ b/devtools/client/themes/variables.css @@ -93,6 +93,17 @@ --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); @@ -101,6 +112,16 @@ --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; @@ -180,6 +201,17 @@ --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); @@ -252,5 +284,6 @@ --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); }