diff --git a/package.json b/package.json index 0ebf49c20..6e7fd8fd0 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "surfer": "surfer", "test": "python3 scripts/run_tests.py", "test:dbg": "python3 scripts/run_tests.py --jsdebugger --debug-on-failure", + "test:gtest": "cd engine && ./mach gtest", "ffprefs": "cd tools/ffprefs && cargo run --bin ffprefs -- ../../", "lc": "surfer license-check", "lc:fix": "surfer license-check --fix", diff --git a/prefs/zen/boosts.yaml b/prefs/zen/boosts.yaml index b0de3d3fa..b9ab5b473 100644 --- a/prefs/zen/boosts.yaml +++ b/prefs/zen/boosts.yaml @@ -3,7 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. - name: zen.boosts.enabled - value: "@IS_TWILIGHT@" + value: true - name: zen.boosts.dissolve-on-zap value: true diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 1f7779e35..cb1b6f892 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -924,7 +924,7 @@ position: relative; - list-style-image: url("paintbrush.svg"); + list-style-image: url("boost.svg"); & .toolbarbutton-text { display: none; @@ -937,6 +937,8 @@ & image { -moz-context-properties: fill, fill-opacity; fill: currentColor; + width: 14px; + fill-opacity: 0.7; } } @@ -1066,7 +1068,7 @@ } #zen-boost-shuffle { - list-style-image: url("arrow-rotate-anticlockwise.svg"); + list-style-image: url("dice.svg"); } #zen-boost-css-picker { diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index be1479dcf..b252b3f52 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -13,13 +13,13 @@ * skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg) * skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg) * skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg) +* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg) * skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg) * skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg) * skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg) * skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg) * skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg) * skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg) -* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg) * skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg) * skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg) * skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg) @@ -37,6 +37,7 @@ * skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/nucleo/desktop-notification-fill.svg) * skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/nucleo/desktop-notification.svg) * skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/nucleo/developer.svg) +* skin/classic/browser/zen-icons/dice.svg (../shared/zen-icons/nucleo/dice.svg) * skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/nucleo/downloads.svg) * skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/nucleo/drag-indicator.svg) * skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/nucleo/duplicate-tab.svg) @@ -102,26 +103,26 @@ * skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/nucleo/popup-fill.svg) * skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/nucleo/popup.svg) * skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/nucleo/print.svg) -* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/nucleo/private-window.svg) * skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/nucleo/privateBrowsing.svg) +* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/nucleo/private-window.svg) * skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/nucleo/reader-mode.svg) * skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/nucleo/reload.svg) * skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/nucleo/save.svg) * skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/nucleo/screen-blocked.svg) -* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/nucleo/screen.svg) * skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/nucleo/screenshot.svg) +* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/nucleo/screen.svg) * skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/nucleo/search-glass.svg) * skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/nucleo/search-page.svg) * skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/nucleo/security-broken.svg) -* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/nucleo/security-warning.svg) * skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/nucleo/security.svg) +* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/nucleo/security-warning.svg) * skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/nucleo/send-to-device.svg) * skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/nucleo/settings-fill.svg) * skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/nucleo/settings.svg) * skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/nucleo/share.svg) * skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg) -* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg) * skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg) +* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg) * skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg) * skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg) * skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg) @@ -161,13 +162,13 @@ * skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg) * skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg) * skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg) +* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg) * skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg) * skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg) * skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg) * skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg) * skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg) * skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg) -* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg) * skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg) * skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg) * skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg) @@ -185,6 +186,7 @@ * skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/nucleo/desktop-notification-fill.svg) * skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/nucleo/desktop-notification.svg) * skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/nucleo/developer.svg) +* skin/classic/browser/zen-icons/dice.svg (../shared/zen-icons/nucleo/dice.svg) * skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/nucleo/downloads.svg) * skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/nucleo/drag-indicator.svg) * skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/nucleo/duplicate-tab.svg) @@ -250,26 +252,26 @@ * skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/nucleo/popup-fill.svg) * skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/nucleo/popup.svg) * skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/nucleo/print.svg) -* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/nucleo/private-window.svg) * skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/nucleo/privateBrowsing.svg) +* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/nucleo/private-window.svg) * skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/nucleo/reader-mode.svg) * skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/nucleo/reload.svg) * skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/nucleo/save.svg) * skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/nucleo/screen-blocked.svg) -* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/nucleo/screen.svg) * skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/nucleo/screenshot.svg) +* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/nucleo/screen.svg) * skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/nucleo/search-glass.svg) * skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/nucleo/search-page.svg) * skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/nucleo/security-broken.svg) -* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/nucleo/security-warning.svg) * skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/nucleo/security.svg) +* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/nucleo/security-warning.svg) * skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/nucleo/send-to-device.svg) * skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/nucleo/settings-fill.svg) * skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/nucleo/settings.svg) * skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/nucleo/share.svg) * skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg) -* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg) * skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg) +* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg) * skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg) * skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg) * skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg) @@ -309,13 +311,13 @@ * skin/classic/browser/zen-icons/autoplay-media-fill.svg (../shared/zen-icons/nucleo/autoplay-media-fill.svg) * skin/classic/browser/zen-icons/autoplay-media.svg (../shared/zen-icons/nucleo/autoplay-media.svg) * skin/classic/browser/zen-icons/back.svg (../shared/zen-icons/nucleo/back.svg) +* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg) * skin/classic/browser/zen-icons/block.svg (../shared/zen-icons/nucleo/block.svg) * skin/classic/browser/zen-icons/bolt.svg (../shared/zen-icons/nucleo/bolt.svg) * skin/classic/browser/zen-icons/bookmark-hollow.svg (../shared/zen-icons/nucleo/bookmark-hollow.svg) * skin/classic/browser/zen-icons/bookmark-star-on-tray.svg (../shared/zen-icons/nucleo/bookmark-star-on-tray.svg) * skin/classic/browser/zen-icons/bookmark.svg (../shared/zen-icons/nucleo/bookmark.svg) * skin/classic/browser/zen-icons/boost.svg (../shared/zen-icons/nucleo/boost.svg) -* skin/classic/browser/zen-icons/blocked-element.svg (../shared/zen-icons/nucleo/blocked-element.svg) * skin/classic/browser/zen-icons/brackets-curly.svg (../shared/zen-icons/nucleo/brackets-curly.svg) * skin/classic/browser/zen-icons/camera-blocked.svg (../shared/zen-icons/nucleo/camera-blocked.svg) * skin/classic/browser/zen-icons/camera-fill.svg (../shared/zen-icons/nucleo/camera-fill.svg) @@ -333,6 +335,7 @@ * skin/classic/browser/zen-icons/desktop-notification-fill.svg (../shared/zen-icons/nucleo/desktop-notification-fill.svg) * skin/classic/browser/zen-icons/desktop-notification.svg (../shared/zen-icons/nucleo/desktop-notification.svg) * skin/classic/browser/zen-icons/developer.svg (../shared/zen-icons/nucleo/developer.svg) +* skin/classic/browser/zen-icons/dice.svg (../shared/zen-icons/nucleo/dice.svg) * skin/classic/browser/zen-icons/downloads.svg (../shared/zen-icons/nucleo/downloads.svg) * skin/classic/browser/zen-icons/drag-indicator.svg (../shared/zen-icons/nucleo/drag-indicator.svg) * skin/classic/browser/zen-icons/duplicate-tab.svg (../shared/zen-icons/nucleo/duplicate-tab.svg) @@ -398,26 +401,26 @@ * skin/classic/browser/zen-icons/popup-fill.svg (../shared/zen-icons/nucleo/popup-fill.svg) * skin/classic/browser/zen-icons/popup.svg (../shared/zen-icons/nucleo/popup.svg) * skin/classic/browser/zen-icons/print.svg (../shared/zen-icons/nucleo/print.svg) -* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/nucleo/private-window.svg) * skin/classic/browser/zen-icons/privateBrowsing.svg (../shared/zen-icons/nucleo/privateBrowsing.svg) +* skin/classic/browser/zen-icons/private-window.svg (../shared/zen-icons/nucleo/private-window.svg) * skin/classic/browser/zen-icons/reader-mode.svg (../shared/zen-icons/nucleo/reader-mode.svg) * skin/classic/browser/zen-icons/reload.svg (../shared/zen-icons/nucleo/reload.svg) * skin/classic/browser/zen-icons/save.svg (../shared/zen-icons/nucleo/save.svg) * skin/classic/browser/zen-icons/screen-blocked.svg (../shared/zen-icons/nucleo/screen-blocked.svg) -* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/nucleo/screen.svg) * skin/classic/browser/zen-icons/screenshot.svg (../shared/zen-icons/nucleo/screenshot.svg) +* skin/classic/browser/zen-icons/screen.svg (../shared/zen-icons/nucleo/screen.svg) * skin/classic/browser/zen-icons/search-glass.svg (../shared/zen-icons/nucleo/search-glass.svg) * skin/classic/browser/zen-icons/search-page.svg (../shared/zen-icons/nucleo/search-page.svg) * skin/classic/browser/zen-icons/security-broken.svg (../shared/zen-icons/nucleo/security-broken.svg) -* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/nucleo/security-warning.svg) * skin/classic/browser/zen-icons/security.svg (../shared/zen-icons/nucleo/security.svg) +* skin/classic/browser/zen-icons/security-warning.svg (../shared/zen-icons/nucleo/security-warning.svg) * skin/classic/browser/zen-icons/send-to-device.svg (../shared/zen-icons/nucleo/send-to-device.svg) * skin/classic/browser/zen-icons/settings-fill.svg (../shared/zen-icons/nucleo/settings-fill.svg) * skin/classic/browser/zen-icons/settings.svg (../shared/zen-icons/nucleo/settings.svg) * skin/classic/browser/zen-icons/share.svg (../shared/zen-icons/nucleo/share.svg) * skin/classic/browser/zen-icons/sidebar-right.svg (../shared/zen-icons/nucleo/sidebar-right.svg) -* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg) * skin/classic/browser/zen-icons/sidebars-right.svg (../shared/zen-icons/nucleo/sidebars-right.svg) +* skin/classic/browser/zen-icons/sidebar.svg (../shared/zen-icons/nucleo/sidebar.svg) * skin/classic/browser/zen-icons/sliders.svg (../shared/zen-icons/nucleo/sliders.svg) * skin/classic/browser/zen-icons/sparkles.svg (../shared/zen-icons/nucleo/sparkles.svg) * skin/classic/browser/zen-icons/spell-check.svg (../shared/zen-icons/nucleo/spell-check.svg) @@ -453,8 +456,8 @@ * skin/classic/browser/zen-icons/selectable/basket.svg (../shared/zen-icons/common/selectable/basket.svg) * skin/classic/browser/zen-icons/selectable/bed.svg (../shared/zen-icons/common/selectable/bed.svg) * skin/classic/browser/zen-icons/selectable/bell.svg (../shared/zen-icons/common/selectable/bell.svg) -* skin/classic/browser/zen-icons/selectable/book.svg (../shared/zen-icons/common/selectable/book.svg) * skin/classic/browser/zen-icons/selectable/bookmark.svg (../shared/zen-icons/common/selectable/bookmark.svg) +* skin/classic/browser/zen-icons/selectable/book.svg (../shared/zen-icons/common/selectable/book.svg) * skin/classic/browser/zen-icons/selectable/briefcase.svg (../shared/zen-icons/common/selectable/briefcase.svg) * skin/classic/browser/zen-icons/selectable/brush.svg (../shared/zen-icons/common/selectable/brush.svg) * skin/classic/browser/zen-icons/selectable/bug.svg (../shared/zen-icons/common/selectable/bug.svg) @@ -516,8 +519,8 @@ * skin/classic/browser/zen-icons/selectable/shapes.svg (../shared/zen-icons/common/selectable/shapes.svg) * skin/classic/browser/zen-icons/selectable/shirt.svg (../shared/zen-icons/common/selectable/shirt.svg) * skin/classic/browser/zen-icons/selectable/skull.svg (../shared/zen-icons/common/selectable/skull.svg) -* skin/classic/browser/zen-icons/selectable/square.svg (../shared/zen-icons/common/selectable/square.svg) * skin/classic/browser/zen-icons/selectable/squares.svg (../shared/zen-icons/common/selectable/squares.svg) +* skin/classic/browser/zen-icons/selectable/square.svg (../shared/zen-icons/common/selectable/square.svg) * skin/classic/browser/zen-icons/selectable/star-1.svg (../shared/zen-icons/common/selectable/star-1.svg) * skin/classic/browser/zen-icons/selectable/star.svg (../shared/zen-icons/common/selectable/star.svg) * skin/classic/browser/zen-icons/selectable/stats-chart.svg (../shared/zen-icons/common/selectable/stats-chart.svg) diff --git a/src/browser/themes/shared/zen-icons/nucleo/dice.svg b/src/browser/themes/shared/zen-icons/nucleo/dice.svg new file mode 100644 index 000000000..7a6197e79 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/nucleo/dice.svg @@ -0,0 +1,5 @@ +#filter dumbComments emptyLines substitution +# 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/. + diff --git a/src/gfx/layers/AnimationInfo-cpp.patch b/src/gfx/layers/AnimationInfo-cpp.patch index e9779a101..aa58cdcbc 100644 --- a/src/gfx/layers/AnimationInfo-cpp.patch +++ b/src/gfx/layers/AnimationInfo-cpp.patch @@ -1,5 +1,5 @@ diff --git a/gfx/layers/AnimationInfo.cpp b/gfx/layers/AnimationInfo.cpp -index 1d330056bd7a4e89aac5e5296a3c164fb42b5c42..ef112715580b6bb7238e8f37bbe3133e187685dc 100644 +index 1d330056bd7a4e89aac5e5296a3c164fb42b5c42..38bfbcfcaf0c791ee817aafbd24b1cad67974e62 100644 --- a/gfx/layers/AnimationInfo.cpp +++ b/gfx/layers/AnimationInfo.cpp @@ -14,6 +14,7 @@ @@ -10,12 +10,22 @@ index 1d330056bd7a4e89aac5e5296a3c164fb42b5c42..ef112715580b6bb7238e8f37bbe3133e #include "nsIContent.h" #include "nsLayoutUtils.h" #include "nsRefreshDriver.h" -@@ -343,7 +344,7 @@ static void SetAnimatable(NonCustomCSSPropertyId aProperty, +@@ -343,7 +344,17 @@ static void SetAnimatable(NonCustomCSSPropertyId aProperty, // resolve currentColor at this moment. nscolor foreground = aFrame->Style()->GetVisitedDependentColor(&nsStyleText::mColor); - aAnimatable = aAnimationValue.GetColor(foreground); -+ aAnimatable = zen::nsZenBoostsBackend::FilterColorFromPresContext(aAnimationValue.GetColor(foreground), aFrame->PresContext()); ++ nscolor resolved = aAnimationValue.GetColor(foreground); ++ // |foreground| is already boost-resolved through ++ // StyleAbsoluteColor::ToColor, so a currentColor keyframe is already ++ // filtered; only absolute keyframe colors still need the boost applied ++ // here, exactly once, so the composited transition endpoint matches the ++ // resting/static paint and doesn't snap when the transition ends. ++ aAnimatable = ++ aAnimationValue.IsCurrentColor() ++ ? resolved ++ : zen::nsZenBoostsBackend::FilterColorFromPresContext( ++ resolved, aFrame->PresContext()); break; } case eCSSProperty_opacity: diff --git a/src/layout/style/StyleColor-cpp.patch b/src/layout/style/StyleColor-cpp.patch index 6f84f6327..ef81c99dc 100644 --- a/src/layout/style/StyleColor-cpp.patch +++ b/src/layout/style/StyleColor-cpp.patch @@ -1,5 +1,5 @@ diff --git a/layout/style/StyleColor.cpp b/layout/style/StyleColor.cpp -index 95c7ae6abea5032bef0466e8d59d212374d7a4d0..8dbfbb846b786d51af288989163aacfae12e787c 100644 +index 95c7ae6abea5032bef0466e8d59d212374d7a4d0..3b2118e224141f5151a31ac663dfbe17864ef182 100644 --- a/layout/style/StyleColor.cpp +++ b/layout/style/StyleColor.cpp @@ -8,6 +8,7 @@ @@ -10,25 +10,7 @@ index 95c7ae6abea5032bef0466e8d59d212374d7a4d0..8dbfbb846b786d51af288989163aacfa namespace mozilla { -@@ -21,6 +22,8 @@ bool StyleColor::MaybeTransparent() const { - template <> - StyleAbsoluteColor StyleColor::ResolveColor( - const StyleAbsoluteColor& aForegroundColor) const { -+ auto ResolveColorInner = [this, -+ &aForegroundColor]() -> StyleAbsoluteColor { - if (IsAbsolute()) { - return AsAbsolute(); - } -@@ -30,6 +33,8 @@ StyleAbsoluteColor StyleColor::ResolveColor( - } - - return Servo_ResolveColor(this, &aForegroundColor); -+ }; -+ return zen::nsZenBoostsBackend::ResolveStyleColor(ResolveColorInner()); - } - - template <> -@@ -68,10 +73,11 @@ nscolor StyleAbsoluteColor::ToColor() const { +@@ -68,10 +69,11 @@ nscolor StyleAbsoluteColor::ToColor() const { auto green = std::clamp(srgb.components._1, 0.0f, 1.0f); auto blue = std::clamp(srgb.components._2, 0.0f, 1.0f); diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 67abb7bc4..0525b5378 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -539,6 +539,12 @@ class nsZenBoostsManager { const directoryPath = this.#cssPath; const savePath = PathUtils.join(directoryPath, fileName); + if (!css || css.trim() === "") { + if (await IOUtils.exists(savePath)) { + await IOUtils.remove(savePath); + } + return; + } await IOUtils.makeDirectory(directoryPath, { createAncestors: true }); await IOUtils.writeUTF8(savePath, css); } diff --git a/src/zen/boosts/ZenZapOverlayChild.sys.mjs b/src/zen/boosts/ZenZapOverlayChild.sys.mjs index df7c10cc0..7b8b4ed35 100644 --- a/src/zen/boosts/ZenZapOverlayChild.sys.mjs +++ b/src/zen/boosts/ZenZapOverlayChild.sys.mjs @@ -26,6 +26,7 @@ export class ZapOverlay { #dissolvePoolSize = 5; #dissolveEffectPool = []; #currentDissolveIndex = 0; + #onZapDoneClick = null; /** * @param {*} document Webpage document @@ -77,10 +78,8 @@ export class ZapOverlay { */ #initializeElements() { this.zapDoneButton = this.getElementById("zap-done"); - this.zapDoneButton.addEventListener( - "click", - this.#disableZapMode.bind(this) - ); + this.#onZapDoneClick = this.#disableZapMode.bind(this); + this.zapDoneButton.addEventListener("click", this.#onZapDoneClick); this.#updateZappedList(); } @@ -355,6 +354,12 @@ export class ZapOverlay { dissolve.tearDown(); }); + if (this.zapDoneButton && this.#onZapDoneClick) { + this.zapDoneButton.removeEventListener("click", this.#onZapDoneClick); + } + this.#onZapDoneClick = null; + this.zapDoneButton = null; + if (this.#content) { try { this.document.removeAnonymousContent(this.#content); diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 1ea6b1e60..78260f30a 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -322,13 +322,18 @@ export class ZenBoostsChild extends JSWindowActorChild { return p; } + get #hostWithoutPort() { + const host = this.browsingContext.topWindow?.location.host; + return host?.split(":")[0]; + } + /** * Aquires the boost data for this website * * @returns {object} Boost data for the current website */ getWebsiteBoost() { - const domain = this.browsingContext.topWindow?.location?.host; + const domain = this.#hostWithoutPort; if (!domain) { return null; } @@ -362,6 +367,7 @@ export class ZenBoostsChild extends JSWindowActorChild { this.#loadStyleSheet(boost.styleSheet); } + browsingContext.fullZoom = boostData.sizeOverride; browsingContext.isZenBoostsInverted = boostData.smartInvert; if (boostData.enableColorBoost) { let primaryColor; @@ -484,7 +490,7 @@ export class ZenBoostsChild extends JSWindowActorChild { } addZapSelector(selector) { - const domain = this.browsingContext.topWindow?.location?.host; + const domain = this.#hostWithoutPort; this.sendQuery("ZenBoost:ZapSelector", { action: "add", selector, @@ -493,7 +499,7 @@ export class ZenBoostsChild extends JSWindowActorChild { } removeZapSelector(selector) { - const domain = this.browsingContext.topWindow?.location?.host; + const domain = this.#hostWithoutPort; this.sendQuery("ZenBoost:ZapSelector", { action: "remove", selector, diff --git a/src/zen/boosts/gtest/TestZenBoostsColorFilter.cpp b/src/zen/boosts/gtest/TestZenBoostsColorFilter.cpp new file mode 100644 index 000000000..e378451d0 --- /dev/null +++ b/src/zen/boosts/gtest/TestZenBoostsColorFilter.cpp @@ -0,0 +1,121 @@ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/nsZenBoostsBackend.h" + +using zen::detail::FilterColorChannel; +using zen::detail::InvertColorChannel; +using zen::detail::PrecomputeAccent; +using zen::detail::RotateAccent; + +namespace { + +// A spread of representative input colors (opaque unless noted). +const nscolor kColors[] = { + NS_RGBA(255, 0, 0, 255), // pure red + NS_RGBA(0, 255, 0, 255), // pure green + NS_RGBA(0, 0, 255, 255), // pure blue + NS_RGBA(0, 0, 0, 255), // black + NS_RGBA(255, 255, 255, 255), // white + NS_RGBA(128, 128, 128, 255), // mid gray + NS_RGBA(18, 52, 86, 200), // arbitrary, semi-transparent + NS_RGBA(240, 17, 99, 1), // near-min alpha +}; + +// The accent stores the contrast/strength in its alpha byte +// (NS_GET_CONTRAST == NS_GET_A). 0 means "no tint". +zen::nsZenAccentOklab MakeAccent(uint8_t r, uint8_t g, uint8_t b, + uint8_t contrast) { + return PrecomputeAccent(NS_RGBA(r, g, b, contrast)); +} + +} // namespace + +// The headline invariant: filtering must never change opacity. The whole +// pipeline overloads the alpha byte for contrast on the *accent*, but a +// filtered *content* color must keep its original alpha. +TEST(ZenBoostsColorFilter, PreservesAlpha) +{ + const zen::nsZenAccentOklab accent = MakeAccent(80, 120, 200, 180); + const zen::nsZenAccentOklab complementary = RotateAccent(accent, 180.0f); + + for (nscolor c : kColors) { + const nscolor out = FilterColorChannel(c, accent, complementary); + EXPECT_EQ(NS_GET_A(out), NS_GET_A(c)) << "alpha changed for input " << c; + } +} + +// Fully transparent colors are invisible; the filter must pass them through +// untouched (and must not interpret their zero alpha as contrast). +TEST(ZenBoostsColorFilter, TransparentPassthrough) +{ + const zen::nsZenAccentOklab accent = MakeAccent(80, 120, 200, 180); + const zen::nsZenAccentOklab complementary = RotateAccent(accent, 90.0f); + + const nscolor transparent = NS_RGBA(255, 0, 0, 0); + EXPECT_EQ(FilterColorChannel(transparent, accent, complementary), + transparent); + EXPECT_EQ(InvertColorChannel(transparent), transparent); +} + +// Same inputs must always yield the same output (no hidden global state in +// the math itself; the production cache lives outside these primitives). +TEST(ZenBoostsColorFilter, Deterministic) +{ + const zen::nsZenAccentOklab accent = MakeAccent(33, 200, 90, 200); + const zen::nsZenAccentOklab complementary = RotateAccent(accent, 200.0f); + + for (nscolor c : kColors) { + const nscolor a = FilterColorChannel(c, accent, complementary); + const nscolor b = FilterColorChannel(c, accent, complementary); + EXPECT_EQ(a, b); + EXPECT_EQ(InvertColorChannel(c), InvertColorChannel(c)); + } +} + +// A zero-contrast accent means "no boost strength": the color must come back +// essentially unchanged (allow +/-1 per channel for sRGB<->Oklab rounding). +TEST(ZenBoostsColorFilter, ZeroContrastIsNearIdentity) +{ + const zen::nsZenAccentOklab accent = MakeAccent(200, 50, 50, 0); + const zen::nsZenAccentOklab complementary = RotateAccent(accent, 180.0f); + + for (nscolor c : kColors) { + if (NS_GET_A(c) == 0) { + continue; + } + const nscolor out = FilterColorChannel(c, accent, complementary); + EXPECT_NEAR(NS_GET_R(out), NS_GET_R(c), 1); + EXPECT_NEAR(NS_GET_G(out), NS_GET_G(c), 1); + EXPECT_NEAR(NS_GET_B(out), NS_GET_B(c), 1); + EXPECT_EQ(NS_GET_A(out), NS_GET_A(c)); + } +} + +// Guards against a regression that turns the filter into a no-op: a strong +// accent applied to a neutral gray must actually move the color. +TEST(ZenBoostsColorFilter, StrongAccentActuallyTints) +{ + const zen::nsZenAccentOklab accent = MakeAccent(20, 130, 240, 255); + const zen::nsZenAccentOklab complementary = RotateAccent(accent, 30.0f); + + const nscolor gray = NS_RGBA(128, 128, 128, 255); + const nscolor out = FilterColorChannel(gray, accent, complementary); + + const bool moved = NS_GET_R(out) != NS_GET_R(gray) || + NS_GET_G(out) != NS_GET_G(gray) || + NS_GET_B(out) != NS_GET_B(gray); + EXPECT_TRUE(moved) << "a full-strength accent should tint mid gray"; + EXPECT_EQ(NS_GET_A(out), NS_GET_A(gray)); +} + +// Inversion must also preserve opacity. +TEST(ZenBoostsColorFilter, InvertPreservesAlpha) +{ + for (nscolor c : kColors) { + EXPECT_EQ(NS_GET_A(InvertColorChannel(c)), NS_GET_A(c)); + } +} diff --git a/src/zen/boosts/gtest/moz.build b/src/zen/boosts/gtest/moz.build new file mode 100644 index 000000000..cc14100b7 --- /dev/null +++ b/src/zen/boosts/gtest/moz.build @@ -0,0 +1,9 @@ +# 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/. + +UNIFIED_SOURCES += [ + "TestZenBoostsColorFilter.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index 211d68fc2..a436cf3de 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -26,3 +26,7 @@ SOURCES += [ ] FINAL_LIBRARY = "xul" + +TEST_DIRS += [ + "gtest", +] diff --git a/src/zen/boosts/nsZenBCOverrides.cpp b/src/zen/boosts/nsZenBCOverrides.cpp index 0be59563e..306d35bf7 100644 --- a/src/zen/boosts/nsZenBCOverrides.cpp +++ b/src/zen/boosts/nsZenBCOverrides.cpp @@ -43,7 +43,7 @@ static void RefreshBoostCacheIfMatchesCurrent(BrowsingContext* aChanged) { if (!backend) { return; } - auto current = backend->GetCurrentBrowsingContext(); + RefPtr current = backend->GetCurrentBrowsingContext(); if (!current || current->Top() != aChanged) { return; } diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 1968c6ad1..1a54b0264 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include "nsZenBoostsBackend.h" @@ -48,10 +50,6 @@ namespace zen { NS_IMPL_ISUPPORTS0(nsZenBoostsBackend) -nsZenAccentOklab nsZenBoostsBackend::mCachedAccent{0}; -nsZenAccentOklab nsZenBoostsBackend::mCachedComplementary{0}; -float nsZenBoostsBackend::mCachedComplementaryRotationDeg = 0.0f; - namespace { /** @@ -83,17 +81,43 @@ static inline float linearToSrgb(float c) { static inline float fastCbrt(float x) { if (x == 0.0f) return 0.0f; float a = std::abs(x); - union { - float f; - uint32_t i; - } u = {a}; - u.i = u.i / 3 + 0x2a504a2e; - float y = u.f; + // Bit-level initial guess. Use memcpy rather than a union to avoid the + // undefined behaviour of type-punning through a union member in C++. + uint32_t i; + std::memcpy(&i, &a, sizeof(i)); + i = i / 3 + 0x2a504a2e; + float y; + std::memcpy(&y, &i, sizeof(y)); y = (2.0f * y + a / (y * y)) * (1.0f / 3.0f); y = (2.0f * y + a / (y * y)) * (1.0f / 3.0f); return x < 0.0f ? -y : y; } +/** + * @brief sRGB(0..255) -> linear lookup table. The filter only ever feeds + * integer 8-bit channels through srgbToLinear, so the 256 possible results + * are precomputed once instead of calling std::pow three times per color on + * the per-color hot path. Built lazily on first use; the function-local + * static makes initialization thread-safe. + */ +static inline const std::array& SrgbLinearTable() { + static const std::array kTable = [] { + std::array table{}; + for (int i = 0; i < 256; ++i) { + table[i] = srgbToLinear(i * (1.0f / 255.0f)); + } + return table; + }(); + return kTable; +} + +/** + * @brief Linearizes an 8-bit sRGB channel via the precomputed table. + */ +static inline float srgbToLinear8(uint8_t aChannel) { + return SrgbLinearTable()[aChannel]; +} + /** * @brief Precomputes the Oklab values for a given accent color. This allows us * to efficiently apply the accent color as a filter to other colors without @@ -107,13 +131,9 @@ ZEN_HOT_FUNCTION inline static auto zenPrecomputeAccent(nscolor aAccentColor) { constexpr float inv255 = 1.0f / 255.0f; - const float r = NS_GET_R(aAccentColor) * inv255; - const float g = NS_GET_G(aAccentColor) * inv255; - const float b = NS_GET_B(aAccentColor) * inv255; - - const float lr = srgbToLinear(r); - const float lg = srgbToLinear(g); - const float lb = srgbToLinear(b); + const float lr = srgbToLinear8(NS_GET_R(aAccentColor)); + const float lg = srgbToLinear8(NS_GET_G(aAccentColor)); + const float lb = srgbToLinear8(NS_GET_B(aAccentColor)); const float l_ = fastCbrt(0.4122214708f * lr + 0.5363325363f * lg + 0.0514459929f * lb); @@ -156,6 +176,45 @@ inline static nsZenAccentOklab zenRotateAccent(const nsZenAccentOklab& aBase, }; } +/** + * @brief Small round-robin cache of precomputed accents. Painting several + * boosted tabs with different accents interleaved would otherwise recompute + * the Oklab base accent (with cbrt) and the rotated complementary accent on + * every single color. Keyed by the accent nscolor and the complementary hue + * rotation. Main-thread only (same threading assumption as the per-color + * paint path it serves). + */ +struct AccentCacheEntry { + nscolor accentNS = 0; + float rotationDeg = 0.0f; + bool valid = false; + nsZenAccentOklab accent{}; + nsZenAccentOklab complementary{}; +}; + +static constexpr size_t kAccentCacheSize = 4; +static AccentCacheEntry sAccentCache[kAccentCacheSize]; +static size_t sAccentCacheNext = 0; + +ZEN_HOT_FUNCTION +static const AccentCacheEntry& GetCachedAccent(nscolor aAccentNS, + float aRotationDeg) { + for (const auto& entry : sAccentCache) { + if (entry.valid && entry.accentNS == aAccentNS && + entry.rotationDeg == aRotationDeg) { + return entry; + } + } + AccentCacheEntry& slot = sAccentCache[sAccentCacheNext]; + sAccentCacheNext = (sAccentCacheNext + 1) % kAccentCacheSize; + slot.accentNS = aAccentNS; + slot.rotationDeg = aRotationDeg; + slot.accent = zenPrecomputeAccent(aAccentNS); + slot.complementary = zenRotateAccent(slot.accent, aRotationDeg); + slot.valid = true; + return slot; +} + /** * @brief Applies a duotone color filter to transform an original color toward * one of two accent colors. The original color's perceived lightness decides @@ -183,10 +242,10 @@ inline static nsZenAccentOklab zenRotateAccent(const nsZenAccentOklab& aBase, constexpr float inv255 = 1.0f / 255.0f; const float blendFactor = contrast * inv255; - // sRGB -> linear - const float lr = srgbToLinear(NS_GET_R(aOriginalColor) * inv255); - const float lg = srgbToLinear(NS_GET_G(aOriginalColor) * inv255); - const float lb = srgbToLinear(NS_GET_B(aOriginalColor) * inv255); + // sRGB -> linear (8-bit channels via the precomputed table) + const float lr = srgbToLinear8(NS_GET_R(aOriginalColor)); + const float lg = srgbToLinear8(NS_GET_G(aOriginalColor)); + const float lb = srgbToLinear8(NS_GET_B(aOriginalColor)); // Linear RGB -> LMS -> cube root -> Oklab (fused) const float l_ = @@ -347,6 +406,32 @@ inline static void GetZenBoostsDataFromBrowsingContext( } // namespace +namespace detail { + +// Thin forwarders that give unit tests access to the pure color math without +// pulling in the singleton / BrowsingContext. They are defined here, after the +// anonymous namespace, so they can reach those file-local implementations. +nsZenAccentOklab PrecomputeAccent(nscolor aAccentColor) { + return zenPrecomputeAccent(aAccentColor); +} + +nsZenAccentOklab RotateAccent(const nsZenAccentOklab& aBase, + float aRotationDeg) { + return zenRotateAccent(aBase, aRotationDeg); +} + +nscolor FilterColorChannel(nscolor aOriginalColor, + const nsZenAccentOklab& aAccent, + const nsZenAccentOklab& aComplementary) { + return zenFilterColorChannel(aOriginalColor, aAccent, aComplementary); +} + +nscolor InvertColorChannel(nscolor aColor) { + return zenInvertColorChannel(aColor); +} + +} // namespace detail + static mozilla::StaticRefPtr sZenBoostsBackend; auto nsZenBoostsBackend::GetInstance() -> nsZenBoostsBackend* { @@ -376,18 +461,25 @@ auto nsZenBoostsBackend::onPresShellEntered(mozilla::dom::Document* aDocument) if (!browsingContext) { return; } - mCurrentBrowsingContext = browsingContext; + mCurrentBrowsingContextId = browsingContext->Id(); RefreshCachedBoostState(); } +already_AddRefed +nsZenBoostsBackend::GetCurrentBrowsingContext() const { + return mozilla::dom::BrowsingContext::Get(mCurrentBrowsingContextId); +} + auto nsZenBoostsBackend::RefreshCachedBoostState() -> void { - if (!mCurrentBrowsingContext) { + RefPtr current = + mozilla::dom::BrowsingContext::Get(mCurrentBrowsingContextId); + if (!current) { mCachedCurrentAccent = 0; mCachedCurrentComplementaryRotation = 0.0f; mCachedCurrentInverted = false; return; } - auto top = mCurrentBrowsingContext->Top(); + auto top = current->Top(); mCachedCurrentAccent = top->ZenBoostsData(); mCachedCurrentComplementaryRotation = top->ZenBoostsComplementaryRotation(); mCachedCurrentInverted = top->IsZenBoostsInverted(); @@ -397,33 +489,27 @@ auto nsZenBoostsBackend::RefreshCachedBoostState() -> void { nsZenBoostsBackend::FilterColorFromPresContext(nscolor aColor, nsPresContext* aPresContext) -> nscolor { + if (NS_GET_A(aColor) == 0) { + // Skip processing fully transparent colors since they won't be visible and + // we want to avoid unnecessary computations. This also prevents issues with + // using the alpha channel for contrast information in the accent color. + return aColor; + } ZenBoostData accentNS = 0; float complementaryRotation = 0.0f; bool invertColors = false; GetZenBoostsDataFromBrowsingContext(&accentNS, &complementaryRotation, &invertColors, aPresContext); if (accentNS) { - if (mCachedAccent.accentNS != accentNS) { - mCachedAccent = zenPrecomputeAccent(accentNS); - // Trigger a recompute of the complementary accent since - // it depends on the base accent. - mCachedComplementary.accentNS = 0; - } - // Derive the complementary accent by rotating the base accent's hue by the - // boost's complementary rotation. Cached so the per-color hot path only - // recomputes it when the base accent or rotation changes. - if (mCachedComplementary.accentNS != accentNS || - mCachedComplementaryRotationDeg != complementaryRotation) { - mCachedComplementary = - zenRotateAccent(mCachedAccent, complementaryRotation); - mCachedComplementaryRotationDeg = complementaryRotation; - } - // Apply a filter-like tint: + // Resolve (and cache) the base + complementary accent for this accent and + // complementary rotation. Apply a filter-like tint: // - Preserve the original color's perceived luminance // - Map hue/chroma toward the base or complementary accent depending on // the original color's lightness // - Keep the original alpha - aColor = zenFilterColorChannel(aColor, mCachedAccent, mCachedComplementary); + const AccentCacheEntry& cached = + GetCachedAccent(accentNS, complementaryRotation); + aColor = zenFilterColorChannel(aColor, cached.accent, cached.complementary); } if (invertColors) { aColor = zenInvertColorChannel(aColor); @@ -431,20 +517,8 @@ nsZenBoostsBackend::FilterColorFromPresContext(nscolor aColor, return aColor; } -[[nodiscard]] ZEN_HOT_FUNCTION auto nsZenBoostsBackend::ResolveStyleColor( - mozilla::StyleAbsoluteColor aColor) -> mozilla::StyleAbsoluteColor { - const auto resultColor = FilterColorFromPresContext(aColor.ToColor()); - return mozilla::StyleAbsoluteColor::FromColor(resultColor); -} - [[nodiscard]] ZEN_HOT_FUNCTION auto nsZenBoostsBackend::ResolveStyleColor( nscolor aColor) -> nscolor { - if (NS_GET_A(aColor) == 0) { - // Skip processing fully transparent colors since they won't be visible and - // we want to avoid unnecessary computations. This also prevents issues with - // using the alpha channel for contrast information in the accent color. - return aColor; - } return FilterColorFromPresContext(aColor); } diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h index 67f3af6e8..81ca1d95b 100644 --- a/src/zen/boosts/nsZenBoostsBackend.h +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -10,6 +10,13 @@ #include "nsPresContext.h" #include "mozilla/RefPtr.h" +#include "mozilla/AlreadyAddRefed.h" + +#include + +namespace mozilla::dom { +class BrowsingContext; +} #define ZEN_BOOSTS_BACKEND_CONTRACTID "@mozilla.org/zen/boosts-backend;1" @@ -23,6 +30,19 @@ struct nsZenAccentOklab { float contrastFactor; }; +namespace detail { +// Pure color-math primitives, exposed for unit testing. These have no +// dependency on the singleton, the BrowsingContext, or the process type, so +// they can be exercised directly from gtest. +nsZenAccentOklab PrecomputeAccent(nscolor aAccentColor); +nsZenAccentOklab RotateAccent(const nsZenAccentOklab& aBase, + float aRotationDeg); +nscolor FilterColorChannel(nscolor aOriginalColor, + const nsZenAccentOklab& aAccent, + const nsZenAccentOklab& aComplementary); +nscolor InvertColorChannel(nscolor aColor); +} // namespace detail + class nsZenBoostsBackend final : public nsISupports { public: NS_DECL_ISUPPORTS @@ -36,17 +56,15 @@ class nsZenBoostsBackend final : public nsISupports { bool mCurrentFrameIsAnonymousContent = false; /** - * @brief Resolve a StyleAbsoluteColor to take into account Zen boosts. + * @brief Resolve a color to take into account Zen boosts. This is the single + * place style colors are filtered; it is reached for every style color via + * StyleAbsoluteColor::ToColor. Do not add a second StyleColor::ResolveColor + * filter on top of this or colors get filtered multiple times (which also + * makes resting colors disagree with composited transition endpoints). * @param aColor The color to resolve. * @return The resolved color with Zen boost filters applied, or the original * color if no boost is active. - * @see StyleColor::ResolveColor for reference. - */ - static auto ResolveStyleColor(mozilla::StyleAbsoluteColor aColor) - -> mozilla::StyleAbsoluteColor; - - /** - * @see ResolveStyleColor for reference. + * @see StyleAbsoluteColor::ToColor for reference. */ static auto ResolveStyleColor(nscolor aColor) -> nscolor; @@ -73,10 +91,13 @@ class nsZenBoostsBackend final : public nsISupports { */ auto RefreshCachedBoostState() -> void; - [[nodiscard]] - inline auto GetCurrentBrowsingContext() const { - return mCurrentBrowsingContext; - } + /** + * Resolves the current top BrowsingContext from its stored id. May return + * null if it has since been discarded. Not on the per-color hot path; the + * hot path uses the mCachedCurrent* fields instead. + */ + [[nodiscard]] already_AddRefed + GetCurrentBrowsingContext() const; /** * Cached boost data for the current top BrowsingContext, refreshed on @@ -95,15 +116,12 @@ class nsZenBoostsBackend final : public nsISupports { ~nsZenBoostsBackend() = default; /** - * The presshell of the current document being rendered. + * Id of the top BrowsingContext of the current document being rendered. + * Stored as an id rather than a strong RefPtr so the process-wide singleton + * does not keep a navigated-away BrowsingContext (and its subtree) alive + * until the next presshell entry. */ - RefPtr mCurrentBrowsingContext; - - static nsZenAccentOklab mCachedAccent; - // Base accent with its Oklab hue rotated by mCachedComplementaryRotationDeg, - // recomputed only when the base accent or rotation changes. - static nsZenAccentOklab mCachedComplementary; - static float mCachedComplementaryRotationDeg; + uint64_t mCurrentBrowsingContextId = 0; public: /** diff --git a/src/zen/tests/boosts/browser.toml b/src/zen/tests/boosts/browser.toml index c24f62f6a..9cacfe47f 100644 --- a/src/zen/tests/boosts/browser.toml +++ b/src/zen/tests/boosts/browser.toml @@ -8,5 +8,6 @@ support-files = [ ] ["browser_boost_selector_basic.js"] +["browser_boost_selector_escaping.js"] ["browser_boost_selector_invalid.js"] ["browser_boost_selector_nthchild.js"] diff --git a/src/zen/tests/boosts/browser_boost_selector_escaping.js b/src/zen/tests/boosts/browser_boost_selector_escaping.js new file mode 100644 index 000000000..5a3e299b6 --- /dev/null +++ b/src/zen/tests/boosts/browser_boost_selector_escaping.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Covers code paths the basic/invalid/nthchild tests don't: +// - getIdentification() running ids/classes through CSS.escape() +// - the ancestor-disambiguation while-loop in traverse(), which only runs +// when the exact path still matches more than one element. + +add_task(async function test_getSelectionPath_escapesSpecialChars() { + const doc = document.implementation.createHTMLDocument("TestEscape"); + + const container = doc.createElement("div"); + // Characters that are invalid in a CSS selector unless escaped. + container.id = "with.dot:and#hash"; + const target = doc.createElement("span"); + target.className = "foo:bar baz.qux"; + target.textContent = "target"; + container.appendChild(target); + doc.body.appendChild(container); + + const component = new SelectorComponent(doc, null, [], () => {}); + + const path = component.getSelectionPath(doc, 0, target); + ok(path, "A path should be generated for an element with special chars"); + + // The unescaped raw strings must not leak into the selector verbatim. + ok( + !path.includes("with.dot:and#hash"), + "Raw unescaped id must not appear in the selector" + ); + + // The generated selector must be valid and resolve back to the target. + let matched; + try { + matched = doc.querySelectorAll(path); + } catch (e) { + ok(false, `Generated selector should be parseable, got: ${e}`); + return; + } + ok( + Array.from(matched).includes(target), + "Escaped selector must still match the original element" + ); + Assert.equal( + matched.length, + 1, + "Selector should uniquely identify the element" + ); +}); + +add_task(async function test_getSelectionPath_disambiguatesAncestors() { + const doc = document.implementation.createHTMLDocument("TestAncestors"); + + // Two structurally identical subtrees. The leaf elements carry no id/class, + // so disambiguation must climb ancestors until the path is unique. The two + // wrappers differ only by id, forcing the ancestor-walk loop in traverse(). + const makeBranch = wrapperId => { + const wrapper = doc.createElement("section"); + wrapper.id = wrapperId; + const mid = doc.createElement("div"); + const leaf = doc.createElement("span"); + leaf.textContent = "leaf"; + mid.appendChild(leaf); + wrapper.appendChild(mid); + doc.body.appendChild(wrapper); + return leaf; + }; + + const leafA = makeBranch("branch-a"); + const leafB = makeBranch("branch-b"); + + const component = new SelectorComponent(doc, null, [], () => {}); + + for (const [leaf, label] of [ + [leafA, "branch-a"], + [leafB, "branch-b"], + ]) { + const path = component.getSelectionPath(doc, 0, leaf); + ok(path, `Path generated for the leaf under ${label}`); + + const matched = doc.querySelectorAll(path); + Assert.equal( + matched.length, + 1, + `Selector for the ${label} leaf must be unique despite an identical sibling subtree` + ); + ok( + matched[0] === leaf, + `Selector must resolve to the correct ${label} leaf, not the other branch` + ); + } +});