diff --git a/res/css/style.css b/res/css/style.css index 03e8dea49e..c8bd550a77 100644 --- a/res/css/style.css +++ b/res/css/style.css @@ -39,6 +39,19 @@ body { color: var(--grey-90); } +@media (forced-colors: active) { + :root { + /* The following properties are retrieved in index.js to create SVG filters that + * we can apply on icons to control their color. + * These variable can be declared in any rule, as long as their value is still + * a valid color (which means we can use `light-dark()` or any other color functions). + * The colors can be applied on SVG icons with `filter: url(#--var-name)` */ + --button-icon-color: ButtonText; + --button-icon-hover-color: SelectedItem; + --button-icon-active-color: SelectedItem; + } +} + .profileFilterNavigator { height: 24px; flex-shrink: 1; @@ -49,3 +62,11 @@ body { flex: 1; flex-flow: column nowrap; } + +#svg-filters { + position: absolute; + overflow: hidden; + width: 0; + height: 0; + inset-inline-start: -1000vw; +} diff --git a/res/index.html b/res/index.html index 894db7c501..0e8721a65e 100644 --- a/res/index.html +++ b/res/index.html @@ -10,6 +10,7 @@ +
diff --git a/src/components/timeline/Selection.css b/src/components/timeline/Selection.css index b92c5f20dd..dc05ed3555 100644 --- a/src/components/timeline/Selection.css +++ b/src/components/timeline/Selection.css @@ -9,6 +9,10 @@ 100vw - var(--thread-label-column-width) - var(--vertical-scrollbar-reserved-width) ); + --grippy-range-background-color: #aaa; + --grippy-range-border-color: white; + --grippy-range-hover-background-color: #888; + --selection-outbound-opacity: 0.1; position: relative; display: flex; @@ -54,7 +58,8 @@ .timelineSelectionDimmerBefore, .timelineSelectionDimmerAfter { flex-shrink: 0; - background: rgb(12 12 13 / 0.1); + background: rgb(12 12 13 / var(--selection-outbound-opacity)); + forced-color-adjust: none; } .timelineSelectionDimmerAfter { @@ -79,10 +84,10 @@ z-index: 3; width: 0; padding: 3px; - border: 1px solid white; + border: 1px solid var(--grippy-range-border-color); border-radius: 5px; margin: 0 -4px; - background: #aaa; + background: var(--grippy-range-background-color); cursor: ew-resize; } @@ -99,7 +104,7 @@ .timelineSelectionGrippyRangeEnd:hover, .draggingEnd > .timelineSelectionGrippyRangeEnd, :not(.draggingStart, .draggingEnd) > .timelineSelectionGrippyRangeEnd.dragging { - background: #888; + background: var(--grippy-range-hover-background-color); } .timelineSelectionGrippyMoveRange { @@ -145,14 +150,21 @@ border: 1px solid rgb(0 0 0 / 0.2); border-radius: 100%; margin: -15px; - background: url(../../../res/img/svg/zoom-icon.svg) center center no-repeat - rgb(255 255 255 / 0.6); + background-color: rgb(255 255 255 / 0.6); opacity: 0.5; pointer-events: auto; transition: opacity 200ms ease-in-out; will-change: opacity; } +.timelineSelectionOverlayZoomButton::before { + position: absolute; + display: grid; + content: url(firefox-profiler-res/img/svg/zoom-icon.svg); + inset: 0; + place-content: center; +} + .timelineSelectionOverlayZoomButton.hidden { opacity: 0 !important; pointer-events: none; @@ -181,4 +193,47 @@ .timelineSelectionHoverLine { background-color: CanvasText; } + + .timelineSelection { + --grippy-range-background-color: ButtonText; + --grippy-range-border-color: ButtonBorder; + --grippy-range-hover-background-color: SelectedItem; + --selection-outbound-opacity: 0.6; + } + + .timelineSelectionDimmerBefore { + border-inline-end: 1px solid CanvasText; + } + + .timelineSelectionDimmerAfter { + border-inline-start: 1px solid CanvasText; + } + + .timelineSelectionOverlayZoomButton { + border-color: ButtonText; + background-color: ButtonFace; + opacity: 1; + } + + .timelineSelectionOverlayZoomButton::before { + filter: url(#--button-icon-color); + } + + .timelineSelectionOverlayZoomButton:hover { + border-color: SelectedItem; + background-color: SelectedItemText; + } + + .timelineSelectionOverlayZoomButton:hover::before { + filter: url(#--button-icon-hover-color); + } + + .timelineSelectionOverlayZoomButton:active:hover { + border-color: ButtonText; + background-color: ButtonFace; + } + + .timelineSelectionOverlayZoomButton:hover:active::before { + filter: url(#--button-icon-active-color); + } } diff --git a/src/index.js b/src/index.js index e12a34bf7b..efff61037d 100644 --- a/src/index.js +++ b/src/index.js @@ -44,6 +44,57 @@ window.geckoProfilerPromise = new Promise(function (resolve) { window.connectToGeckoProfiler = resolve; }); +// We're using an element from the page to apply filter from CSS rules +// to be able to dynamically change the color of SVG icons (e.g. to adapt +// to light/dark/high contrast themes). +const svgFiltersElement = document.getElementById('svg-filters'); +if (svgFiltersElement) { + const defineSvgFiltersForColors = () => { + const colors = [ + '--button-icon-color', + '--button-icon-hover-color', + '--button-icon-active-color', + ]; + for (const cssVariable of colors) { + let filterEl = document.getElementById(cssVariable); + if (!filterEl) { + svgFiltersElement.insertAdjacentHTML( + 'beforeend', + ` + + + ` + ); + filterEl = document.getElementById(cssVariable); + } + + if (!filterEl) { + continue; + } + + const feFloodEl = filterEl.querySelector('feFlood'); + + if (!feFloodEl) { + continue; + } + + const color = document.documentElement + ? getComputedStyle(document.documentElement).getPropertyValue( + cssVariable + ) + : ''; + + feFloodEl.setAttribute('flood-color', color); + } + }; + defineSvgFiltersForColors(); + + const forcedColorsMql = window.matchMedia('(forced-colors: active)'); + const darkSchemeMql = window.matchMedia('(prefers-color-scheme: dark)'); + forcedColorsMql.addEventListener('change', defineSvgFiltersForColors); + darkSchemeMql.addEventListener('change', defineSvgFiltersForColors); +} + const store = createStore(); const root = createRoot( ensureExists( diff --git a/src/types/globals/Window.js b/src/types/globals/Window.js index 5ce847297a..a9e61b8081 100644 --- a/src/types/globals/Window.js +++ b/src/types/globals/Window.js @@ -90,6 +90,7 @@ declare class Window { platform: string, }; postMessage: (message: any, targetOrigin: string) => void; + matchMedia: (matchMedia: string) => MediaQueryList; } declare var window: Window;