From 4369d5b90b64641feac6d200482c0008ccd52f11 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Wed, 29 Oct 2025 21:24:26 +0100 Subject: [PATCH 01/33] Add basic boosts implementation --- crowdin.yml | 2 + locales/en-US/browser/browser/zen-boosts.ftl | 34 ++ prefs/zen/zen-boosts.yaml | 6 + src/browser/base/content/zen-assets.inc.xhtml | 3 + .../base/content/zen-assets.jar.inc.mn | 3 + .../base/content/zen-commands.inc.xhtml | 2 + .../base/content/zen-locales.inc.xhtml | 1 + .../base/content/zen-panels/boost-editor.inc | 47 +++ .../base/content/zen-popupset.inc.xhtml | 1 + .../zen-icons/common/selectable/block.svg | 2 + .../zen-icons/common/selectable/bulb.svg | 2 + .../common/selectable/paintbrush.svg | 2 + .../zen-icons/common/selectable/zap.svg | 2 + src/browser/themes/shared/zen-icons/icons.css | 15 +- .../themes/shared/zen-icons/jar.inc.mn | 4 + .../shared/zen-icons/lin/paintbrush.svg | 2 + src/zen/boosts/ZenBoosts.mjs | 7 + src/zen/boosts/ZenBoostsEditor.mjs | 352 ++++++++++++++++++ src/zen/boosts/ZenBoostsManager.sys.mjs | 201 ++++++++++ src/zen/boosts/moz.build | 8 + src/zen/boosts/zen-boosts.css | 180 +++++++++ src/zen/common/styles/zen-animations.css | 12 + src/zen/common/zen-sets.js | 5 + src/zen/moz.build | 1 + src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 22 ++ src/zen/zen.globals.js | 3 + 26 files changed, 918 insertions(+), 1 deletion(-) create mode 100644 locales/en-US/browser/browser/zen-boosts.ftl create mode 100644 prefs/zen/zen-boosts.yaml create mode 100644 src/browser/base/content/zen-panels/boost-editor.inc create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/block.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/bulb.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/zap.svg create mode 100644 src/browser/themes/shared/zen-icons/lin/paintbrush.svg create mode 100644 src/zen/boosts/ZenBoosts.mjs create mode 100644 src/zen/boosts/ZenBoostsEditor.mjs create mode 100644 src/zen/boosts/ZenBoostsManager.sys.mjs create mode 100644 src/zen/boosts/moz.build create mode 100644 src/zen/boosts/zen-boosts.css diff --git a/crowdin.yml b/crowdin.yml index c2ccb1c7d5..cb0af5ec02 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -18,3 +18,5 @@ files: translation: browser/browser/preferences/zen-preferences.ftl - source: en-US/browser/browser/zen-folders.ftl translation: browser/browser/zen-folders.ftl + - source: en-US/browser/browser/zen-boosts.ftl + translation: browser/browser/zen-boosts.ftl diff --git a/locales/en-US/browser/browser/zen-boosts.ftl b/locales/en-US/browser/browser/zen-boosts.ftl new file mode 100644 index 0000000000..ea5ba791fe --- /dev/null +++ b/locales/en-US/browser/browser/zen-boosts.ftl @@ -0,0 +1,34 @@ +zen-panel-ui-boost-create = + .label = New Boost + +PanelUI-zen-boost-invert = + .tooltiptext = Smart Invert Colors +PanelUI-zen-boost-zap = + .tooltiptext = Zap Elements +PanelUI-zen-boost-disable = + .tooltiptext = Disable Color Adjustments + +PanelUI-zen-boost-font-arial = + .tooltiptext = Arial +PanelUI-zen-boost-font-serif = + .tooltiptext = Sans Serif +PanelUI-zen-boost-font-mono = + .tooltiptext = Monospace +PanelUI-zen-boost-font-georgia = + .tooltiptext = Georgia +PanelUI-zen-boost-font-comic = + .tooltiptext = Comic Sans MS +PanelUI-zen-boost-font-tahoma = + .tooltiptext = Tahoma +PanelUI-zen-boost-font-verdana = + .tooltiptext = Verdana +PanelUI-zen-boost-font-corsiva = + .tooltiptext = Corsiva + +PanelUI-zen-boost-name = + .tooltiptext = Change Boost Name +PanelUI-zen-boost-delete = + .tooltiptext = Delete Boost + +zen-panel-ui-boosts-saved-message = Successfully saved the boost! +zen-panel-ui-boosts-deleted-message = Deleted the boost! \ No newline at end of file diff --git a/prefs/zen/zen-boosts.yaml b/prefs/zen/zen-boosts.yaml new file mode 100644 index 0000000000..6476cde132 --- /dev/null +++ b/prefs/zen/zen-boosts.yaml @@ -0,0 +1,6 @@ +# 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/. + +- name: zen.boosts.enabled + value: true \ No newline at end of file diff --git a/src/browser/base/content/zen-assets.inc.xhtml b/src/browser/base/content/zen-assets.inc.xhtml index 91b136f2ec..cd3b14d36f 100644 --- a/src/browser/base/content/zen-assets.inc.xhtml +++ b/src/browser/base/content/zen-assets.inc.xhtml @@ -30,6 +30,7 @@ + # Startup "preloaded" scripts that requre globals such as gBrowser and gURLBar @@ -56,5 +57,7 @@ + + diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index fc39083763..d01e05fb4e 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -73,6 +73,9 @@ content/browser/zen-styles/zen-download-arc-animation.css (../../zen/downloads/zen-download-arc-animation.css) content/browser/zen-styles/zen-download-box-animation.css (../../zen/downloads/zen-download-box-animation.css) + content/browser/zen-components/ZenBoosts.mjs (../../zen/boosts/ZenBoosts.mjs) + content/browser/zen-components/ZenBoostsEditor.mjs (../../zen/boosts/ZenBoostsEditor.mjs) + content/browser/zen-styles/zen-boosts.css (../../zen/boosts/zen-boosts.css) # Images content/browser/zen-images/brand-header.svg (../../zen/images/brand-header.svg) diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml index 66cfe1c5bc..791c25c9c4 100644 --- a/src/browser/base/content/zen-commands.inc.xhtml +++ b/src/browser/base/content/zen-commands.inc.xhtml @@ -18,6 +18,8 @@ + + diff --git a/src/browser/base/content/zen-locales.inc.xhtml b/src/browser/base/content/zen-locales.inc.xhtml index 247fbbc518..d6c5a2d4e5 100644 --- a/src/browser/base/content/zen-locales.inc.xhtml +++ b/src/browser/base/content/zen-locales.inc.xhtml @@ -8,4 +8,5 @@ + diff --git a/src/browser/base/content/zen-panels/boost-editor.inc b/src/browser/base/content/zen-panels/boost-editor.inc new file mode 100644 index 0000000000..a63a2d94ea --- /dev/null +++ b/src/browser/base/content/zen-panels/boost-editor.inc @@ -0,0 +1,47 @@ +# 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/. + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
\ No newline at end of file diff --git a/src/browser/base/content/zen-popupset.inc.xhtml b/src/browser/base/content/zen-popupset.inc.xhtml index e58a85c4c8..d21b1ff662 100644 --- a/src/browser/base/content/zen-popupset.inc.xhtml +++ b/src/browser/base/content/zen-popupset.inc.xhtml @@ -2,6 +2,7 @@ # 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 zen-panels/boost-editor.inc #include zen-panels/gradient-generator.inc #include zen-panels/emojis-picker.inc #include zen-panels/folders-search.inc diff --git a/src/browser/themes/shared/zen-icons/common/selectable/block.svg b/src/browser/themes/shared/zen-icons/common/selectable/block.svg new file mode 100644 index 0000000000..2fc9cf026f --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/block.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg new file mode 100644 index 0000000000..09a60769e8 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg new file mode 100644 index 0000000000..46d4247596 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg new file mode 100644 index 0000000000..18368b3bb5 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 082e5b7c8f..fbf5eeba42 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -33,7 +33,8 @@ .close-icon, .zen-glance-sidebar-close, .zen-theme-picker-custom-list-item-remove, -#appMenu-quit-button2 { +#appMenu-quit-button2, +#PanelUI-zen-boost-delete { list-style-image: url('close.svg') !important; } @@ -945,3 +946,15 @@ list-style-image: url('link.svg'); fill-opacity: 0.7; } + +#PanelUI-zen-boost-zap { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/zap.svg') !important; +} + +#PanelUI-zen-boost-invert { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/bulb.svg') !important; +} + +#PanelUI-zen-boost-disable { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/block.svg') !important; +} \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index d52bde3b58..89042fbe21 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -468,12 +468,14 @@ * 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/block.svg (../shared/zen-icons/common/selectable/block.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/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) * skin/classic/browser/zen-icons/selectable/build.svg (../shared/zen-icons/common/selectable/build.svg) +* skin/classic/browser/zen-icons/selectable/bulb.svg (../shared/zen-icons/common/selectable/bulb.svg) * skin/classic/browser/zen-icons/selectable/cafe.svg (../shared/zen-icons/common/selectable/cafe.svg) * skin/classic/browser/zen-icons/selectable/call.svg (../shared/zen-icons/common/selectable/call.svg) * skin/classic/browser/zen-icons/selectable/card.svg (../shared/zen-icons/common/selectable/card.svg) @@ -519,6 +521,7 @@ * skin/classic/browser/zen-icons/selectable/navigate.svg (../shared/zen-icons/common/selectable/navigate.svg) * skin/classic/browser/zen-icons/selectable/nuclear.svg (../shared/zen-icons/common/selectable/nuclear.svg) * skin/classic/browser/zen-icons/selectable/page.svg (../shared/zen-icons/common/selectable/page.svg) +* skin/classic/browser/zen-icons/selectable/paintbrush.svg (../shared/zen-icons/common/selectable/paintbrush.svg) * skin/classic/browser/zen-icons/selectable/palette.svg (../shared/zen-icons/common/selectable/palette.svg) * skin/classic/browser/zen-icons/selectable/paw.svg (../shared/zen-icons/common/selectable/paw.svg) * skin/classic/browser/zen-icons/selectable/people.svg (../shared/zen-icons/common/selectable/people.svg) @@ -548,4 +551,5 @@ * skin/classic/browser/zen-icons/selectable/warning.svg (../shared/zen-icons/common/selectable/warning.svg) * skin/classic/browser/zen-icons/selectable/water.svg (../shared/zen-icons/common/selectable/water.svg) * skin/classic/browser/zen-icons/selectable/weight.svg (../shared/zen-icons/common/selectable/weight.svg) +* skin/classic/browser/zen-icons/selectable/zap.svg (../shared/zen-icons/common/selectable/zap.svg) skin/classic/browser/zen-icons/icons.css (../shared/zen-icons/icons.css) diff --git a/src/browser/themes/shared/zen-icons/lin/paintbrush.svg b/src/browser/themes/shared/zen-icons/lin/paintbrush.svg new file mode 100644 index 0000000000..46d4247596 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/lin/paintbrush.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/zen/boosts/ZenBoosts.mjs b/src/zen/boosts/ZenBoosts.mjs new file mode 100644 index 0000000000..a8b71edf02 --- /dev/null +++ b/src/zen/boosts/ZenBoosts.mjs @@ -0,0 +1,7 @@ +// 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/. + +ChromeUtils.defineESModuleGetters(this, { + gZenBoostsManager: "resource:///modules/ZenBoostsManager.sys.mjs", +}); \ No newline at end of file diff --git a/src/zen/boosts/ZenBoostsEditor.mjs b/src/zen/boosts/ZenBoostsEditor.mjs new file mode 100644 index 0000000000..d4e2472e7b --- /dev/null +++ b/src/zen/boosts/ZenBoostsEditor.mjs @@ -0,0 +1,352 @@ +// 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/. + +{ + class nsZenBoostEditor extends nsZenMultiWindowFeature { + initialized = false; + + constructor() { + super(); + + if (!gZenWorkspaces.shouldHaveWorkspaces || gZenWorkspaces.privateWindowOrDisabled) { + return; + } + + this.isMouseDown = false; + this.wasDragging = false; + this.lastDotSetPos = { x: 0, y: 0 }; + this.currentBoostData = null; + + this.promiseInitialized = new Promise((resolve) => { + this._resolveInitialized = resolve; + }); + + ChromeUtils.defineLazyGetter(this, 'panel', () => + document.getElementById('PanelUI-zen-boost-editor') + ); + + ChromeUtils.defineLazyGetter(this, 'toolbox', () => document.getElementById('TabsToolbar')); + + this._resolveInitialized(); + delete this._resolveInitialized; + } + + tryInitialize() { + if (this.initialized) return; + if (document.getElementById('PanelUI-zen-boost-editor')) { + this.panel.addEventListener('popupshowing', this.handlePanelOpen.bind(this)); + this.panel.addEventListener('popuphidden', this.handlePanelClose.bind(this)); + this.panel.addEventListener('command', this.handlePanelCommand.bind(this)); + + document + .getElementById('PanelUI-zen-boost-font-arial') + .addEventListener('click', (event) => this.onFontChange(event, 'Arial, sans-serif')); + document + .getElementById('PanelUI-zen-boost-font-serif') + .addEventListener('click', (event) => + this.onFontChange(event, "'Times New Roman', serif") + ); + document + .getElementById('PanelUI-zen-boost-font-mono') + .addEventListener('click', (event) => + this.onFontChange(event, "'Courier New', monospace") + ); + document + .getElementById('PanelUI-zen-boost-font-georgia') + .addEventListener('click', (event) => this.onFontChange(event, "'Georgia', serif")); + document + .getElementById('PanelUI-zen-boost-font-tahoma') + .addEventListener('click', (event) => this.onFontChange(event, 'Tahoma')); + document + .getElementById('PanelUI-zen-boost-font-verdana') + .addEventListener('click', (event) => this.onFontChange(event, 'Verdana')); + document + .getElementById('PanelUI-zen-boost-font-comic') + .addEventListener('click', (event) => this.onFontChange(event, "'Comic Sans MS'")); + document + .getElementById('PanelUI-zen-boost-font-corsiva') + .addEventListener('click', (event) => + this.onFontChange(event, "'Monotype Corsiva, cursive'") + ); + + document + .getElementById('PanelUI-zen-boost-zap') + .addEventListener('click', (event) => console.error('Not implemented')); + document + .getElementById('PanelUI-zen-boost-disable') + .addEventListener('click', this.onToggleDisable.bind(this)); + document + .getElementById('PanelUI-zen-boost-invert') + .addEventListener('click', this.onToggleInvert.bind(this)); + document + .getElementById('PanelUI-zen-boost-delete') + .addEventListener('click', this.onDeleteBoost.bind(this)); + + document + .getElementById('PanelUI-zen-boost-name') + .addEventListener('input', (e) => (this.currentBoostData.boostName = e.target.value)); + + this.initialized = true; + } else { + console.error('Panel element PanelUI-zen-boost-editor not found'); + return; + } + } + + initColorPicker() { + const themePicker = this.panel.querySelector('.zen-boost-color-picker-gradient'); + this._onMouseMove = this.onMouseMove.bind(this); + this._onMouseUp = this.onMouseUp.bind(this); + this._onMouseDown = this.onMouseDown.bind(this); + this._onThemePickerClick = this.onThemePickerClick.bind(this); + document.addEventListener('mousemove', this._onMouseMove); + document.addEventListener('mouseup', this._onMouseUp); + themePicker.addEventListener('mousedown', this._onMouseDown); + themePicker.addEventListener('click', this._onThemePickerClick); + } + + uninitColorPicker() { + const themePicker = this.panel.querySelector('.zen-boost-color-picker-gradient'); + document.removeEventListener('mousemove', this._onMouseMove); + document.removeEventListener('mouseup', this._onMouseUp); + themePicker.removeEventListener('mousedown', this._onMouseDown); + themePicker.removeEventListener('click', this._onThemePickerClick); + this._onThemePickerClick = null; + this._onMouseMove = null; + this._onMouseUp = null; + this._onMouseDown = null; + } + + onMouseMove(event) { + if (this.isMouseDown) { + this.wasDragging = true; + event.preventDefault(); + this.setDotPos(event.clientX, event.clientY, false); + } + } + + onMouseDown(event) { + if (event.button === 2) { + return; + } + + this.isMouseDown = true; + } + + onMouseUp(event) { + if (event.button === 2) { + return; + } + + this.isMouseDown = false; + this.wasDragging = false; + } + + resetDotPosition() { + this.setDotPos(null, null); + } + + onThemePickerClick(event) { + event.preventDefault(); + + this.setDotPos(event.clientX, event.clientY, !this.wasDragging); + this.wasDragging = false; + } + + // Sets the position of the dot + setDotPos(pixelX, pixelY, animate = true) { + const gradient = this.panel.querySelector('.zen-boost-color-picker-gradient'); + const dot = this.panel.querySelector('.zen-boost-color-picker-dot'); + + const rect = gradient.getBoundingClientRect(); + const padding = 40; + + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + const radius = (rect.width - padding) / 2; + + if (!animate) { + let nDistance = Math.sqrt( + (pixelX - this.lastDotSetPos.x) ** 2 + (pixelY - this.lastDotSetPos.y) ** 2 + ); + + if (nDistance > 15) { + // Optional haptic feedback + // Services.zen.playHapticFeedback(); + + this.lastDotSetPos = { + x: pixelX, + y: pixelY, + }; + } + } + + if (pixelX == null || pixelY == null) { + pixelX = centerX; + pixelY = centerY; + + this.currentBoostData.dotAngleDeg = 0; + this.currentBoostData.dotDistance = 0; + this.currentBoostData.dotAngleRad = 0; + } else { + let distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); + distance = Math.min(distance, radius); // Clamp distance + + const angle = Math.atan2(pixelY - centerY, pixelX - centerX); + + pixelX = centerX + Math.cos(angle) * distance; + pixelY = centerY + Math.sin(angle) * distance; + + // Rad to degree + this.currentBoostData.dotAngleDeg = + ((Math.atan2(pixelY - centerY, pixelX - centerX) * 180) / Math.PI + 100) % 360; + if (this.currentBoostData.dotAngleDeg < 0) this.currentBoostData.dotAngleDeg += 360; + + // Map to 0-1 range + this.currentBoostData.dotDistance = distance / radius; + } + + const relativeX = pixelX - rect.left; + const relativeY = pixelY - rect.top; + + // Capture position of dot for restoring it correctly later + this.currentBoostData.dotPos.x = relativeX; + this.currentBoostData.dotPos.y = relativeY; + + if (animate) { + gZenUIManager.motion.animate( + dot, + { + left: `${relativeX}px`, + top: `${relativeY}px`, + }, + { + duration: 0.4, + type: 'spring', + bounce: 0.3, + } + ); + } else { + dot.style.left = `${relativeX}px`; + dot.style.top = `${relativeY}px`; + } + + // Enable color boosting again + if(!this.currentBoostData.enableColorBoost) + this.onToggleDisable(null); + + this.updateDot(); + this.updateCurrentBoost(); + } + + updateDot() { + const dot = this.panel.querySelector('.zen-boost-color-picker-dot'); + dot.style.setProperty( + '--zen-theme-picker-dot-color', + `hsl(${this.currentBoostData.dotAngleDeg}deg, ${this.currentBoostData.dotDistance * 100}%, 55%)` + ); + } + + // This toggles the color changes + onToggleDisable(event) { + this.currentBoostData.enableColorBoost = !this.currentBoostData.enableColorBoost; + + this.updateButtonToggleVisuals(); + this.updateCurrentBoost(); + } + + onToggleInvert(event) { + this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; + + this.updateButtonToggleVisuals(); + this.updateCurrentBoost(); + } + + updateButtonToggleVisuals() { + const invertButton = document.getElementById('PanelUI-zen-boost-invert'); + const disableButton = document.getElementById('PanelUI-zen-boost-disable'); + const gradient = this.panel.querySelector('.zen-boost-color-picker-gradient'); + + if (this.currentBoostData.smartInvert) invertButton.classList.add('zen-boost-button-active'); + else invertButton.classList.remove('zen-boost-button-active'); + + if (!this.currentBoostData.enableColorBoost) + disableButton.classList.add('zen-boost-button-active'); + else disableButton.classList.remove('zen-boost-button-active'); + + if (!this.currentBoostData.enableColorBoost) + gradient.classList.add('zen-boost-panel-disabled'); + else gradient.classList.remove('zen-boost-panel-disabled'); + } + + onFontChange(event, fontFamily) { + if (this.currentBoostData.fontFamily == fontFamily) this.currentBoostData.fontFamily = ''; + else this.currentBoostData.fontFamily = fontFamily; + + this.updateCurrentBoost(); + } + + updateCurrentBoost() { + window.gZenBoostsManager.updateBoost(this.currentBoostData); + } + + onDeleteBoost() { + window.gZenBoostsManager.deleteBoost(this.currentBoostData.domain); + this.currentBoostData = null; + + PanelMultiView.hidePopup(this.panel); + + // Still write modifications to disk + window.gZenBoostsManager.saveBoostToStore(null); + gZenUIManager.showToast('zen-panel-ui-boosts-deleted-message'); + } + + openEditor(event, domain) { + this.tryInitialize(); + this.loadBoost(domain); + + PanelMultiView.openPopup(this.panel, this.toolbox, { + position: 'topright topleft', + triggerEvent: event, + y: 0, + }); + } + + handlePanelClose() { + this.uninitColorPicker(); + if(this.currentBoostData != null) + this.saveBoost(); + } + + handlePanelOpen() { + this.initColorPicker(); + } + + loadBoost(domain) { + const dot = this.panel.querySelector('.zen-boost-color-picker-dot'); + this.currentBoostData = window.gZenBoostsManager.loadBoostFromStore(domain); + + document.getElementById('PanelUI-zen-boost-name').value = this.currentBoostData.boostName; + + // TODO: This doesn't work, it should center the dot + if(this.currentBoostData.dotPos.x == null || this.currentBoostData.dotPos.y == null) this.resetDotPosition(); + else { + dot.style.left = `${this.currentBoostData.dotPos.x}px`; + dot.style.top = `${this.currentBoostData.dotPos.y}px`; + } + + this.updateDot(); + this.updateButtonToggleVisuals(); + } + + saveBoost() { + window.gZenBoostsManager.saveBoostToStore(this.currentBoostData); + gZenUIManager.showToast('zen-panel-ui-boosts-saved-message'); + } + + handlePanelCommand(event) {} + } + + window.gZenBoostsEditor = new nsZenBoostEditor(); +} diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs new file mode 100644 index 0000000000..1b4624111a --- /dev/null +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -0,0 +1,201 @@ +// 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/. + +export class nsZenBoostsManager { + + initialized = false; + registeredSheets = new Map(); + registeredBoosts = new Map(); + + saveFilename = 'zen-boosts.json'; + + constructor() { + this.init(); + } + + init() { + this.readBoostsFromStore(() => initialized = true); + } + + // Store Firefox Style Sheet Service and IO Service for later use + sss = Components.classes['@mozilla.org/content/style-sheet-service;1'].getService( + Components.interfaces.nsIStyleSheetService + ); + ioService = Services.io; + + // TODO: Causes flickering when updating often + registerCSSForDomain(cssString, domain, sheetType = this.sss.USER_SHEET) { + // Make sure existing overrides get overwritten and not added on top + if (this.registeredSheets.has(domain)) { + this.unregisterSheet(this.registeredSheets.get(domain), sheetType); + } + + // Add the @-moz-document wrapper and specific domain attribute + const wrapped = `@-moz-document domain("${domain}") { ${cssString} }`; + const uri = this.ioService.newURI('data:text/css;charset=utf-8,' + encodeURIComponent(wrapped)); + + // Register and store in map + this.sss.loadAndRegisterSheet(uri, sheetType); + this.registeredSheets.set(domain, uri); + + return uri; + } + + // Unregisters a sheet based on either domain or uri + unregisterSheet(uriOrDomain, sheetType = this.sss.USER_SHEET) { + let uri = uriOrDomain; + + // Check if a uri or domain + if (typeof uriOrDomain === 'string' && this.registeredSheets.has(uriOrDomain)) { + uri = this.registeredSheets.get(uriOrDomain); + this.registeredSheets.delete(uriOrDomain); + } + + if (this.sss.sheetRegistered(uri, sheetType)) { + this.sss.unregisterSheet(uri, sheetType); + } + } + + deleteBoost(domain) { + if(this.registeredBoosts.has(domain)) { + this.unregisterSheet(domain); + this.registeredBoosts.delete(domain); + } + } + + // Load a boost from a domain + loadBoostFromStore(domain) { + if (domain == null) console.error('[ZenBoostsManager] Domain expected but got null.'); + const dom = domain ?? ''; + + let boostData = { + boostName: 'New Boost', + domain: dom, + dotAngleDeg: 0, + dotPos: { x: null, y: null }, + dotDistance: 0, + fontFamily: '', + enableColorBoost: false, + smartInvert: false, + }; + + if (this.registeredBoosts.has(dom)) { + boostData = this.registeredBoosts.get(dom); + // console.log('Boost found for domain ', dom, boostData); + } + // else + // console.log('Boost not found'); + + return boostData; + } + + // Injects css based on boost data + updateBoost(boost) { + let fontFamily = ''; + if (boost.fontFamily != '') { + fontFamily = ` + body, p, h1, h2, h3, h4, h5, a, span, textarea, input { + font-family: ${boost.fontFamily} !important; + } + `; + } + + const invert = boost.smartInvert ? 'invert()' : ''; + + let colorFilters = ` + html { + filter: ${invert} !important; + } + + img, video, picture { + filter: ${invert} !important; + } + `; + + // TODO: Temporary color theming using mix-blend-mode + if (boost.enableColorBoost) { + colorFilters += ` + html, body { position: relative; } + body::before { + content: ""; + position: fixed; /* cover everything */ + pointer-events: none; /* don't block interactions */ + top: 0; left: 0; right: 0; bottom: 0; + + background-color: hsl(${boost.dotAngleDeg - (boost.smartInvert ? 180 : 0)}deg, ${boost.dotDistance * 100}%, ${10 + boost.dotDistance * 60}%); + + mix-blend-mode: color; + z-index: 1000; + } + body img, + body video, + body canvas, + body picture { + position: relative; + isolation: isolate; + z-index: 1001; /* put images above the overlay */ + } + + `; + } + + this.registerCSSForDomain(colorFilters + ' ' + fontFamily, boost.domain); + } + + // Save all boosts to the profile folder + saveBoostToStore(boostData) { + if(boostData != null) + this.registeredBoosts.set(boostData.domain, boostData); + + (async () => this.writeToDisk(this.registeredBoosts))(); + } + + // Reads all boosts from the profile folder + readBoostsFromStore(done) { + this.readFromDisk() + .then((map) => { + this.registeredBoosts = map; + + // Load in all boosts + for (const [key, value] of this.registeredBoosts) { + this.updateBoost(value); + } + + done(); + }); + } + + // Helper method, disk => json => map + async readFromDisk() { + const profilePath = PathUtils.profileDir; + const savePath = PathUtils.join(profilePath, this.saveFilename); + + if (!(await IOUtils.exists(savePath))) return new Map(); + + const data = await IOUtils.read(savePath); + const decoder = new TextDecoder(); + const json = decoder.decode(data); + + return new Map(JSON.parse(json)); + } + + // Helper method, map => json => disk + async writeToDisk(map) { + const encoder = new TextEncoder(); + const json = JSON.stringify([...map]); + const data = encoder.encode(json); + + const profilePath = PathUtils.profileDir; + const savePath = PathUtils.join(profilePath, this.saveFilename); + + await IOUtils.write(savePath, new Uint8Array(data)); + } + + // Checks if there is a boost registered for the currently open tab + registeredBoostForDomain(domain) { + return this.registeredSheets.has(domain); + } +} + +export const gZenBoostsManager = new nsZenBoostsManager(); diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build new file mode 100644 index 0000000000..0fa56f4cda --- /dev/null +++ b/src/zen/boosts/moz.build @@ -0,0 +1,8 @@ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXTRA_JS_MODULES += [ + "ZenBoostsManager.sys.mjs", +] \ No newline at end of file diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css new file mode 100644 index 0000000000..2e78cb4bc1 --- /dev/null +++ b/src/zen/boosts/zen-boosts.css @@ -0,0 +1,180 @@ +/* + * 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/. + */ + +#PanelUI-zen-boost-font-arial { + font-family: Arial, sans-serif; +} + +#PanelUI-zen-boost-font-serif { + font-family: serif; +} + +#PanelUI-zen-boost-font-mono { + font-family: monospace, monospace; +} + +#PanelUI-zen-boost-editor-view { + gap: 10px; +} + +#PanelUI-zen-boost-filter-wrapper { + width: 135px; + + & input { + width: auto; + } + + & button { + padding: 0 !important; + min-width: fit-content !important; + + border: solid 6px transparent !important; + width: 26px; + height: 26px; + + transition: background 0.2s; + appearance: none; + color: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.9)); + + & .button-text { + display: none; + } + + &:hover { + background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); + } + + &[disabled] { + opacity: 0.5; + cursor: not-allowed; + } + } +} + +#PanelUI-zen-boost-delete { + width: auto; +} + +#PanelUI-zen-boost-zap { + opacity: 0.5 !important; + cursor: not-allowed !important; + pointer-events: none !important; +} + +#PanelUI-zen-boost-toolbar-wrapper{ + width: auto; + margin: auto; + gap: 1.15em; + + & button { + width: auto; + height: auto !important; + + margin: auto; + } +} + +.zen-boost-panel-disabled { + filter: grayscale(1); +} + +#PanelUI-zen-boost-filter-wrapper separator { + height: 5px !important; +} + +#PanelUI-zen-boost-toolbar-wrapper button:hover{ + background-color: #FFFFFF11; + opacity: 0.9; +} + +.zen-boost-button-active { + background-color: white; + color: black !important; +} + +.zen-boost-button-active:hover { + background-color: white !important; +} + +.zen-boost-color-picker-gradient::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + + border: 5px solid hsl(100 100% 60%); + border-image-slice: 1; + border-image-source: conic-gradient( + rgba(255, 0, 0, 1) 0%, + rgba(255, 162, 0, 1) 10%, + rgba(255, 242, 0, 1) 20%, + rgba(89, 255, 0, 1) 30%, + rgba(0, 255, 128, 1) 40%, + rgba(0, 255, 247, 1) 50%, + rgba(0, 89, 255, 1) 60%, + rgba(8, 0, 255, 1) 70%, + rgba(204, 0, 255, 1) 80%, + rgba(255, 0, 144, 1) 90%, + rgba(255, 0, 8, 1) 100% + ); + + pointer-events: none; +} + +.zen-boost-color-picker-gradient { + position: relative; + overflow: hidden; + border-radius: calc(var(--panel-border-radius) - 4px); + + width: auto; + height: 135px; + + margin: 10px 0px 10px 0px; + + min-height: calc(var(--panel-width) - var(--panel-padding) * 2 - 2px); + background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.03)); + background-image: radial-gradient( + light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.1)) 1px, + transparent 0 + ); + background-position: -23px -23px; + background-size: 6px 6px; + + & .zen-boost-color-picker-dot { + position: absolute; + z-index: 2; + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--zen-theme-picker-dot-color); + @media (-prefers-color-scheme: dark) { + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); + } + cursor: pointer; + border: 3px solid #ffffff; + animation: zen-boost-color-picker-dot-animation 0.5s; + transform: translate(-50%, -50%); + pointer-events: none; + transform-origin: center center; + + &:first-of-type { + width: 26px; + height: 26px; + border-width: 3px; + z-index: 2; + pointer-events: all; + transition: transform 0.2s; + z-index: 999; + &:hover { + transform: scale(1.05) translate(-50%, -50%); + } + } + + &[dragging='true'] { + transform: scale(1.2) translate(-50%, -50%) !important; + } + } +} diff --git a/src/zen/common/styles/zen-animations.css b/src/zen/common/styles/zen-animations.css index ae77e95021..14167ad830 100644 --- a/src/zen/common/styles/zen-animations.css +++ b/src/zen/common/styles/zen-animations.css @@ -28,6 +28,18 @@ } } +@keyframes zen-boost-color-picker-dot-animation { + from { + transform: scale(0.8) translate(-50%, -50%); + } + 50% { + transform: scale(1.2) translate(-50%, -50%); + } + to { + transform: scale(1) translate(-50%, -50%); + } +} + /* Mark: Zen Glance */ @keyframes zen-glance-overlay-animation { from { diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js index b7b6c00ad6..f8d0c583f0 100644 --- a/src/zen/common/zen-sets.js +++ b/src/zen/common/zen-sets.js @@ -23,6 +23,11 @@ document.addEventListener( case 'cmd_zenWorkspaceForward': gZenWorkspaces.changeWorkspaceShortcut(); break; + case 'cmd_zenOpenBoostEditor': + const currentTab = gBrowser.selectedTab; + const domain = new URL(currentTab.linkedBrowser.currentURI.spec).hostname; + gZenBoostsEditor.openEditor(event, domain); + break; case 'cmd_zenWorkspaceBackward': gZenWorkspaces.changeWorkspaceShortcut(-1); break; diff --git a/src/zen/moz.build b/src/zen/moz.build index 56782122f5..58eb17ad7c 100644 --- a/src/zen/moz.build +++ b/src/zen/moz.build @@ -9,4 +9,5 @@ DIRS += [ "tests", "urlbar", "toolkit", + "boosts", ] diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 5f09607d41..38ad5b5401 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -2,6 +2,8 @@ * 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/. */ +import { gZenBoostsManager } from "resource:///modules/ZenBoostsManager.sys.mjs"; + function isNotEmptyTab(window) { return !window.gBrowser.selectedTab.hasAttribute('zen-empty-tab'); } @@ -22,6 +24,26 @@ const globalActionsTemplate = [ command: 'cmd_zenNewEmptySplit', icon: 'chrome://browser/skin/zen-icons/split.svg', }, + { + label: 'Create New Boost', + command: 'cmd_zenOpenBoostEditor', + icon: 'chrome://browser/skin/zen-icons/selectable/paintbrush.svg', + isAvailable: (window) => { + const tab = window.gBrowser.selectedTab; + const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + return !tab.hasAttribute('zen-empty-tab') && !gZenBoostsManager.registeredBoostForDomain(domain); + }, + }, + { + label: 'Edit Boost', + command: 'cmd_zenOpenBoostEditor', + icon: 'chrome://browser/skin/zen-icons/selectable/paintbrush.svg', + isAvailable: (window) => { + const tab = window.gBrowser.selectedTab; + const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + return !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain); + }, + }, { label: 'New Folder', command: 'cmd_zenOpenFolderCreation', diff --git a/src/zen/zen.globals.js b/src/zen/zen.globals.js index ce22cd4a8b..fc964de0c9 100644 --- a/src/zen/zen.globals.js +++ b/src/zen/zen.globals.js @@ -36,6 +36,9 @@ export default [ 'gZenMediaController', 'gZenGlanceManager', + 'gZenBoostsManager', + 'gZenBoostsEditor', + 'nsZenThemePicker', 'gZenThemePicker', From f799d004ee95fd8bbb8acf4c594db914e013168d Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Thu, 30 Oct 2025 00:32:40 +0100 Subject: [PATCH 02/33] feat: Add boosts backend boilerplate, b=no-bug, c=no-component --- src/zen/boosts/components.conf | 14 +++++++++ src/zen/boosts/moz.build | 21 ++++++++++++- src/zen/boosts/nsIZenBoostsBackend.idl | 17 +++++++++++ src/zen/boosts/nsZenBoostsBackend.cpp | 20 ++++++++++++ src/zen/boosts/nsZenBoostsBackend.h | 42 ++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/zen/boosts/components.conf create mode 100644 src/zen/boosts/nsIZenBoostsBackend.idl create mode 100644 src/zen/boosts/nsZenBoostsBackend.cpp create mode 100644 src/zen/boosts/nsZenBoostsBackend.h diff --git a/src/zen/boosts/components.conf b/src/zen/boosts/components.conf new file mode 100644 index 0000000000..827787a7f1 --- /dev/null +++ b/src/zen/boosts/components.conf @@ -0,0 +1,14 @@ +# 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/. + +Classes = [ + { + 'cid': '{7d46aa4e-7486-4f77-ab47-81125f1a5723}', + 'interfaces': ['nsIZenBoostsBackend'], + 'contract_ids': ['@mozilla.org/zen/boosts-backend;1'], + 'type': 'zen::nsZenBoostsBackend', + 'headers': ['mozilla/nsZenBoostsBackend.h'], + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index 0fa56f4cda..8bef94eaf7 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -5,4 +5,23 @@ EXTRA_JS_MODULES += [ "ZenBoostsManager.sys.mjs", -] \ No newline at end of file +] + +XPIDL_SOURCES += [ + "nsIZenBoostsBackend.idl", +] + +EXPORTS.mozilla += [ + "nsZenBoostsBackend.h", +] + +SOURCES += [ + "nsZenBoostsBackend.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +FINAL_LIBRARY = "xul" +XPIDL_MODULE = "zen_boosts" diff --git a/src/zen/boosts/nsIZenBoostsBackend.idl b/src/zen/boosts/nsIZenBoostsBackend.idl new file mode 100644 index 0000000000..6f047bb77a --- /dev/null +++ b/src/zen/boosts/nsIZenBoostsBackend.idl @@ -0,0 +1,17 @@ +/* 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 "nsISupports.idl" +#include "nsIURI.idl" + +%{C++ +#include "nsColor.h" +%} + +/** + * @brief Interface for Zen boosts backend. + */ +[scriptable, uuid(7d46aa4e-7486-4f77-ab47-81125f1a5723)] +interface nsIZenBoostsBackend : nsISupports { +}; diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp new file mode 100644 index 0000000000..8eaa0560b9 --- /dev/null +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -0,0 +1,20 @@ +/* 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 "nsZenBoostsBackend.h" + +#include "nsIXULRuntime.h" +#include "mozilla/PresShell.h" +#include "nsPresContext.h" + +namespace zen { + +// Use the macro to inject all of the definitions for nsISupports. +NS_IMPL_ISUPPORTS(nsZenBoostsBackend, nsIZenBoostsBackend) + +nsZenBoostsBackend::nsZenBoostsBackend() {}; + +nsPresContext* nsZenBoostsBackend::mCurrentPresContext = nullptr; + +} // namespace zen diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h new file mode 100644 index 0000000000..49a7a8c91d --- /dev/null +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -0,0 +1,42 @@ +/* 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/. */ + +#ifndef mozilla_ZenBoostsBackend_h__ +#define mozilla_ZenBoostsBackend_h__ + +#include "nsIZenBoostsBackend.h" +#include "nsPresContext.h" +#include "mozilla/PresShell.h" + +namespace zen { + +class nsZenBoostsBackend final : public nsIZenBoostsBackend { + NS_DECL_ISUPPORTS + NS_DECL_NSIZENBOOSTSBACKEND + + public: + explicit nsZenBoostsBackend(); + + /* + * @brief Called when the presshell is entered. + */ + auto onPressShellEntered(mozilla::PresShell* aPresShell) -> void; + + /* + * @brief Called when the presshell is exited. + */ + auto onPressShellExited(mozilla::PresShell* aPresShell) -> void; + + private: + ~nsZenBoostsBackend() = default; + + /** + * The presshell of the current document being rendered. + */ + static nsPresContext* mCurrentPresContext; +}; + +} // namespace zen + +#endif From 4ee1235717c6565df1fa70dfb9910124690d57e3 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Thu, 30 Oct 2025 18:10:52 +0100 Subject: [PATCH 03/33] feat: Add changes to firefox engine, b=no-bug, c=no-component --- src/layout/base/nsPresContext-cpp.patch | 25 +++++ src/layout/base/nsPresContext-h.patch | 20 ++++ src/layout/painting/nsDisplayList-cpp.patch | 41 ++++++++ src/layout/style/StyleColor-cpp.patch | 30 ++++++ src/zen/boosts/ZenBoostsPresContext.h | 27 ++++++ src/zen/boosts/components.conf | 2 +- src/zen/boosts/moz.build | 2 +- src/zen/boosts/nsZenBoostsBackend.cpp | 102 +++++++++++++++++++- src/zen/boosts/nsZenBoostsBackend.h | 32 ++++-- 9 files changed, 271 insertions(+), 10 deletions(-) create mode 100644 src/layout/base/nsPresContext-cpp.patch create mode 100644 src/layout/base/nsPresContext-h.patch create mode 100644 src/layout/painting/nsDisplayList-cpp.patch create mode 100644 src/layout/style/StyleColor-cpp.patch create mode 100644 src/zen/boosts/ZenBoostsPresContext.h diff --git a/src/layout/base/nsPresContext-cpp.patch b/src/layout/base/nsPresContext-cpp.patch new file mode 100644 index 0000000000..601436e305 --- /dev/null +++ b/src/layout/base/nsPresContext-cpp.patch @@ -0,0 +1,25 @@ +diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp +index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..f83e4ecb6379a3b12edcef152affa7030aa5f9aa 100644 +--- a/layout/base/nsPresContext.cpp ++++ b/layout/base/nsPresContext.cpp +@@ -20,6 +20,7 @@ + #include "base/basictypes.h" + #include "gfxPlatform.h" + #include "gfxTextRun.h" ++#include "mozilla/nsZenBoostsBackend.h" + #include "mozilla/AnimationEventDispatcher.h" + #include "mozilla/ContentBlockingAllowList.h" + #include "mozilla/CycleCollectedJSContext.h" +@@ -914,6 +915,12 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { + SetOverrideDPPX(browsingContext->OverrideDPPX()); + } + ++ nsCOMPtr zenBackend( ++ do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); ++ if (zenBackend) { ++ zenBackend->RecomputeBrowsingContextDependentData(this); ++ } ++ + auto* top = browsingContext->Top(); + SetColorSchemeOverride([&] { + auto overriden = top->PrefersColorSchemeOverride(); diff --git a/src/layout/base/nsPresContext-h.patch b/src/layout/base/nsPresContext-h.patch new file mode 100644 index 0000000000..e2238ca423 --- /dev/null +++ b/src/layout/base/nsPresContext-h.patch @@ -0,0 +1,20 @@ +diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h +index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..7328d1ce3a26955b10e8789d0f46dfeba2281c82 100644 +--- a/layout/base/nsPresContext.h ++++ b/layout/base/nsPresContext.h +@@ -45,6 +45,7 @@ + #include "nsTHashSet.h" + #include "nsTHashtable.h" + #include "nsThreadUtils.h" ++#include "mozilla/ZenBoostsPresContext.h" + + class nsIPrintSettings; + class nsDocShell; +@@ -560,6 +561,7 @@ class nsPresContext : public nsISupports, + void UpdateForcedColors(bool aNotify = true); + + public: ++ nsCOMPtr mZenBoostsPresContext; + float GetFullZoom() { return mFullZoom; } + /** + * Device full zoom differs from full zoom because it gets the zoom from diff --git a/src/layout/painting/nsDisplayList-cpp.patch b/src/layout/painting/nsDisplayList-cpp.patch new file mode 100644 index 0000000000..9f2fc332a6 --- /dev/null +++ b/src/layout/painting/nsDisplayList-cpp.patch @@ -0,0 +1,41 @@ +diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp +index 6c5b38eb3cdb08a8afd670705c75c052b85b6dd9..823df9ef5245dcffcbfaba1fafc6b15ece174676 100644 +--- a/layout/painting/nsDisplayList.cpp ++++ b/layout/painting/nsDisplayList.cpp +@@ -85,6 +85,7 @@ + #include "mozilla/layers/WebRenderLayerManager.h" + #include "mozilla/layers/WebRenderMessages.h" + #include "mozilla/layers/WebRenderScrollData.h" ++#include "mozilla/nsZenBoostsBackend.h" + #include "nsCSSProps.h" + #include "nsCSSRendering.h" + #include "nsCSSRenderingGradients.h" +@@ -1046,6 +1047,12 @@ void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame, + state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length(); + state->mFirstFrameWithOOFData = mFramesWithOOFData.Length(); + ++ nsCOMPtr zenBackend( ++ do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); ++ if (zenBackend) { ++ zenBackend->onPressShellEntered(state->mPresShell->GetPresContext()); ++ } ++ + ScrollContainerFrame* sf = state->mPresShell->GetRootScrollContainerFrame(); + if (sf && IsInSubdocument()) { + // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure +@@ -1221,6 +1228,15 @@ void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame, + ResetMarkedFramesForDisplayList(aReferenceFrame); + mPresShellStates.RemoveLastElement(); + ++ nsCOMPtr zenBackend( ++ do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); ++ if (zenBackend) { ++ zenBackend->onPressShellExited( ++ mPresShellStates.IsEmpty() ++ ? nullptr ++ : CurrentPresShellState()->mPresShell->GetPresContext()); ++ } ++ + if (!mPresShellStates.IsEmpty()) { + nsPresContext* pc = CurrentPresContext(); + nsIDocShell* docShell = pc->GetDocShell(); diff --git a/src/layout/style/StyleColor-cpp.patch b/src/layout/style/StyleColor-cpp.patch new file mode 100644 index 0000000000..461e9f0835 --- /dev/null +++ b/src/layout/style/StyleColor-cpp.patch @@ -0,0 +1,30 @@ +diff --git a/layout/style/StyleColor.cpp b/layout/style/StyleColor.cpp +index 5fd5f7efba9bcb2febdc9a6b8f8812df673513d8..200e9bf82aacbf38a50f5b99671e403de5b4f61b 100644 +--- a/layout/style/StyleColor.cpp ++++ b/layout/style/StyleColor.cpp +@@ -10,6 +10,7 @@ + #include "mozilla/dom/BindingDeclarations.h" + #include "nsIFrame.h" + #include "nsStyleStruct.h" ++#include "mozilla/nsZenBoostsBackend.h" + + namespace mozilla { + +@@ -23,6 +24,8 @@ bool StyleColor::MaybeTransparent() const { + template <> + StyleAbsoluteColor StyleColor::ResolveColor( + const StyleAbsoluteColor& aForegroundColor) const { ++ auto ResolveColorInner = [this, ++ &aForegroundColor]() -> StyleAbsoluteColor { + if (IsAbsolute()) { + return AsAbsolute(); + } +@@ -32,6 +35,8 @@ StyleAbsoluteColor StyleColor::ResolveColor( + } + + return Servo_ResolveColor(this, &aForegroundColor); ++ }; ++ return zen::nsZenBoostsBackend::ResolveStyleColor(ResolveColorInner()); + } + + template <> diff --git a/src/zen/boosts/ZenBoostsPresContext.h b/src/zen/boosts/ZenBoostsPresContext.h new file mode 100644 index 0000000000..24cac4a92a --- /dev/null +++ b/src/zen/boosts/ZenBoostsPresContext.h @@ -0,0 +1,27 @@ +/* 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/. */ + +#ifndef mozilla_ZenBoostsPressContext_h__ +#define mozilla_ZenBoostsPressContext_h__ + +#include "nsColor.h" +#include "nsISupports.h" + +namespace mozilla { + +class ZenBoostsPresContext : public nsISupports { + ~ZenBoostsPresContext(); + + public: + NS_DECL_ISUPPORTS + + nscolor mAccentColor; + + explicit ZenBoostsPresContext(nscolor aAccentColor) + : mAccentColor(aAccentColor) {} +}; + +} // namespace mozilla + +#endif // mozilla_ZenBoostsPressContext_h__ diff --git a/src/zen/boosts/components.conf b/src/zen/boosts/components.conf index 827787a7f1..57a2f20378 100644 --- a/src/zen/boosts/components.conf +++ b/src/zen/boosts/components.conf @@ -9,6 +9,6 @@ Classes = [ 'contract_ids': ['@mozilla.org/zen/boosts-backend;1'], 'type': 'zen::nsZenBoostsBackend', 'headers': ['mozilla/nsZenBoostsBackend.h'], - 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + 'processes': ProcessSelector.CONTENT_PROCESS_ONLY, }, ] diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index 8bef94eaf7..ebfbb6f73f 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -1,4 +1,3 @@ -# # 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/. @@ -13,6 +12,7 @@ XPIDL_SOURCES += [ EXPORTS.mozilla += [ "nsZenBoostsBackend.h", + "ZenBoostsPresContext.h", ] SOURCES += [ diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 8eaa0560b9..c26b15034b 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -5,16 +5,114 @@ #include "nsZenBoostsBackend.h" #include "nsIXULRuntime.h" -#include "mozilla/PresShell.h" #include "nsPresContext.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" + +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/ServoStyleConstsInlines.h" + +#include "mozilla/dom/Document.h" + namespace zen { +namespace { + +// llvm x86 is poor at ternary operator, so use branchless min/max. +static __inline int32_t clamp255(int32_t v) { + return (((255 - (v)) >> 31) | (v)) & 255; +} + +} + // Use the macro to inject all of the definitions for nsISupports. NS_IMPL_ISUPPORTS(nsZenBoostsBackend, nsIZenBoostsBackend) +NS_IMPL_ISUPPORTS(ZenBoostsPresContextData, nsISupports) nsZenBoostsBackend::nsZenBoostsBackend() {}; -nsPresContext* nsZenBoostsBackend::mCurrentPresContext = nullptr; +auto nsZenBoostsBackend::onPressShellEntered(nsPresContext* aPresContext) -> void { + if (!aPresContext) { + return; + } + + mCurrentPresContext = aPresContext; +} + +auto nsZenBoostsBackend::onPressShellLeave(nsPresContext* aPresContext) -> void { + // TODO: We should set it as a null as well, but this prevents borders and shadows + // from being drawn into our Zen boosts modifications. + if (!aPresContext) { + return; + } + mCurrentPresContext = aPresContext; +} + +auto nsZenBoostsBackend::RecomputeBrowsingContextDependentData( + nsPresContext* aPresContext) -> void { + if (!aPresContext) { + return; + } + + auto document = aPresContext->Document(); + if (!document) { + return; + } + + // Create the Zen boosts data if it doesn't exist yet. + // TODO: Actually check on the document's href or other properties to + // determine what data to store. + aPresContext->mZenBoostsPresContextData = + new ZenBoostsPresContextData(NS_RGBA(111, 78, 55, 1)); +} + +auto nsZenBoostsBackend::ResolveStyleColor( + mozilla::StyleAbsoluteColor aColor) -> mozilla::StyleAbsoluteColor { + static nsCOMPtr zenBoosts( + do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); + + if (zenBoosts) { + if (auto presContext = zenBoosts->mCurrentPresContext) { + if (auto data = presContext->mZenBoostsPresContextData) { + // Apply a filter-like tint: + // - Preserve the original color's perceived luminance + // - Map hue/chroma toward the accent by scaling the accent's RGB + // to match the original luminance + // - Keep the original alpha + + // Convert both colors to nscolor to access channels + nscolor originalNS = aColor.ToColor(); + nscolor accentNS = data->mAccentColor; + + auto r1 = NS_GET_R(originalNS); + auto g1 = NS_GET_G(originalNS); + auto b1 = NS_GET_B(originalNS); + + auto r2 = NS_GET_R(accentNS); + auto g2 = NS_GET_G(accentNS); + auto b2 = NS_GET_B(accentNS); + + // Approximate perceived luminance in sRGB space + // Coefficients per Rec.709; gamma correction ignored for speed + double origLum = 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1; + double accentLum = 0.2126 * r2 + 0.7152 * g2 + 0.0722 * b2; + + double scale = accentLum > 0.0 ? (origLum / accentLum) : 1.0; + + uint8_t fr = clamp255(r2 * scale); + uint8_t fg = clamp255(g2 * scale); + uint8_t fb = clamp255(b2 * scale); + + nscolor filteredNS = NS_RGBA(fr, fg, fb, 1); + auto filtered = mozilla::StyleAbsoluteColor::FromColor(filteredNS); + filtered.alpha = aColor.alpha; + return filtered; + } + } + } + + return aColor; +} } // namespace zen diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h index 49a7a8c91d..f338aa682d 100644 --- a/src/zen/boosts/nsZenBoostsBackend.h +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -5,9 +5,15 @@ #ifndef mozilla_ZenBoostsBackend_h__ #define mozilla_ZenBoostsBackend_h__ -#include "nsIZenBoostsBackend.h" +#include "nsColor.h" #include "nsPresContext.h" -#include "mozilla/PresShell.h" +#include "nsIZenBoostsBackend.h" +#include "ZenBoostsPresContext.h" + +#include "mozilla/RefPtr.h" + +#define ZEN_BOOSTS_BACKEND_CONTRACTID \ + "@mozilla.org/zen/boosts-backend;1" namespace zen { @@ -19,14 +25,28 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { explicit nsZenBoostsBackend(); /* - * @brief Called when the presshell is entered. + * @brief Called when the presshell is entered. See nsDisplayListBuilder::EnterPresShell + * for context. */ - auto onPressShellEntered(mozilla::PresShell* aPresShell) -> void; + auto onPressShellEntered(nsPresContext* aPresContext) -> void; /* * @brief Called when the presshell is exited. */ - auto onPressShellExited(mozilla::PresShell* aPresShell) -> void; + auto onPressShellLeave(nsPresContext* aPresContext) -> void; + + /** + * Recomputes the data dependent on the browsing context, like zoom and text + * zoom. We use it to store Zen boosts related data too. + */ + void RecomputeBrowsingContextDependentData(nsPresContext* aPresContext); + + /** + * @brief Resolve a StyleAbsoluteColor to take into account Zen boosts. + * @param aColor The color to resolve. + * @see StyleColor::ResolveColor for reference. + */ + static auto ResolveStyleColor(mozilla::StyleAbsoluteColor aColor) -> mozilla::StyleAbsoluteColor; private: ~nsZenBoostsBackend() = default; @@ -34,7 +54,7 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { /** * The presshell of the current document being rendered. */ - static nsPresContext* mCurrentPresContext; + RefPtr mCurrentPresContext; }; } // namespace zen From b9e8c39ce92b1f635c03a6c10eb56143dbf4e0e4 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Thu, 30 Oct 2025 19:37:09 +0100 Subject: [PATCH 04/33] chore: Fix builds and lint, b=no-bug, c=no-component --- src/layout/base/nsPresContext-h.patch | 4 ++-- src/zen/boosts/ZenBoostsPresContext.h | 10 +++++----- src/zen/boosts/nsZenBoostsBackend.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/layout/base/nsPresContext-h.patch b/src/layout/base/nsPresContext-h.patch index e2238ca423..0c0bbea78c 100644 --- a/src/layout/base/nsPresContext-h.patch +++ b/src/layout/base/nsPresContext-h.patch @@ -1,5 +1,5 @@ diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h -index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..7328d1ce3a26955b10e8789d0f46dfeba2281c82 100644 +index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..63d2ff7975abf0af3316dc5533554633eaf2b2a5 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -45,6 +45,7 @@ @@ -14,7 +14,7 @@ index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..7328d1ce3a26955b10e8789d0f46dfeb void UpdateForcedColors(bool aNotify = true); public: -+ nsCOMPtr mZenBoostsPresContext; ++ nsCOMPtr mZenBoostsPresContextData; float GetFullZoom() { return mFullZoom; } /** * Device full zoom differs from full zoom because it gets the zoom from diff --git a/src/zen/boosts/ZenBoostsPresContext.h b/src/zen/boosts/ZenBoostsPresContext.h index 24cac4a92a..7e6d042041 100644 --- a/src/zen/boosts/ZenBoostsPresContext.h +++ b/src/zen/boosts/ZenBoostsPresContext.h @@ -8,20 +8,20 @@ #include "nsColor.h" #include "nsISupports.h" -namespace mozilla { +namespace zen { -class ZenBoostsPresContext : public nsISupports { - ~ZenBoostsPresContext(); +class ZenBoostsPresContextData final : public nsISupports { + ~ZenBoostsPresContextData() = default; public: NS_DECL_ISUPPORTS nscolor mAccentColor; - explicit ZenBoostsPresContext(nscolor aAccentColor) + explicit ZenBoostsPresContextData(nscolor aAccentColor) : mAccentColor(aAccentColor) {} }; -} // namespace mozilla +} // namespace zen #endif // mozilla_ZenBoostsPressContext_h__ diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index c26b15034b..556b2a876f 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -63,8 +63,8 @@ auto nsZenBoostsBackend::RecomputeBrowsingContextDependentData( // Create the Zen boosts data if it doesn't exist yet. // TODO: Actually check on the document's href or other properties to // determine what data to store. - aPresContext->mZenBoostsPresContextData = - new ZenBoostsPresContextData(NS_RGBA(111, 78, 55, 1)); + aPresContext->mZenBoostsPresContextData = + new ZenBoostsPresContextData(NS_RGBA(111, 78, 55, 1)); } auto nsZenBoostsBackend::ResolveStyleColor( From 6c2c798c9da9b2395403c8df12266af74a863af5 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Thu, 30 Oct 2025 19:45:12 +0100 Subject: [PATCH 05/33] Change boost editor to be a separate window Remove unecessary ZenBoosts.mjs Update ZenBoostsEditor to be a module --- src/browser/base/content/zen-assets.inc.xhtml | 2 - .../base/content/zen-assets.jar.inc.mn | 5 +- src/zen/boosts/ZenBoosts.mjs | 7 - src/zen/boosts/ZenBoostsEditor.mjs | 352 ------------------ src/zen/boosts/ZenBoostsEditor.sys.mjs | 343 +++++++++++++++++ src/zen/boosts/ZenBoostsManager.sys.mjs | 65 +--- src/zen/boosts/moz.build | 1 + src/zen/boosts/zen-boost-editor.xhtml | 86 +++++ src/zen/boosts/zen-boosts.css | 91 ++++- src/zen/common/ZenUIManager.mjs | 77 ++++ src/zen/common/zen-sets.js | 4 +- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 10 +- src/zen/zen.globals.js | 3 - 13 files changed, 606 insertions(+), 440 deletions(-) delete mode 100644 src/zen/boosts/ZenBoosts.mjs delete mode 100644 src/zen/boosts/ZenBoostsEditor.mjs create mode 100644 src/zen/boosts/ZenBoostsEditor.sys.mjs create mode 100644 src/zen/boosts/zen-boost-editor.xhtml diff --git a/src/browser/base/content/zen-assets.inc.xhtml b/src/browser/base/content/zen-assets.inc.xhtml index cd3b14d36f..a925d400b0 100644 --- a/src/browser/base/content/zen-assets.inc.xhtml +++ b/src/browser/base/content/zen-assets.inc.xhtml @@ -57,7 +57,5 @@ - - diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index d01e05fb4e..6a115fcb5b 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -73,10 +73,11 @@ content/browser/zen-styles/zen-download-arc-animation.css (../../zen/downloads/zen-download-arc-animation.css) content/browser/zen-styles/zen-download-box-animation.css (../../zen/downloads/zen-download-box-animation.css) - content/browser/zen-components/ZenBoosts.mjs (../../zen/boosts/ZenBoosts.mjs) - content/browser/zen-components/ZenBoostsEditor.mjs (../../zen/boosts/ZenBoostsEditor.mjs) content/browser/zen-styles/zen-boosts.css (../../zen/boosts/zen-boosts.css) + # Windows + content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.xhtml) + # Images content/browser/zen-images/brand-header.svg (../../zen/images/brand-header.svg) content/browser/zen-images/layouts/collapsed.png (../../zen/images/layouts/collapsed.png) diff --git a/src/zen/boosts/ZenBoosts.mjs b/src/zen/boosts/ZenBoosts.mjs deleted file mode 100644 index a8b71edf02..0000000000 --- a/src/zen/boosts/ZenBoosts.mjs +++ /dev/null @@ -1,7 +0,0 @@ -// 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/. - -ChromeUtils.defineESModuleGetters(this, { - gZenBoostsManager: "resource:///modules/ZenBoostsManager.sys.mjs", -}); \ No newline at end of file diff --git a/src/zen/boosts/ZenBoostsEditor.mjs b/src/zen/boosts/ZenBoostsEditor.mjs deleted file mode 100644 index d4e2472e7b..0000000000 --- a/src/zen/boosts/ZenBoostsEditor.mjs +++ /dev/null @@ -1,352 +0,0 @@ -// 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/. - -{ - class nsZenBoostEditor extends nsZenMultiWindowFeature { - initialized = false; - - constructor() { - super(); - - if (!gZenWorkspaces.shouldHaveWorkspaces || gZenWorkspaces.privateWindowOrDisabled) { - return; - } - - this.isMouseDown = false; - this.wasDragging = false; - this.lastDotSetPos = { x: 0, y: 0 }; - this.currentBoostData = null; - - this.promiseInitialized = new Promise((resolve) => { - this._resolveInitialized = resolve; - }); - - ChromeUtils.defineLazyGetter(this, 'panel', () => - document.getElementById('PanelUI-zen-boost-editor') - ); - - ChromeUtils.defineLazyGetter(this, 'toolbox', () => document.getElementById('TabsToolbar')); - - this._resolveInitialized(); - delete this._resolveInitialized; - } - - tryInitialize() { - if (this.initialized) return; - if (document.getElementById('PanelUI-zen-boost-editor')) { - this.panel.addEventListener('popupshowing', this.handlePanelOpen.bind(this)); - this.panel.addEventListener('popuphidden', this.handlePanelClose.bind(this)); - this.panel.addEventListener('command', this.handlePanelCommand.bind(this)); - - document - .getElementById('PanelUI-zen-boost-font-arial') - .addEventListener('click', (event) => this.onFontChange(event, 'Arial, sans-serif')); - document - .getElementById('PanelUI-zen-boost-font-serif') - .addEventListener('click', (event) => - this.onFontChange(event, "'Times New Roman', serif") - ); - document - .getElementById('PanelUI-zen-boost-font-mono') - .addEventListener('click', (event) => - this.onFontChange(event, "'Courier New', monospace") - ); - document - .getElementById('PanelUI-zen-boost-font-georgia') - .addEventListener('click', (event) => this.onFontChange(event, "'Georgia', serif")); - document - .getElementById('PanelUI-zen-boost-font-tahoma') - .addEventListener('click', (event) => this.onFontChange(event, 'Tahoma')); - document - .getElementById('PanelUI-zen-boost-font-verdana') - .addEventListener('click', (event) => this.onFontChange(event, 'Verdana')); - document - .getElementById('PanelUI-zen-boost-font-comic') - .addEventListener('click', (event) => this.onFontChange(event, "'Comic Sans MS'")); - document - .getElementById('PanelUI-zen-boost-font-corsiva') - .addEventListener('click', (event) => - this.onFontChange(event, "'Monotype Corsiva, cursive'") - ); - - document - .getElementById('PanelUI-zen-boost-zap') - .addEventListener('click', (event) => console.error('Not implemented')); - document - .getElementById('PanelUI-zen-boost-disable') - .addEventListener('click', this.onToggleDisable.bind(this)); - document - .getElementById('PanelUI-zen-boost-invert') - .addEventListener('click', this.onToggleInvert.bind(this)); - document - .getElementById('PanelUI-zen-boost-delete') - .addEventListener('click', this.onDeleteBoost.bind(this)); - - document - .getElementById('PanelUI-zen-boost-name') - .addEventListener('input', (e) => (this.currentBoostData.boostName = e.target.value)); - - this.initialized = true; - } else { - console.error('Panel element PanelUI-zen-boost-editor not found'); - return; - } - } - - initColorPicker() { - const themePicker = this.panel.querySelector('.zen-boost-color-picker-gradient'); - this._onMouseMove = this.onMouseMove.bind(this); - this._onMouseUp = this.onMouseUp.bind(this); - this._onMouseDown = this.onMouseDown.bind(this); - this._onThemePickerClick = this.onThemePickerClick.bind(this); - document.addEventListener('mousemove', this._onMouseMove); - document.addEventListener('mouseup', this._onMouseUp); - themePicker.addEventListener('mousedown', this._onMouseDown); - themePicker.addEventListener('click', this._onThemePickerClick); - } - - uninitColorPicker() { - const themePicker = this.panel.querySelector('.zen-boost-color-picker-gradient'); - document.removeEventListener('mousemove', this._onMouseMove); - document.removeEventListener('mouseup', this._onMouseUp); - themePicker.removeEventListener('mousedown', this._onMouseDown); - themePicker.removeEventListener('click', this._onThemePickerClick); - this._onThemePickerClick = null; - this._onMouseMove = null; - this._onMouseUp = null; - this._onMouseDown = null; - } - - onMouseMove(event) { - if (this.isMouseDown) { - this.wasDragging = true; - event.preventDefault(); - this.setDotPos(event.clientX, event.clientY, false); - } - } - - onMouseDown(event) { - if (event.button === 2) { - return; - } - - this.isMouseDown = true; - } - - onMouseUp(event) { - if (event.button === 2) { - return; - } - - this.isMouseDown = false; - this.wasDragging = false; - } - - resetDotPosition() { - this.setDotPos(null, null); - } - - onThemePickerClick(event) { - event.preventDefault(); - - this.setDotPos(event.clientX, event.clientY, !this.wasDragging); - this.wasDragging = false; - } - - // Sets the position of the dot - setDotPos(pixelX, pixelY, animate = true) { - const gradient = this.panel.querySelector('.zen-boost-color-picker-gradient'); - const dot = this.panel.querySelector('.zen-boost-color-picker-dot'); - - const rect = gradient.getBoundingClientRect(); - const padding = 40; - - const centerX = rect.left + rect.width / 2; - const centerY = rect.top + rect.height / 2; - const radius = (rect.width - padding) / 2; - - if (!animate) { - let nDistance = Math.sqrt( - (pixelX - this.lastDotSetPos.x) ** 2 + (pixelY - this.lastDotSetPos.y) ** 2 - ); - - if (nDistance > 15) { - // Optional haptic feedback - // Services.zen.playHapticFeedback(); - - this.lastDotSetPos = { - x: pixelX, - y: pixelY, - }; - } - } - - if (pixelX == null || pixelY == null) { - pixelX = centerX; - pixelY = centerY; - - this.currentBoostData.dotAngleDeg = 0; - this.currentBoostData.dotDistance = 0; - this.currentBoostData.dotAngleRad = 0; - } else { - let distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); - distance = Math.min(distance, radius); // Clamp distance - - const angle = Math.atan2(pixelY - centerY, pixelX - centerX); - - pixelX = centerX + Math.cos(angle) * distance; - pixelY = centerY + Math.sin(angle) * distance; - - // Rad to degree - this.currentBoostData.dotAngleDeg = - ((Math.atan2(pixelY - centerY, pixelX - centerX) * 180) / Math.PI + 100) % 360; - if (this.currentBoostData.dotAngleDeg < 0) this.currentBoostData.dotAngleDeg += 360; - - // Map to 0-1 range - this.currentBoostData.dotDistance = distance / radius; - } - - const relativeX = pixelX - rect.left; - const relativeY = pixelY - rect.top; - - // Capture position of dot for restoring it correctly later - this.currentBoostData.dotPos.x = relativeX; - this.currentBoostData.dotPos.y = relativeY; - - if (animate) { - gZenUIManager.motion.animate( - dot, - { - left: `${relativeX}px`, - top: `${relativeY}px`, - }, - { - duration: 0.4, - type: 'spring', - bounce: 0.3, - } - ); - } else { - dot.style.left = `${relativeX}px`; - dot.style.top = `${relativeY}px`; - } - - // Enable color boosting again - if(!this.currentBoostData.enableColorBoost) - this.onToggleDisable(null); - - this.updateDot(); - this.updateCurrentBoost(); - } - - updateDot() { - const dot = this.panel.querySelector('.zen-boost-color-picker-dot'); - dot.style.setProperty( - '--zen-theme-picker-dot-color', - `hsl(${this.currentBoostData.dotAngleDeg}deg, ${this.currentBoostData.dotDistance * 100}%, 55%)` - ); - } - - // This toggles the color changes - onToggleDisable(event) { - this.currentBoostData.enableColorBoost = !this.currentBoostData.enableColorBoost; - - this.updateButtonToggleVisuals(); - this.updateCurrentBoost(); - } - - onToggleInvert(event) { - this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; - - this.updateButtonToggleVisuals(); - this.updateCurrentBoost(); - } - - updateButtonToggleVisuals() { - const invertButton = document.getElementById('PanelUI-zen-boost-invert'); - const disableButton = document.getElementById('PanelUI-zen-boost-disable'); - const gradient = this.panel.querySelector('.zen-boost-color-picker-gradient'); - - if (this.currentBoostData.smartInvert) invertButton.classList.add('zen-boost-button-active'); - else invertButton.classList.remove('zen-boost-button-active'); - - if (!this.currentBoostData.enableColorBoost) - disableButton.classList.add('zen-boost-button-active'); - else disableButton.classList.remove('zen-boost-button-active'); - - if (!this.currentBoostData.enableColorBoost) - gradient.classList.add('zen-boost-panel-disabled'); - else gradient.classList.remove('zen-boost-panel-disabled'); - } - - onFontChange(event, fontFamily) { - if (this.currentBoostData.fontFamily == fontFamily) this.currentBoostData.fontFamily = ''; - else this.currentBoostData.fontFamily = fontFamily; - - this.updateCurrentBoost(); - } - - updateCurrentBoost() { - window.gZenBoostsManager.updateBoost(this.currentBoostData); - } - - onDeleteBoost() { - window.gZenBoostsManager.deleteBoost(this.currentBoostData.domain); - this.currentBoostData = null; - - PanelMultiView.hidePopup(this.panel); - - // Still write modifications to disk - window.gZenBoostsManager.saveBoostToStore(null); - gZenUIManager.showToast('zen-panel-ui-boosts-deleted-message'); - } - - openEditor(event, domain) { - this.tryInitialize(); - this.loadBoost(domain); - - PanelMultiView.openPopup(this.panel, this.toolbox, { - position: 'topright topleft', - triggerEvent: event, - y: 0, - }); - } - - handlePanelClose() { - this.uninitColorPicker(); - if(this.currentBoostData != null) - this.saveBoost(); - } - - handlePanelOpen() { - this.initColorPicker(); - } - - loadBoost(domain) { - const dot = this.panel.querySelector('.zen-boost-color-picker-dot'); - this.currentBoostData = window.gZenBoostsManager.loadBoostFromStore(domain); - - document.getElementById('PanelUI-zen-boost-name').value = this.currentBoostData.boostName; - - // TODO: This doesn't work, it should center the dot - if(this.currentBoostData.dotPos.x == null || this.currentBoostData.dotPos.y == null) this.resetDotPosition(); - else { - dot.style.left = `${this.currentBoostData.dotPos.x}px`; - dot.style.top = `${this.currentBoostData.dotPos.y}px`; - } - - this.updateDot(); - this.updateButtonToggleVisuals(); - } - - saveBoost() { - window.gZenBoostsManager.saveBoostToStore(this.currentBoostData); - gZenUIManager.showToast('zen-panel-ui-boosts-saved-message'); - } - - handlePanelCommand(event) {} - } - - window.gZenBoostsEditor = new nsZenBoostEditor(); -} diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs new file mode 100644 index 0000000000..47c629c0fd --- /dev/null +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -0,0 +1,343 @@ +// 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/. + +import { gZenBoostsManager } from './ZenBoostsManager.sys.mjs'; + +export class nsZenBoostEditor { + doc = null; + window = null; + + constructor(doc, domain, window) { + this.doc = doc; + this.window = window; + + this.isMouseDown = false; + this.wasDragging = false; + this.lastDotSetPos = { x: 0, y: 0 }; + this.currentBoostData = null; + + this.killOtherEditorInstances(); + Services.obs.addObserver(this, 'zen-boosts-kill-editor'); + + this.init(); + this.initColorPicker(); + this.loadBoost(domain); + } + + init() { + this.window.addEventListener("unload", () => this.handleClose()); + + this.doc + .getElementById('PanelUI-zen-boost-font-arial') + .addEventListener('click', (event) => this.onFontChange(event, 'Arial, sans-serif')); + this.doc + .getElementById('PanelUI-zen-boost-font-serif') + .addEventListener('click', (event) => this.onFontChange(event, "'Times New Roman', serif")); + this.doc + .getElementById('PanelUI-zen-boost-font-mono') + .addEventListener('click', (event) => this.onFontChange(event, "'Courier New', monospace")); + this.doc + .getElementById('PanelUI-zen-boost-font-georgia') + .addEventListener('click', (event) => this.onFontChange(event, "'Georgia', serif")); + this.doc + .getElementById('PanelUI-zen-boost-font-tahoma') + .addEventListener('click', (event) => this.onFontChange(event, 'Tahoma')); + this.doc + .getElementById('PanelUI-zen-boost-font-verdana') + .addEventListener('click', (event) => this.onFontChange(event, 'Verdana')); + this.doc + .getElementById('PanelUI-zen-boost-font-comic') + .addEventListener('click', (event) => this.onFontChange(event, "'Comic Sans MS'")); + this.doc + .getElementById('PanelUI-zen-boost-font-corsiva') + .addEventListener('click', (event) => + this.onFontChange(event, "'Monotype Corsiva, cursive'") + ); + + this.doc + .getElementById('PanelUI-zen-boost-zap') + .addEventListener('click', (event) => console.error('Not implemented')); + this.doc + .getElementById('PanelUI-zen-boost-disable') + .addEventListener('click', this.onToggleDisable.bind(this)); + this.doc + .getElementById('PanelUI-zen-boost-invert') + .addEventListener('click', this.onToggleInvert.bind(this)); + this.doc + .getElementById('PanelUI-zen-boost-delete') + .addEventListener('click', this.onDeleteBoost.bind(this)); + + this.doc + .getElementById('PanelUI-zen-boost-name') + .addEventListener('input', (e) => (this.currentBoostData.boostName = e.target.value)); + + this.initialized = true; + } + + uninit() { + this.uninitColorPicker(); + Services.obs.removeObserver(this, "zen-boosts-kill-editor"); + } + + killOtherEditorInstances() { + Services.obs.notifyObservers(null, "zen-boosts-kill-editor"); + } + + observe(subject, topic, data) { if (topic === 'zen-boosts-kill-editor') { this.window.close(); } } + + registerTabChangedEvent() { + this.window.gBrowser.tabContainer.addEventListener("TabSelect", event => { + const tab = event.target; + const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + + if(domain != this.currentBoostData.domain) + this.window.close(); + }); + } + + initColorPicker() { + const themePicker = this.doc.querySelector('.zen-boost-color-picker-gradient'); + this._onMouseMove = this.onMouseMove.bind(this); + this._onMouseUp = this.onMouseUp.bind(this); + this._onMouseDown = this.onMouseDown.bind(this); + this._onThemePickerClick = this.onThemePickerClick.bind(this); + this.doc.addEventListener('mousemove', this._onMouseMove); + this.doc.addEventListener('mouseup', this._onMouseUp); + themePicker.addEventListener('mousedown', this._onMouseDown); + themePicker.addEventListener('click', this._onThemePickerClick); + } + + uninitColorPicker() { + const themePicker = this.doc.querySelector('.zen-boost-color-picker-gradient'); + this.doc.removeEventListener('mousemove', this._onMouseMove); + this.doc.removeEventListener('mouseup', this._onMouseUp); + themePicker.removeEventListener('mousedown', this._onMouseDown); + themePicker.removeEventListener('click', this._onThemePickerClick); + this._onThemePickerClick = null; + this._onMouseMove = null; + this._onMouseUp = null; + this._onMouseDown = null; + } + + onMouseMove(event) { + if (this.isMouseDown) { + this.wasDragging = true; + event.preventDefault(); + this.setDotPos(event.clientX, event.clientY, false); + } + } + + onMouseDown(event) { + if (event.button === 2) { + return; + } + + this.isMouseDown = true; + } + + onMouseUp(event) { + if (event.button === 2) { + return; + } + + this.isMouseDown = false; + this.wasDragging = false; + } + + resetDotPosition() { + this.setDotPos(null, null); + } + + onThemePickerClick(event) { + event.preventDefault(); + + this.setDotPos(event.clientX, event.clientY, !this.wasDragging); + this.wasDragging = false; + } + + // Sets the position of the dot + setDotPos(pixelX, pixelY, animate = true) { + const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); + const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); + + const rect = gradient.getBoundingClientRect(); + const padding = 40; + + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + const radius = (rect.width - padding) / 2; + + if (!animate) { + let nDistance = Math.sqrt( + (pixelX - this.lastDotSetPos.x) ** 2 + (pixelY - this.lastDotSetPos.y) ** 2 + ); + + if (nDistance > 15) { + // Optional haptic feedback + // Services.zen.playHapticFeedback(); + + this.lastDotSetPos = { + x: pixelX, + y: pixelY, + }; + } + } + + if (pixelX == null || pixelY == null) { + pixelX = centerX; + pixelY = centerY; + + this.currentBoostData.dotAngleDeg = 0; + this.currentBoostData.dotDistance = 0; + this.currentBoostData.dotAngleRad = 0; + } else { + let distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); + distance = Math.min(distance, radius); // Clamp distance + + const angle = Math.atan2(pixelY - centerY, pixelX - centerX); + + pixelX = centerX + Math.cos(angle) * distance; + pixelY = centerY + Math.sin(angle) * distance; + + // Rad to degree + this.currentBoostData.dotAngleDeg = + ((Math.atan2(pixelY - centerY, pixelX - centerX) * 180) / Math.PI + 100) % 360; + if (this.currentBoostData.dotAngleDeg < 0) this.currentBoostData.dotAngleDeg += 360; + + // Map to 0-1 range + this.currentBoostData.dotDistance = distance / radius; + } + + const relativeX = pixelX - rect.left; + const relativeY = pixelY - rect.top; + + // Capture position of dot for restoring it correctly later + this.currentBoostData.dotPos.x = relativeX; + this.currentBoostData.dotPos.y = relativeY; + + // TODO: Fix animation + // if (animate) { + // this.window.motion.animate( + // dot, + // { + // left: `${relativeX}px`, + // top: `${relativeY}px`, + // }, + // { + // duration: 0.4, + // type: 'spring', + // bounce: 0.3, + // } + // ); + // } else { + dot.style.left = `${relativeX}px`; + dot.style.top = `${relativeY}px`; + // } + + // Enable color boosting again + if (!this.currentBoostData.enableColorBoost) this.onToggleDisable(null); + + this.updateDot(); + this.updateCurrentBoost(); + } + + updateDot() { + const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); + dot.style.setProperty( + '--zen-theme-picker-dot-color', + `hsl(${this.currentBoostData.dotAngleDeg}deg, ${this.currentBoostData.dotDistance * 100}%, 55%)` + ); + + const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); + const rect = gradient.getBoundingClientRect(); + const padding = 40; + const radius = (rect.width - padding) / 2; + + const circle = this.doc.querySelector('.zen-boost-color-picker-circle'); + circle.style.width = `${this.currentBoostData.dotDistance * radius * 2}px`; + circle.style.height = `${this.currentBoostData.dotDistance * radius * 2}px`; + } + + // This toggles the color changes + onToggleDisable(event) { + this.currentBoostData.enableColorBoost = !this.currentBoostData.enableColorBoost; + + this.updateButtonToggleVisuals(); + this.updateCurrentBoost(); + } + + onToggleInvert(event) { + this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; + + this.updateButtonToggleVisuals(); + this.updateCurrentBoost(); + } + + updateButtonToggleVisuals() { + const invertButton = this.doc.getElementById('PanelUI-zen-boost-invert'); + const disableButton = this.doc.getElementById('PanelUI-zen-boost-disable'); + const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); + + if (this.currentBoostData.smartInvert) invertButton.classList.add('zen-boost-button-active'); + else invertButton.classList.remove('zen-boost-button-active'); + + if (!this.currentBoostData.enableColorBoost) + disableButton.classList.add('zen-boost-button-active'); + else disableButton.classList.remove('zen-boost-button-active'); + + if (!this.currentBoostData.enableColorBoost) gradient.classList.add('zen-boost-panel-disabled'); + else gradient.classList.remove('zen-boost-panel-disabled'); + } + + onFontChange(event, fontFamily) { + if (this.currentBoostData.fontFamily == fontFamily) this.currentBoostData.fontFamily = ''; + else this.currentBoostData.fontFamily = fontFamily; + + this.updateCurrentBoost(); + } + + updateCurrentBoost() { + gZenBoostsManager.updateBoost(this.currentBoostData); + } + + onDeleteBoost() { + this.window.prompt + + gZenBoostsManager.deleteBoost(this.currentBoostData.domain); + this.currentBoostData = null; + + // Still write modifications to disk + gZenBoostsManager.saveBoostToStore(null); + this.window.gZenUIManager.showToast('zen-panel-ui-boosts-deleted-message'); + + this.window.close(); + } + + handleClose() { + this.uninit(); + if (this.currentBoostData != null) this.saveBoost(); + } + + loadBoost(domain) { + this.currentBoostData = gZenBoostsManager.loadBoostFromStore(domain); + + this.doc.getElementById('PanelUI-zen-boost-name').value = this.currentBoostData.boostName; + + const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); + if (this.currentBoostData.dotPos.x == null || this.currentBoostData.dotPos.y == null) + this.resetDotPosition(); + else { + dot.style.left = `${this.currentBoostData.dotPos.x}px`; + dot.style.top = `${this.currentBoostData.dotPos.y}px`; + } + + this.updateDot(); + this.updateButtonToggleVisuals(); + } + + saveBoost() { + gZenBoostsManager.saveBoostToStore(this.currentBoostData); + this.window.gZenUIManager.showToast('zen-panel-ui-boosts-saved-message'); + } +} diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 1b4624111a..5420107778 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -3,7 +3,6 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. export class nsZenBoostsManager { - initialized = false; registeredSheets = new Map(); registeredBoosts = new Map(); @@ -15,7 +14,7 @@ export class nsZenBoostsManager { } init() { - this.readBoostsFromStore(() => initialized = true); + this.readBoostsFromStore(() => (initialized = true)); } // Store Firefox Style Sheet Service and IO Service for later use @@ -58,7 +57,7 @@ export class nsZenBoostsManager { } deleteBoost(domain) { - if(this.registeredBoosts.has(domain)) { + if (this.registeredBoosts.has(domain)) { this.unregisterSheet(domain); this.registeredBoosts.delete(domain); } @@ -101,51 +100,14 @@ export class nsZenBoostsManager { `; } - const invert = boost.smartInvert ? 'invert()' : ''; - - let colorFilters = ` - html { - filter: ${invert} !important; - } - - img, video, picture { - filter: ${invert} !important; - } - `; - - // TODO: Temporary color theming using mix-blend-mode - if (boost.enableColorBoost) { - colorFilters += ` - html, body { position: relative; } - body::before { - content: ""; - position: fixed; /* cover everything */ - pointer-events: none; /* don't block interactions */ - top: 0; left: 0; right: 0; bottom: 0; - - background-color: hsl(${boost.dotAngleDeg - (boost.smartInvert ? 180 : 0)}deg, ${boost.dotDistance * 100}%, ${10 + boost.dotDistance * 60}%); - - mix-blend-mode: color; - z-index: 1000; - } - body img, - body video, - body canvas, - body picture { - position: relative; - isolation: isolate; - z-index: 1001; /* put images above the overlay */ - } - - `; - } + // TODO: Send colors to boosts backend - this.registerCSSForDomain(colorFilters + ' ' + fontFamily, boost.domain); + this.registerCSSForDomain(fontFamily, boost.domain); } // Save all boosts to the profile folder saveBoostToStore(boostData) { - if(boostData != null) + if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); (async () => this.writeToDisk(this.registeredBoosts))(); @@ -153,17 +115,16 @@ export class nsZenBoostsManager { // Reads all boosts from the profile folder readBoostsFromStore(done) { - this.readFromDisk() - .then((map) => { - this.registeredBoosts = map; + this.readFromDisk().then((map) => { + this.registeredBoosts = map; - // Load in all boosts - for (const [key, value] of this.registeredBoosts) { - this.updateBoost(value); - } + // Load in all boosts + for (const [key, value] of this.registeredBoosts) { + this.updateBoost(value); + } - done(); - }); + done(); + }); } // Helper method, disk => json => map diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index ebfbb6f73f..5a29502c69 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -3,6 +3,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXTRA_JS_MODULES += [ + "ZenBoostsEditor.sys.mjs", "ZenBoostsManager.sys.mjs", ] diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.xhtml new file mode 100644 index 0000000000..2b6c8cfd20 --- /dev/null +++ b/src/zen/boosts/zen-boost-editor.xhtml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + const { nsZenBoostEditor } = ChromeUtils.importESModule( "resource:///modules/ZenBoostsEditor.sys.mjs" ); + window.addEventListener("load", () => { + new nsZenBoostEditor(document, window.domain, window); + }); + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 2e78cb4bc1..41e29e7087 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -4,6 +4,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +:root { + @media (-moz-platform: macos) { + --panel-border-radius: 10px; + + @media (-moz-mac-tahoe-theme) { + --panel-border-radius: 12px; + } + } +} + #PanelUI-zen-boost-font-arial { font-family: Arial, sans-serif; } @@ -20,14 +30,45 @@ gap: 10px; } +#PanelUI-zen-boost-head-wrapper { + margin-top: -10px; + margin-bottom: -10px; + + & input { + height: 26px !important; + } +} + +#PanelUI-zen-boost-font-wrapper { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0px; + width: 100%; + + & button { + padding: auto !important; + margin: auto !important; + } +} + #PanelUI-zen-boost-filter-wrapper { width: 135px; & input { - width: auto; + height: 26px; + + border: none; + background-color: transparent; + outline: none; + + & :active { + border: solid 1px #FFFFFF99; + } } & button { + border-radius: 5px; + padding: 0 !important; min-width: fit-content !important; @@ -54,6 +95,10 @@ } } +#zen-boost-editor-root { + padding: 15px; +} + #PanelUI-zen-boost-delete { width: auto; } @@ -64,7 +109,7 @@ pointer-events: none !important; } -#PanelUI-zen-boost-toolbar-wrapper{ +#PanelUI-zen-boost-toolbar-wrapper { width: auto; margin: auto; gap: 1.15em; @@ -85,8 +130,8 @@ height: 5px !important; } -#PanelUI-zen-boost-toolbar-wrapper button:hover{ - background-color: #FFFFFF11; +#PanelUI-zen-boost-toolbar-wrapper button:hover { + background-color: #ffffff11; opacity: 0.9; } @@ -108,18 +153,18 @@ border: 5px solid hsl(100 100% 60%); border-image-slice: 1; border-image-source: conic-gradient( - rgba(255, 0, 0, 1) 0%, - rgba(255, 162, 0, 1) 10%, - rgba(255, 242, 0, 1) 20%, - rgba(89, 255, 0, 1) 30%, - rgba(0, 255, 128, 1) 40%, - rgba(0, 255, 247, 1) 50%, - rgba(0, 89, 255, 1) 60%, - rgba(8, 0, 255, 1) 70%, - rgba(204, 0, 255, 1) 80%, - rgba(255, 0, 144, 1) 90%, - rgba(255, 0, 8, 1) 100% - ); + rgba(255, 0, 0, 1) 0%, + rgba(255, 162, 0, 1) 10%, + rgba(255, 242, 0, 1) 20%, + rgba(89, 255, 0, 1) 30%, + rgba(0, 255, 128, 1) 40%, + rgba(0, 255, 247, 1) 50%, + rgba(0, 89, 255, 1) 60%, + rgba(8, 0, 255, 1) 70%, + rgba(204, 0, 255, 1) 80%, + rgba(255, 0, 144, 1) 90%, + rgba(255, 0, 8, 1) 100% + ); pointer-events: none; } @@ -143,6 +188,20 @@ background-position: -23px -23px; background-size: 6px 6px; + & .zen-boost-color-picker-circle { + position: absolute; + z-index: 1; + opacity: 0; + width: 100px; + height: 100px; + + transform-origin: center center; + transform: translate(-50%, -50%); + + outline: solid 1px white; + border-radius: 100%; + } + & .zen-boost-color-picker-dot { position: absolute; z-index: 2; diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index a95f7e60de..516f9ddd69 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -162,6 +162,83 @@ var gZenUIManager = { }; }, + openBoostWindow() { + const screenX = window.screenX; + const screenY = window.screenY; + const width = window.outerWidth; + const height = window.outerHeight; + + // TODO: This needs to be changed to exact values + const editorWidth = 175; + const editorHeight = 435; + const pad = 20; + + const animationTarget = 25; + + let left = screenX + width + pad; + let top = screenY + height / 2 - editorHeight / 2; + + if (left + editorWidth > screen.availWidth) { + left = screenX + width - (editorWidth + pad); + } + + const editor = window.openDialog( + 'chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml', + '', + `left=${left},top=${top + animationTarget},chrome,titlebar,alwaysontop` + ); + + // Close the editor if the tab is switched + window.gBrowser.tabContainer.addEventListener( + "TabSelect", + event => { + const tab = event.target; + const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + + // Close if domain doesn't match + if (domain != editor.domain) { + editor.close(); + } + }, + // Remove the event listener after the window closes + { once: true } + ); + + // Cleaning up on close + editor.window.addEventListener("unload", () => this._openBoostEditor = null); + + // Rather inconvenient window animation using moveTo + // editor.addEventListener('load', () => { + // const interval = 16; + // let value = animationTarget; + + // let anim = null; + // anim = setInterval(() => { + // if (window.closed) { clearInterval(anim); return; } + // if (!editor || typeof editor.moveTo !== 'function') { clearInterval(anim); return; } + + // if ((value - 1) <= 0) { + // clearInterval(anim); + // editor.moveTo(left, top); + // return; + // } + + // value = value + (0 - value) * 0.25; + // editor.moveTo(left, top + value); + + // }, interval); + // }); + + // Give the domain + const tab = window.gBrowser.selectedTab; + const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + editor.domain = domain; + + // Give the animator + editor.gZenUIManager = this; + return editor; + }, + updateTabsToolbar() { const kUrlbarHeight = 335; gURLBar.textbox.style.setProperty( diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js index f8d0c583f0..892a66ca82 100644 --- a/src/zen/common/zen-sets.js +++ b/src/zen/common/zen-sets.js @@ -24,9 +24,7 @@ document.addEventListener( gZenWorkspaces.changeWorkspaceShortcut(); break; case 'cmd_zenOpenBoostEditor': - const currentTab = gBrowser.selectedTab; - const domain = new URL(currentTab.linkedBrowser.currentURI.spec).hostname; - gZenBoostsEditor.openEditor(event, domain); + gZenUIManager.openBoostWindow(); break; case 'cmd_zenWorkspaceBackward': gZenWorkspaces.changeWorkspaceShortcut(-1); diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 38ad5b5401..4335f16d44 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -2,7 +2,7 @@ * 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/. */ -import { gZenBoostsManager } from "resource:///modules/ZenBoostsManager.sys.mjs"; +import { gZenBoostsManager } from 'resource:///modules/ZenBoostsManager.sys.mjs'; function isNotEmptyTab(window) { return !window.gBrowser.selectedTab.hasAttribute('zen-empty-tab'); @@ -31,7 +31,9 @@ const globalActionsTemplate = [ isAvailable: (window) => { const tab = window.gBrowser.selectedTab; const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; - return !tab.hasAttribute('zen-empty-tab') && !gZenBoostsManager.registeredBoostForDomain(domain); + return ( + !tab.hasAttribute('zen-empty-tab') && !gZenBoostsManager.registeredBoostForDomain(domain) + ); }, }, { @@ -41,7 +43,9 @@ const globalActionsTemplate = [ isAvailable: (window) => { const tab = window.gBrowser.selectedTab; const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; - return !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain); + return ( + !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain) + ); }, }, { diff --git a/src/zen/zen.globals.js b/src/zen/zen.globals.js index fc964de0c9..ce22cd4a8b 100644 --- a/src/zen/zen.globals.js +++ b/src/zen/zen.globals.js @@ -36,9 +36,6 @@ export default [ 'gZenMediaController', 'gZenGlanceManager', - 'gZenBoostsManager', - 'gZenBoostsEditor', - 'nsZenThemePicker', 'gZenThemePicker', From 6d4578ec3b73f0b3af0c78fdd505c5e1b1a8c8b5 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Thu, 30 Oct 2025 20:43:52 +0100 Subject: [PATCH 06/33] Add check if current tab can be boosted --- src/zen/boosts/ZenBoostsManager.sys.mjs | 8 ++++++-- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 5420107778..25631a59a6 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -107,8 +107,7 @@ export class nsZenBoostsManager { // Save all boosts to the profile folder saveBoostToStore(boostData) { - if (boostData != null) - this.registeredBoosts.set(boostData.domain, boostData); + if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); (async () => this.writeToDisk(this.registeredBoosts))(); } @@ -157,6 +156,11 @@ export class nsZenBoostsManager { registeredBoostForDomain(domain) { return this.registeredSheets.has(domain); } + + // Checks if a boost can be created + canBoostSite(uri) { + return uri.schemeIs('http') || uri.schemeIs('https'); + } } export const gZenBoostsManager = new nsZenBoostsManager(); diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 4335f16d44..8e28f88498 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -30,9 +30,11 @@ const globalActionsTemplate = [ icon: 'chrome://browser/skin/zen-icons/selectable/paintbrush.svg', isAvailable: (window) => { const tab = window.gBrowser.selectedTab; - const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + const url = new URL(tab.linkedBrowser.currentURI.spec); + const domain = url.hostname; + const uri = window.gBrowser.currentURI; return ( - !tab.hasAttribute('zen-empty-tab') && !gZenBoostsManager.registeredBoostForDomain(domain) + !tab.hasAttribute('zen-empty-tab') && !gZenBoostsManager.registeredBoostForDomain(domain && gZenBoostsManager.canBoostSite(uri)) ); }, }, @@ -42,9 +44,11 @@ const globalActionsTemplate = [ icon: 'chrome://browser/skin/zen-icons/selectable/paintbrush.svg', isAvailable: (window) => { const tab = window.gBrowser.selectedTab; - const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + const url = new URL(tab.linkedBrowser.currentURI.spec); + const domain = url.hostname; + const uri = window.gBrowser.currentURI; return ( - !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain) + !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain) && gZenBoostsManager.canBoostSite(uri) ); }, }, From bcb84886f2d470088081bb2c1140256d4b6338ce Mon Sep 17 00:00:00 2001 From: fen4flo Date: Thu, 30 Oct 2025 20:48:01 +0100 Subject: [PATCH 07/33] Run lint:fix --- prefs/zen/zen-boosts.yaml | 2 +- src/browser/themes/shared/zen-icons/icons.css | 2 +- src/zen/boosts/ZenBoostsEditor.sys.mjs | 27 +++++++++-------- src/zen/boosts/ZenBoostsManager.sys.mjs | 29 +++++++++++++++++++ src/zen/boosts/zen-boosts.css | 6 ++-- src/zen/common/ZenUIManager.mjs | 10 +++---- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 7 +++-- 7 files changed, 59 insertions(+), 24 deletions(-) diff --git a/prefs/zen/zen-boosts.yaml b/prefs/zen/zen-boosts.yaml index 6476cde132..95d321cd70 100644 --- a/prefs/zen/zen-boosts.yaml +++ b/prefs/zen/zen-boosts.yaml @@ -3,4 +3,4 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. - name: zen.boosts.enabled - value: true \ No newline at end of file + value: true diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index fbf5eeba42..1d6061b6cb 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -957,4 +957,4 @@ #PanelUI-zen-boost-disable { list-style-image: url('chrome://browser/skin/zen-icons/selectable/block.svg') !important; -} \ No newline at end of file +} diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 47c629c0fd..7cc8e261d7 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -26,7 +26,7 @@ export class nsZenBoostEditor { } init() { - this.window.addEventListener("unload", () => this.handleClose()); + this.window.addEventListener('unload', () => this.handleClose()); this.doc .getElementById('PanelUI-zen-boost-font-arial') @@ -77,22 +77,25 @@ export class nsZenBoostEditor { uninit() { this.uninitColorPicker(); - Services.obs.removeObserver(this, "zen-boosts-kill-editor"); + Services.obs.removeObserver(this, 'zen-boosts-kill-editor'); } killOtherEditorInstances() { - Services.obs.notifyObservers(null, "zen-boosts-kill-editor"); + Services.obs.notifyObservers(null, 'zen-boosts-kill-editor'); } - observe(subject, topic, data) { if (topic === 'zen-boosts-kill-editor') { this.window.close(); } } + observe(subject, topic, data) { + if (topic === 'zen-boosts-kill-editor') { + this.window.close(); + } + } registerTabChangedEvent() { - this.window.gBrowser.tabContainer.addEventListener("TabSelect", event => { + this.window.gBrowser.tabContainer.addEventListener('TabSelect', (event) => { const tab = event.target; const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; - if(domain != this.currentBoostData.domain) - this.window.close(); + if (domain != this.currentBoostData.domain) this.window.close(); }); } @@ -231,8 +234,8 @@ export class nsZenBoostEditor { // } // ); // } else { - dot.style.left = `${relativeX}px`; - dot.style.top = `${relativeY}px`; + dot.style.left = `${relativeX}px`; + dot.style.top = `${relativeY}px`; // } // Enable color boosting again @@ -302,11 +305,11 @@ export class nsZenBoostEditor { } onDeleteBoost() { - this.window.prompt + this.window.prompt; gZenBoostsManager.deleteBoost(this.currentBoostData.domain); this.currentBoostData = null; - + // Still write modifications to disk gZenBoostsManager.saveBoostToStore(null); this.window.gZenUIManager.showToast('zen-panel-ui-boosts-deleted-message'); @@ -323,7 +326,7 @@ export class nsZenBoostEditor { this.currentBoostData = gZenBoostsManager.loadBoostFromStore(domain); this.doc.getElementById('PanelUI-zen-boost-name').value = this.currentBoostData.boostName; - + const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); if (this.currentBoostData.dotPos.x == null || this.currentBoostData.dotPos.y == null) this.resetDotPosition(); diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 25631a59a6..2739267a1d 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -161,6 +161,35 @@ export class nsZenBoostsManager { canBoostSite(uri) { return uri.schemeIs('http') || uri.schemeIs('https'); } + + /** + * From ZenGradientGenerator.mjs + * Converts an HSL color value to RGB. Conversion formula + * adapted from https://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ + hslToRgb(h, s, l) { + const { round } = Math; + let r, g, b; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = this.hueToRgb(p, q, h + 1 / 3); + g = this.hueToRgb(p, q, h); + b = this.hueToRgb(p, q, h - 1 / 3); + } + + return [round(r * 255), round(g * 255), round(b * 255)]; + } } export const gZenBoostsManager = new nsZenBoostsManager(); diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 41e29e7087..8591b0a795 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -33,7 +33,7 @@ #PanelUI-zen-boost-head-wrapper { margin-top: -10px; margin-bottom: -10px; - + & input { height: 26px !important; } @@ -62,7 +62,7 @@ outline: none; & :active { - border: solid 1px #FFFFFF99; + border: solid 1px #ffffff99; } } @@ -197,7 +197,7 @@ transform-origin: center center; transform: translate(-50%, -50%); - + outline: solid 1px white; border-radius: 100%; } diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index 516f9ddd69..86dc90604a 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -190,8 +190,8 @@ var gZenUIManager = { // Close the editor if the tab is switched window.gBrowser.tabContainer.addEventListener( - "TabSelect", - event => { + 'TabSelect', + (event) => { const tab = event.target; const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; @@ -201,11 +201,11 @@ var gZenUIManager = { } }, // Remove the event listener after the window closes - { once: true } + { once: true } ); // Cleaning up on close - editor.window.addEventListener("unload", () => this._openBoostEditor = null); + editor.window.addEventListener('unload', () => (this._openBoostEditor = null)); // Rather inconvenient window animation using moveTo // editor.addEventListener('load', () => { @@ -228,7 +228,7 @@ var gZenUIManager = { // }, interval); // }); - + // Give the domain const tab = window.gBrowser.selectedTab; const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 8e28f88498..dc9e60bcdd 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -34,7 +34,8 @@ const globalActionsTemplate = [ const domain = url.hostname; const uri = window.gBrowser.currentURI; return ( - !tab.hasAttribute('zen-empty-tab') && !gZenBoostsManager.registeredBoostForDomain(domain && gZenBoostsManager.canBoostSite(uri)) + !tab.hasAttribute('zen-empty-tab') && + !gZenBoostsManager.registeredBoostForDomain(domain && gZenBoostsManager.canBoostSite(uri)) ); }, }, @@ -48,7 +49,9 @@ const globalActionsTemplate = [ const domain = url.hostname; const uri = window.gBrowser.currentURI; return ( - !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain) && gZenBoostsManager.canBoostSite(uri) + !tab.hasAttribute('zen-empty-tab') && + gZenBoostsManager.registeredBoostForDomain(domain) && + gZenBoostsManager.canBoostSite(uri) ); }, }, From 4091a779dbe1e22c4154844e592040ffc4cd1d11 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 01:15:50 +0100 Subject: [PATCH 08/33] feat: Expose the c++ API to JS, b=no-bug, c=no-component --- src/zen/boosts/ZenBoostsPresContext.h | 3 ++ src/zen/boosts/components.conf | 3 +- src/zen/boosts/nsIZenBoostsBackend.idl | 16 +++++++- src/zen/boosts/nsZenBoostsBackend.cpp | 54 +++++++++++++++++++++++--- src/zen/boosts/nsZenBoostsBackend.h | 6 ++- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/zen/boosts/ZenBoostsPresContext.h b/src/zen/boosts/ZenBoostsPresContext.h index 7e6d042041..2f7635fbf5 100644 --- a/src/zen/boosts/ZenBoostsPresContext.h +++ b/src/zen/boosts/ZenBoostsPresContext.h @@ -17,11 +17,14 @@ class ZenBoostsPresContextData final : public nsISupports { NS_DECL_ISUPPORTS nscolor mAccentColor; + bool mShouldBeApplied = true; explicit ZenBoostsPresContextData(nscolor aAccentColor) : mAccentColor(aAccentColor) {} }; +using ZenBoostsMap = nsTHashMap>; + } // namespace zen #endif // mozilla_ZenBoostsPressContext_h__ diff --git a/src/zen/boosts/components.conf b/src/zen/boosts/components.conf index 57a2f20378..0aa962595e 100644 --- a/src/zen/boosts/components.conf +++ b/src/zen/boosts/components.conf @@ -9,6 +9,7 @@ Classes = [ 'contract_ids': ['@mozilla.org/zen/boosts-backend;1'], 'type': 'zen::nsZenBoostsBackend', 'headers': ['mozilla/nsZenBoostsBackend.h'], - 'processes': ProcessSelector.CONTENT_PROCESS_ONLY, + 'js_name': 'boosts', +# 'processes': ProcessSelector.CONTENT_PROCESS_ONLY, }, ] diff --git a/src/zen/boosts/nsIZenBoostsBackend.idl b/src/zen/boosts/nsIZenBoostsBackend.idl index 6f047bb77a..eed567051a 100644 --- a/src/zen/boosts/nsIZenBoostsBackend.idl +++ b/src/zen/boosts/nsIZenBoostsBackend.idl @@ -3,15 +3,29 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsISupports.idl" -#include "nsIURI.idl" %{C++ #include "nsColor.h" %} +native nscolor(nscolor); + /** * @brief Interface for Zen boosts backend. */ [scriptable, uuid(7d46aa4e-7486-4f77-ab47-81125f1a5723)] interface nsIZenBoostsBackend : nsISupports { + /** + * @brief Register a new Zen boost for a given domain. + * @param aDomain The domain to register the boost for. + * @param aAccentColor The accent color to use for the boost. + */ + void RegisterZenBoost(in AString aDomain, + in Array aAccentColor); + + /** + * @brief Unregister a Zen boost for a given domain. + * @param aDomain The domain to unregister the boost for. + */ + void UnregisterZenBoost(in AString aDomain); }; diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 556b2a876f..348c91d8d9 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -51,7 +51,7 @@ auto nsZenBoostsBackend::onPressShellLeave(nsPresContext* aPresContext) -> void auto nsZenBoostsBackend::RecomputeBrowsingContextDependentData( nsPresContext* aPresContext) -> void { - if (!aPresContext) { + if (!aPresContext || aPresContext->IsChrome()) { return; } @@ -60,11 +60,28 @@ auto nsZenBoostsBackend::RecomputeBrowsingContextDependentData( return; } - // Create the Zen boosts data if it doesn't exist yet. - // TODO: Actually check on the document's href or other properties to - // determine what data to store. - aPresContext->mZenBoostsPresContextData = - new ZenBoostsPresContextData(NS_RGBA(111, 78, 55, 1)); + if (aPresContext->mZenBoostsPresContextData) { + if (aPresContext->mZenBoostsPresContextData->mShouldBeApplied) { + // If the boost data indicates it shouldn't be applied, skip. + // Parsing urls and doing checks can be expensive. + return; + } + // We don't have a boost for this domain anymore. + aPresContext->mZenBoostsPresContextData = nullptr; + return; + } + + // Check if we have a boost for this domain + nsIURI* baseURI = document->GetBaseURI(); + nsAutoCString host; + if (baseURI) { + baseURI->GetHost(host); + nsString hostWStr; + CopyUTF8toUTF16(host, hostWStr); + if (auto boostData = mZenBoostsMap.Get(hostWStr)) { + aPresContext->mZenBoostsPresContextData = boostData; + } + } } auto nsZenBoostsBackend::ResolveStyleColor( @@ -115,4 +132,29 @@ auto nsZenBoostsBackend::ResolveStyleColor( return aColor; } +nsresult nsZenBoostsBackend::RegisterZenBoost( + const nsAString& aDomain, const nsTArray& aAccentColor) { + if (aAccentColor.IsEmpty() || aAccentColor.Length() < 3) { + return NS_ERROR_INVALID_ARG; + } + + nscolor accentColor = NS_RGB(aAccentColor[0], aAccentColor[1], aAccentColor[2]); + RefPtr data = + new ZenBoostsPresContextData(accentColor); + mZenBoostsMap.InsertOrUpdate(aDomain, data); + return NS_OK; +} + +nsresult nsZenBoostsBackend::UnregisterZenBoost(const nsAString& aDomain) { + // We do need to mark the data as not to be applied, so that + // existing prescontexts don't try to re-apply it. + if (auto boostData = mZenBoostsMap.Get(aDomain)) { + boostData->mShouldBeApplied = false; + } + mZenBoostsMap.Remove(aDomain); + return NS_OK; +} + +ZenBoostsMap nsZenBoostsBackend::mZenBoostsMap{}; + } // namespace zen diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h index f338aa682d..7aa3b8bb21 100644 --- a/src/zen/boosts/nsZenBoostsBackend.h +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -19,7 +19,6 @@ namespace zen { class nsZenBoostsBackend final : public nsIZenBoostsBackend { NS_DECL_ISUPPORTS - NS_DECL_NSIZENBOOSTSBACKEND public: explicit nsZenBoostsBackend(); @@ -48,6 +47,7 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { */ static auto ResolveStyleColor(mozilla::StyleAbsoluteColor aColor) -> mozilla::StyleAbsoluteColor; + NS_DECL_NSIZENBOOSTSBACKEND private: ~nsZenBoostsBackend() = default; @@ -55,6 +55,10 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { * The presshell of the current document being rendered. */ RefPtr mCurrentPresContext; + /** + * A map containing what boosts should be applied to which domains. + */ + static ZenBoostsMap mZenBoostsMap; }; } // namespace zen From 6361570a2e41273c2c371d1d72eacaa520b9da47 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 31 Oct 2025 01:25:14 +0100 Subject: [PATCH 09/33] Add boost panel in site specific settings Add animated sparkle icon to control center if site is boosted Fix bad management with the boost/registered css sheet maps Remove boost-editor.inc and references --- .../base/content/zen-assets.jar.inc.mn | 1 + .../base/content/zen-panels/boost-editor.inc | 47 -------- .../base/content/zen-popupset.inc.xhtml | 1 - src/browser/themes/shared/zen-icons/icons.css | 33 +++++- src/zen/boosts/ZenBoostsEditor.sys.mjs | 11 +- src/zen/boosts/ZenBoostsManager.sys.mjs | 9 +- src/zen/common/ZenUIManager.mjs | 35 +++++- src/zen/images/boost-indicator.svg | 50 +++++++++ src/zen/urlbar/ZenSiteDataPanel.sys.mjs | 102 +++++++++++++++++- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 15 ++- 10 files changed, 241 insertions(+), 63 deletions(-) delete mode 100644 src/browser/base/content/zen-panels/boost-editor.inc create mode 100644 src/zen/images/boost-indicator.svg diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index 6a115fcb5b..08c2adbf1b 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -85,6 +85,7 @@ content/browser/zen-images/layouts/single-toolbar.png (../../zen/images/layouts/single-toolbar.png) content/browser/zen-images/grain-bg.png (../../zen/images/grain-bg.png) content/browser/zen-images/note-indicator.svg (../../zen/images/note-indicator.svg) + content/browser/zen-images/boost-indicator.svg (../../zen/images/boost-indicator.svg) content/browser/zen-images/downloads/download.svg (../../zen/images/downloads/download.svg) content/browser/zen-images/downloads/archive.svg (../../zen/images/downloads/archive.svg) diff --git a/src/browser/base/content/zen-panels/boost-editor.inc b/src/browser/base/content/zen-panels/boost-editor.inc deleted file mode 100644 index a63a2d94ea..0000000000 --- a/src/browser/base/content/zen-panels/boost-editor.inc +++ /dev/null @@ -1,47 +0,0 @@ -# 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/. - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
\ No newline at end of file diff --git a/src/browser/base/content/zen-popupset.inc.xhtml b/src/browser/base/content/zen-popupset.inc.xhtml index d21b1ff662..e58a85c4c8 100644 --- a/src/browser/base/content/zen-popupset.inc.xhtml +++ b/src/browser/base/content/zen-popupset.inc.xhtml @@ -2,7 +2,6 @@ # 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 zen-panels/boost-editor.inc #include zen-panels/gradient-generator.inc #include zen-panels/emojis-picker.inc #include zen-panels/folders-search.inc diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 1d6061b6cb..6b9acde432 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -17,6 +17,22 @@ border-radius: 100% !important; } +.zen-boost { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush.svg') !important; +} + +.zen-boost::after { + content: ''; + position: absolute; + width: 85%; + height: 85%; + opacity: 1; + background: url('chrome://browser/content/zen-images/boost-indicator.svg') no-repeat; + z-index: 0; + pointer-events: none; + filter: invert(); +} + #back-button { list-style-image: url('back.svg') !important; } @@ -85,7 +101,8 @@ #appMenu-zoom-controls, #PanelUI-zen-gradient-generator-color-add, -#zen-site-data-new-addon-button { +#zen-site-data-new-addon-button, +.zen-create-new-boost { list-style-image: url('plus.svg') !important; } @@ -517,6 +534,20 @@ &[open] image { list-style-image: url('permissions-fill.svg'); } + + position: relative; +} + +#zen-site-data-icon-button[boosting]::after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + opacity: 1; + background: url('chrome://browser/content/zen-images/boost-indicator.svg') no-repeat; + transform: translateX(-20%); + z-index: 0; + pointer-events: none; } .geo-icon { diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 7cc8e261d7..47dc3a1b5a 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -57,7 +57,7 @@ export class nsZenBoostEditor { this.doc .getElementById('PanelUI-zen-boost-zap') - .addEventListener('click', (event) => console.error('Not implemented')); + .addEventListener('click', () => console.error('Not implemented')); this.doc .getElementById('PanelUI-zen-boost-disable') .addEventListener('click', this.onToggleDisable.bind(this)); @@ -84,7 +84,7 @@ export class nsZenBoostEditor { Services.obs.notifyObservers(null, 'zen-boosts-kill-editor'); } - observe(subject, topic, data) { + observe(subject, topic) { if (topic === 'zen-boosts-kill-editor') { this.window.close(); } @@ -263,14 +263,14 @@ export class nsZenBoostEditor { } // This toggles the color changes - onToggleDisable(event) { + onToggleDisable() { this.currentBoostData.enableColorBoost = !this.currentBoostData.enableColorBoost; this.updateButtonToggleVisuals(); this.updateCurrentBoost(); } - onToggleInvert(event) { + onToggleInvert() { this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; this.updateButtonToggleVisuals(); @@ -324,6 +324,9 @@ export class nsZenBoostEditor { loadBoost(domain) { this.currentBoostData = gZenBoostsManager.loadBoostFromStore(domain); + + // Initial save to register the boost + gZenBoostsManager.saveBoostToStore(null); this.doc.getElementById('PanelUI-zen-boost-name').value = this.currentBoostData.boostName; diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 2739267a1d..1d86ac723b 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -14,7 +14,7 @@ export class nsZenBoostsManager { } init() { - this.readBoostsFromStore(() => (initialized = true)); + this.readBoostsFromStore(() => (this.initialized = true)); } // Store Firefox Style Sheet Service and IO Service for later use @@ -57,10 +57,11 @@ export class nsZenBoostsManager { } deleteBoost(domain) { - if (this.registeredBoosts.has(domain)) { + if (this.registeredSheets.has(domain)) this.unregisterSheet(domain); + + if(this.registeredBoosts.has(domain)) this.registeredBoosts.delete(domain); - } } // Load a boost from a domain @@ -154,7 +155,7 @@ export class nsZenBoostsManager { // Checks if there is a boost registered for the currently open tab registeredBoostForDomain(domain) { - return this.registeredSheets.has(domain); + return this.registeredBoosts.has(domain); } // Checks if a boost can be created diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index 86dc90604a..bfc793c13f 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -50,6 +50,14 @@ var gZenUIManager = { this.onUrlbarSearchModeChanged.bind(this) ); + window.gBrowser.addProgressListener({ + onLocationChange: (aWebProgress) => { + if (aWebProgress.isTopLevel) { + this.onShownTabChange(); + } + }, + }); + gZenMediaController.init(); gZenVerticalTabsManager.init(); @@ -205,7 +213,9 @@ var gZenUIManager = { ); // Cleaning up on close - editor.window.addEventListener('unload', () => (this._openBoostEditor = null)); + editor.window.addEventListener('unload', () => { + this.checkIsTabBoosted(); + }); // Rather inconvenient window animation using moveTo // editor.addEventListener('load', () => { @@ -236,6 +246,10 @@ var gZenUIManager = { // Give the animator editor.gZenUIManager = this; + + // Update icon + this.checkIsTabBoosted(); + return editor; }, @@ -267,6 +281,25 @@ var gZenUIManager = { return this._tabsWrapper; }, + checkIsTabBoosted() { + const button = document.getElementById('zen-site-data-icon-button'); + + const tab = window.gBrowser.selectedTab; + const url = new URL(tab.linkedBrowser.currentURI.spec); + const domain = url.hostname; + + const { gZenBoostsManager } = ChromeUtils.importESModule( + 'resource:///modules/ZenBoostsManager.sys.mjs' + ); + + if (gZenBoostsManager.registeredBoostForDomain(domain)) button.setAttribute('boosting', 'true'); + else button.removeAttribute('boosting'); + }, + + onShownTabChange() { + this.checkIsTabBoosted(); + }, + onTabClose(event = undefined) { if (!event?.target?._closedInMultiselection) { this.updateTabsToolbar(); diff --git a/src/zen/images/boost-indicator.svg b/src/zen/images/boost-indicator.svg new file mode 100644 index 0000000000..4829b24b97 --- /dev/null +++ b/src/zen/images/boost-indicator.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + diff --git a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs index ad3382a11f..c124f69833 100644 --- a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs +++ b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs @@ -124,12 +124,91 @@ export class nsZenSiteDataPanel { } #preparePanel() { + this.#resetSiteOptionsList(); + this.#setSiteBoost(); this.#setSitePermissions(); this.#setSiteSecurityInfo(); this.#setSiteHeader(); this.#setAddonsOverflow(); } + #setSiteBoost() { + const list = this.document.getElementById('zen-site-data-settings-list'); + const tab = this.window.gBrowser.selectedTab; + const url = new URL(tab.linkedBrowser.currentURI.spec); + const domain = url.hostname; + const uri = this.window.gBrowser.currentURI; + + const { gZenBoostsManager } = ChromeUtils.importESModule( + 'resource:///modules/ZenBoostsManager.sys.mjs' + ); + + if (!gZenBoostsManager.canBoostSite(uri)) return; + + if (gZenBoostsManager.registeredBoostForDomain(domain)) { + let boostData = gZenBoostsManager.loadBoostFromStore(domain); + + list.appendChild( + this.#createGenericPanelItem( + 'zen-boost', + boostData.boostName, + 'Enabled', + 'zen-site-data-edit-boost' + ) + ); + } else { + list.appendChild( + this.#createGenericPanelItem( + 'zen-create-new-boost', + 'Create Boost', + 'For ' + domain, + 'zen-site-data-edit-boost' + ) + ); + } + } + + #createGenericPanelItem(iconClass, title, description, actionId) { + const container = this.document.createXULElement('hbox'); + container.classList.add('permission-popup-permission-item'); + container.id = actionId; + + container.setAttribute('align', 'center'); + container.setAttribute('role', 'group'); + container.setAttribute('state', 'custom'); + container.setAttribute('data-action-id', actionId); + + const img = this.document.createXULElement('toolbarbutton'); + img.classList.add('permission-popup-permission-icon', 'zen-site-data-permission-icon'); + img.setAttribute('closemenu', 'none'); + if (iconClass) { + img.classList.add(iconClass); + } + + const labelContainer = this.document.createXULElement('vbox'); + labelContainer.setAttribute('flex', '1'); + labelContainer.setAttribute('align', 'start'); + labelContainer.classList.add('permission-popup-permission-label-container'); + + const nameLabel = this.document.createXULElement('label'); + nameLabel.setAttribute('flex', '1'); + nameLabel.setAttribute('class', 'permission-popup-permission-label'); + nameLabel.textContent = title || ''; + labelContainer.appendChild(nameLabel); + + const stateLabel = this.document.createXULElement('label'); + stateLabel.setAttribute('class', 'zen-permission-popup-permission-state-label'); + stateLabel.textContent = description || ''; + labelContainer.appendChild(stateLabel); + + container.appendChild(img); + container.appendChild(labelContainer); + + container.addEventListener('click', this); + + return container; + } + #setAddonsOverflow() { const addons = this.document.getElementById('zen-site-data-addons'); if (addons.getBoundingClientRect().height > 420) { @@ -187,6 +266,11 @@ export class nsZenSiteDataPanel { return uri.scheme.startsWith('http'); } + #resetSiteOptionsList() { + const list = this.document.getElementById('zen-site-data-settings-list'); + list.innerHTML = ''; + } + #setSiteSecurityInfo() { const { gIdentityHandler } = this.window; const button = this.document.getElementById('zen-site-data-security-info'); @@ -319,7 +403,6 @@ export class nsZenSiteDataPanel { } const separator = this.document.createXULElement('toolbarseparator'); - list.innerHTML = ''; list.appendChild(separator); const settingElements = []; const crossSiteCookieElements = []; @@ -535,6 +618,17 @@ export class nsZenSiteDataPanel { } } + #onGenericClick(item) { + const id = item.id; + switch (id) { + case 'zen-site-data-edit-boost': { + this.window.gZenUIManager.openBoostWindow(); + this.panel.hidePopup(); + break; + } + } + } + #onClickEvent(event) { const id = event.target.id; switch (id) { @@ -549,13 +643,17 @@ export class nsZenSiteDataPanel { break; } default: { - const item = event.target.closest('.permission-popup-permission-item'); + const item = event.target.closest( + '.permission-popup-permission-item, .permission-popup-generic-item' + ); if (!item) { break; } const label = item.querySelector('.permission-popup-permission-label-container'); if (label?._permission) { this.#onPermissionClick(label); + } else { + this.#onGenericClick(item); } break; } diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index dc9e60bcdd..d5e820bb1c 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -2,8 +2,6 @@ * 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/. */ -import { gZenBoostsManager } from 'resource:///modules/ZenBoostsManager.sys.mjs'; - function isNotEmptyTab(window) { return !window.gBrowser.selectedTab.hasAttribute('zen-empty-tab'); } @@ -33,9 +31,15 @@ const globalActionsTemplate = [ const url = new URL(tab.linkedBrowser.currentURI.spec); const domain = url.hostname; const uri = window.gBrowser.currentURI; + + const { gZenBoostsManager } = ChromeUtils.importESModule( + 'resource:///modules/ZenBoostsManager.sys.mjs' + ); + return ( !tab.hasAttribute('zen-empty-tab') && - !gZenBoostsManager.registeredBoostForDomain(domain && gZenBoostsManager.canBoostSite(uri)) + !gZenBoostsManager.registeredBoostForDomain(domain) && + gZenBoostsManager.canBoostSite(uri) ); }, }, @@ -48,6 +52,11 @@ const globalActionsTemplate = [ const url = new URL(tab.linkedBrowser.currentURI.spec); const domain = url.hostname; const uri = window.gBrowser.currentURI; + + const { gZenBoostsManager } = ChromeUtils.importESModule( + 'resource:///modules/ZenBoostsManager.sys.mjs' + ); + return ( !tab.hasAttribute('zen-empty-tab') && gZenBoostsManager.registeredBoostForDomain(domain) && From 672461f2825c8acde29d20217f11d6aea4977fcc Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 13:23:31 +0100 Subject: [PATCH 10/33] feat: Make boost data be stored on the browsing context, b=no-bug, c=glance --- src/docshell/base/BrowsingContext-h.patch | 20 +++++++ .../BrowsingContext-webidl.patch | 13 ++++ src/layout/base/nsPresContext-cpp.patch | 11 ++-- src/layout/base/nsPresContext-h.patch | 14 +---- src/zen/boosts/ZenBoostsPresContext.h | 30 ---------- src/zen/boosts/components.conf | 2 +- src/zen/boosts/moz.build | 1 - src/zen/boosts/nsIZenBoostsBackend.idl | 36 +++++------ src/zen/boosts/nsZenBoostsBackend.cpp | 60 ++----------------- src/zen/boosts/nsZenBoostsBackend.h | 27 ++------- src/zen/glance/actors/ZenGlanceChild.sys.mjs | 1 + src/zen/glance/actors/ZenGlanceParent.sys.mjs | 1 + 12 files changed, 72 insertions(+), 144 deletions(-) create mode 100644 src/docshell/base/BrowsingContext-h.patch create mode 100644 src/dom/chrome-webidl/BrowsingContext-webidl.patch delete mode 100644 src/zen/boosts/ZenBoostsPresContext.h diff --git a/src/docshell/base/BrowsingContext-h.patch b/src/docshell/base/BrowsingContext-h.patch new file mode 100644 index 0000000000..2124e4c1e6 --- /dev/null +++ b/src/docshell/base/BrowsingContext-h.patch @@ -0,0 +1,20 @@ +diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h +index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..7db009990170297b5758c47571c72a9a48b78db1 100644 +--- a/docshell/base/BrowsingContext.h ++++ b/docshell/base/BrowsingContext.h +@@ -256,6 +256,7 @@ struct EmbedderColorSchemes { + FIELD(IsInBFCache, bool) \ + FIELD(HasRestoreData, bool) \ + FIELD(SessionStoreEpoch, uint32_t) \ ++ FIELD(ZenBoostsData, nscolor) \ + /* Whether we can execute scripts in this BrowsingContext. Has no effect \ + * unless scripts are also allowed in the parent WindowContext. */ \ + FIELD(AllowJavascript, bool) \ +@@ -650,6 +651,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { + + bool FullscreenAllowed() const; + ++ auto ZenBoostsData() const { return GetZenBoostsData(); } + float FullZoom() const { return GetFullZoom(); } + float TextZoom() const { return GetTextZoom(); } + diff --git a/src/dom/chrome-webidl/BrowsingContext-webidl.patch b/src/dom/chrome-webidl/BrowsingContext-webidl.patch new file mode 100644 index 0000000000..88b82cb040 --- /dev/null +++ b/src/dom/chrome-webidl/BrowsingContext-webidl.patch @@ -0,0 +1,13 @@ +diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl +index 9730cea647caedaeca4dcbe703e209d79a3440a7..ba0952428625b6a0f0e2f9546ccec5a7c6af0faf 100644 +--- a/dom/chrome-webidl/BrowsingContext.webidl ++++ b/dom/chrome-webidl/BrowsingContext.webidl +@@ -169,6 +169,8 @@ interface BrowsingContext { + + [SetterThrows] attribute float textZoom; + ++ [SetterThrows] attribute unsigned long zenBoostsData; ++ + // Override the dots-per-CSS-pixel scaling factor in this BrowsingContext + // and all of its descendants. May only be set on the top BC, and should + // only be set from the parent process. diff --git a/src/layout/base/nsPresContext-cpp.patch b/src/layout/base/nsPresContext-cpp.patch index 601436e305..e58bdea530 100644 --- a/src/layout/base/nsPresContext-cpp.patch +++ b/src/layout/base/nsPresContext-cpp.patch @@ -1,5 +1,5 @@ diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp -index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..f83e4ecb6379a3b12edcef152affa7030aa5f9aa 100644 +index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..faf802c4ddef7d9d670242dec5b580c8e9b42111 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -20,6 +20,7 @@ @@ -10,16 +10,17 @@ index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..f83e4ecb6379a3b12edcef152affa703 #include "mozilla/AnimationEventDispatcher.h" #include "mozilla/ContentBlockingAllowList.h" #include "mozilla/CycleCollectedJSContext.h" -@@ -914,6 +915,12 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { - SetOverrideDPPX(browsingContext->OverrideDPPX()); +@@ -915,6 +916,13 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { } + auto* top = browsingContext->Top(); ++ + nsCOMPtr zenBackend( + do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); + if (zenBackend) { -+ zenBackend->RecomputeBrowsingContextDependentData(this); ++ zenBackend->RecomputeBrowsingContextDependentData(this, top); + } + - auto* top = browsingContext->Top(); SetColorSchemeOverride([&] { auto overriden = top->PrefersColorSchemeOverride(); + if (overriden != PrefersColorSchemeOverride::None) { diff --git a/src/layout/base/nsPresContext-h.patch b/src/layout/base/nsPresContext-h.patch index 0c0bbea78c..88202ff07b 100644 --- a/src/layout/base/nsPresContext-h.patch +++ b/src/layout/base/nsPresContext-h.patch @@ -1,20 +1,12 @@ diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h -index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..63d2ff7975abf0af3316dc5533554633eaf2b2a5 100644 +index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..03f338d61c0a550407025b6736f80829d6f574de 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h -@@ -45,6 +45,7 @@ - #include "nsTHashSet.h" - #include "nsTHashtable.h" - #include "nsThreadUtils.h" -+#include "mozilla/ZenBoostsPresContext.h" - - class nsIPrintSettings; - class nsDocShell; -@@ -560,6 +561,7 @@ class nsPresContext : public nsISupports, +@@ -560,6 +560,7 @@ class nsPresContext : public nsISupports, void UpdateForcedColors(bool aNotify = true); public: -+ nsCOMPtr mZenBoostsPresContextData; ++ nscolor mZenBoostsPresContextData; float GetFullZoom() { return mFullZoom; } /** * Device full zoom differs from full zoom because it gets the zoom from diff --git a/src/zen/boosts/ZenBoostsPresContext.h b/src/zen/boosts/ZenBoostsPresContext.h deleted file mode 100644 index 2f7635fbf5..0000000000 --- a/src/zen/boosts/ZenBoostsPresContext.h +++ /dev/null @@ -1,30 +0,0 @@ -/* 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/. */ - -#ifndef mozilla_ZenBoostsPressContext_h__ -#define mozilla_ZenBoostsPressContext_h__ - -#include "nsColor.h" -#include "nsISupports.h" - -namespace zen { - -class ZenBoostsPresContextData final : public nsISupports { - ~ZenBoostsPresContextData() = default; - - public: - NS_DECL_ISUPPORTS - - nscolor mAccentColor; - bool mShouldBeApplied = true; - - explicit ZenBoostsPresContextData(nscolor aAccentColor) - : mAccentColor(aAccentColor) {} -}; - -using ZenBoostsMap = nsTHashMap>; - -} // namespace zen - -#endif // mozilla_ZenBoostsPressContext_h__ diff --git a/src/zen/boosts/components.conf b/src/zen/boosts/components.conf index 0aa962595e..d6b81daa17 100644 --- a/src/zen/boosts/components.conf +++ b/src/zen/boosts/components.conf @@ -10,6 +10,6 @@ Classes = [ 'type': 'zen::nsZenBoostsBackend', 'headers': ['mozilla/nsZenBoostsBackend.h'], 'js_name': 'boosts', -# 'processes': ProcessSelector.CONTENT_PROCESS_ONLY, + 'processes': ProcessSelector.CONTENT_PROCESS_ONLY, }, ] diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index 5a29502c69..f9158b5668 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -13,7 +13,6 @@ XPIDL_SOURCES += [ EXPORTS.mozilla += [ "nsZenBoostsBackend.h", - "ZenBoostsPresContext.h", ] SOURCES += [ diff --git a/src/zen/boosts/nsIZenBoostsBackend.idl b/src/zen/boosts/nsIZenBoostsBackend.idl index eed567051a..a7b1aa612c 100644 --- a/src/zen/boosts/nsIZenBoostsBackend.idl +++ b/src/zen/boosts/nsIZenBoostsBackend.idl @@ -4,28 +4,28 @@ #include "nsISupports.idl" -%{C++ -#include "nsColor.h" -%} - -native nscolor(nscolor); - /** * @brief Interface for Zen boosts backend. */ [scriptable, uuid(7d46aa4e-7486-4f77-ab47-81125f1a5723)] interface nsIZenBoostsBackend : nsISupports { - /** - * @brief Register a new Zen boost for a given domain. - * @param aDomain The domain to register the boost for. - * @param aAccentColor The accent color to use for the boost. - */ - void RegisterZenBoost(in AString aDomain, - in Array aAccentColor); + %{C++ + /* + * @brief Called when the presshell is entered. See nsDisplayListBuilder::EnterPresShell + * for context. + */ + auto onPressShellEntered(nsPresContext* aPresContext) -> void; + + /* + * @brief Called when the presshell is exited. + */ + auto onPressShellLeave(nsPresContext* aPresContext) -> void; - /** - * @brief Unregister a Zen boost for a given domain. - * @param aDomain The domain to unregister the boost for. - */ - void UnregisterZenBoost(in AString aDomain); + /* + * Recomputes the data dependent on the browsing context, like zoom and text + * zoom. We use it to store Zen boosts related data too. + */ + void RecomputeBrowsingContextDependentData(nsPresContext* aPresContext, + mozilla::dom::BrowsingContext* aBrowsingContext); + %} }; diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 348c91d8d9..6ab497bd1f 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -14,6 +14,7 @@ #include "mozilla/ServoStyleConstsInlines.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/BrowsingContext.h" namespace zen { @@ -28,7 +29,6 @@ static __inline int32_t clamp255(int32_t v) { // Use the macro to inject all of the definitions for nsISupports. NS_IMPL_ISUPPORTS(nsZenBoostsBackend, nsIZenBoostsBackend) -NS_IMPL_ISUPPORTS(ZenBoostsPresContextData, nsISupports) nsZenBoostsBackend::nsZenBoostsBackend() {}; @@ -50,38 +50,12 @@ auto nsZenBoostsBackend::onPressShellLeave(nsPresContext* aPresContext) -> void } auto nsZenBoostsBackend::RecomputeBrowsingContextDependentData( - nsPresContext* aPresContext) -> void { + nsPresContext* aPresContext, mozilla::dom::BrowsingContext* aBrowsingContext) -> void { if (!aPresContext || aPresContext->IsChrome()) { return; } - auto document = aPresContext->Document(); - if (!document) { - return; - } - - if (aPresContext->mZenBoostsPresContextData) { - if (aPresContext->mZenBoostsPresContextData->mShouldBeApplied) { - // If the boost data indicates it shouldn't be applied, skip. - // Parsing urls and doing checks can be expensive. - return; - } - // We don't have a boost for this domain anymore. - aPresContext->mZenBoostsPresContextData = nullptr; - return; - } - - // Check if we have a boost for this domain - nsIURI* baseURI = document->GetBaseURI(); - nsAutoCString host; - if (baseURI) { - baseURI->GetHost(host); - nsString hostWStr; - CopyUTF8toUTF16(host, hostWStr); - if (auto boostData = mZenBoostsMap.Get(hostWStr)) { - aPresContext->mZenBoostsPresContextData = boostData; - } - } + aPresContext->mZenBoostsPresContextData = aBrowsingContext->ZenBoostsData(); } auto nsZenBoostsBackend::ResolveStyleColor( @@ -91,7 +65,7 @@ auto nsZenBoostsBackend::ResolveStyleColor( if (zenBoosts) { if (auto presContext = zenBoosts->mCurrentPresContext) { - if (auto data = presContext->mZenBoostsPresContextData) { + if (auto accentNS = presContext->mZenBoostsPresContextData) { // Apply a filter-like tint: // - Preserve the original color's perceived luminance // - Map hue/chroma toward the accent by scaling the accent's RGB @@ -100,7 +74,6 @@ auto nsZenBoostsBackend::ResolveStyleColor( // Convert both colors to nscolor to access channels nscolor originalNS = aColor.ToColor(); - nscolor accentNS = data->mAccentColor; auto r1 = NS_GET_R(originalNS); auto g1 = NS_GET_G(originalNS); @@ -132,29 +105,4 @@ auto nsZenBoostsBackend::ResolveStyleColor( return aColor; } -nsresult nsZenBoostsBackend::RegisterZenBoost( - const nsAString& aDomain, const nsTArray& aAccentColor) { - if (aAccentColor.IsEmpty() || aAccentColor.Length() < 3) { - return NS_ERROR_INVALID_ARG; - } - - nscolor accentColor = NS_RGB(aAccentColor[0], aAccentColor[1], aAccentColor[2]); - RefPtr data = - new ZenBoostsPresContextData(accentColor); - mZenBoostsMap.InsertOrUpdate(aDomain, data); - return NS_OK; -} - -nsresult nsZenBoostsBackend::UnregisterZenBoost(const nsAString& aDomain) { - // We do need to mark the data as not to be applied, so that - // existing prescontexts don't try to re-apply it. - if (auto boostData = mZenBoostsMap.Get(aDomain)) { - boostData->mShouldBeApplied = false; - } - mZenBoostsMap.Remove(aDomain); - return NS_OK; -} - -ZenBoostsMap nsZenBoostsBackend::mZenBoostsMap{}; - } // namespace zen diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h index 7aa3b8bb21..3d7667561b 100644 --- a/src/zen/boosts/nsZenBoostsBackend.h +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -8,7 +8,6 @@ #include "nsColor.h" #include "nsPresContext.h" #include "nsIZenBoostsBackend.h" -#include "ZenBoostsPresContext.h" #include "mozilla/RefPtr.h" @@ -23,23 +22,6 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { public: explicit nsZenBoostsBackend(); - /* - * @brief Called when the presshell is entered. See nsDisplayListBuilder::EnterPresShell - * for context. - */ - auto onPressShellEntered(nsPresContext* aPresContext) -> void; - - /* - * @brief Called when the presshell is exited. - */ - auto onPressShellLeave(nsPresContext* aPresContext) -> void; - - /** - * Recomputes the data dependent on the browsing context, like zoom and text - * zoom. We use it to store Zen boosts related data too. - */ - void RecomputeBrowsingContextDependentData(nsPresContext* aPresContext); - /** * @brief Resolve a StyleAbsoluteColor to take into account Zen boosts. * @param aColor The color to resolve. @@ -47,6 +29,11 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { */ static auto ResolveStyleColor(mozilla::StyleAbsoluteColor aColor) -> mozilla::StyleAbsoluteColor; + // Overrides + auto onPressShellEntered(nsPresContext* aPresContext) -> void; + auto onPressShellLeave(nsPresContext* aPresContext) -> void; + void RecomputeBrowsingContextDependentData(nsPresContext* aPresContext, + mozilla::dom::BrowsingContext* aBrowsingContext); NS_DECL_NSIZENBOOSTSBACKEND private: ~nsZenBoostsBackend() = default; @@ -55,10 +42,6 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { * The presshell of the current document being rendered. */ RefPtr mCurrentPresContext; - /** - * A map containing what boosts should be applied to which domains. - */ - static ZenBoostsMap mZenBoostsMap; }; } // namespace zen diff --git a/src/zen/glance/actors/ZenGlanceChild.sys.mjs b/src/zen/glance/actors/ZenGlanceChild.sys.mjs index 51cd6cb176..595697fbdc 100644 --- a/src/zen/glance/actors/ZenGlanceChild.sys.mjs +++ b/src/zen/glance/actors/ZenGlanceChild.sys.mjs @@ -1,6 +1,7 @@ // 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/. + export class ZenGlanceChild extends JSWindowActorChild { #activationMethod; diff --git a/src/zen/glance/actors/ZenGlanceParent.sys.mjs b/src/zen/glance/actors/ZenGlanceParent.sys.mjs index 159a5f5598..18ef441ee9 100644 --- a/src/zen/glance/actors/ZenGlanceParent.sys.mjs +++ b/src/zen/glance/actors/ZenGlanceParent.sys.mjs @@ -1,6 +1,7 @@ // 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/. + export class ZenGlanceParent extends JSWindowActorParent { constructor() { super(); From f1ce0da18b9c9ef9d55c277645e16d6bd5d31067 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 31 Oct 2025 13:44:02 +0100 Subject: [PATCH 11/33] chore: use better icons --- src/browser/base/content/zen-assets.inc.xhtml | 1 - .../themes/shared/zen-icons/common/selectable/block.svg | 2 +- .../zen-icons/common/selectable/boost-filled-small.svg | 2 ++ .../themes/shared/zen-icons/common/selectable/boost.svg | 2 ++ .../themes/shared/zen-icons/common/selectable/bulb.svg | 2 +- .../shared/zen-icons/common/selectable/controls.svg | 2 ++ .../shared/zen-icons/common/selectable/paintbrush.svg | 2 -- .../themes/shared/zen-icons/common/selectable/zap.svg | 2 +- src/browser/themes/shared/zen-icons/icons.css | 2 +- src/browser/themes/shared/zen-icons/jar.inc.mn | 4 +++- src/browser/themes/shared/zen-icons/lin/paintbrush.svg | 2 -- src/zen/boosts/zen-boosts.css | 8 ++++++-- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 4 ++-- 13 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/boost.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/controls.svg delete mode 100644 src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg delete mode 100644 src/browser/themes/shared/zen-icons/lin/paintbrush.svg diff --git a/src/browser/base/content/zen-assets.inc.xhtml b/src/browser/base/content/zen-assets.inc.xhtml index a925d400b0..91b136f2ec 100644 --- a/src/browser/base/content/zen-assets.inc.xhtml +++ b/src/browser/base/content/zen-assets.inc.xhtml @@ -30,7 +30,6 @@ - # Startup "preloaded" scripts that requre globals such as gBrowser and gURLBar diff --git a/src/browser/themes/shared/zen-icons/common/selectable/block.svg b/src/browser/themes/shared/zen-icons/common/selectable/block.svg index 2fc9cf026f..7795483b41 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/block.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/block.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg b/src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg new file mode 100644 index 0000000000..cce6691a1f --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/boost.svg b/src/browser/themes/shared/zen-icons/common/selectable/boost.svg new file mode 100644 index 0000000000..1dd394ea5b --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/boost.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg index 09a60769e8..740ae9ba29 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/controls.svg b/src/browser/themes/shared/zen-icons/common/selectable/controls.svg new file mode 100644 index 0000000000..ee82da27f9 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/controls.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg deleted file mode 100644 index 46d4247596..0000000000 --- a/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg +++ /dev/null @@ -1,2 +0,0 @@ -#filter dumbComments emptyLines substitution - \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg index 18368b3bb5..efb3813698 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 6b9acde432..8dc66e220c 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -18,7 +18,7 @@ } .zen-boost { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/boost-filled-small.svg') !important; } .zen-boost::after { diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index 89042fbe21..4ea9779d6a 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -471,6 +471,8 @@ * skin/classic/browser/zen-icons/selectable/block.svg (../shared/zen-icons/common/selectable/block.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/boost-filled-small.svg (../shared/zen-icons/common/selectable/boost-filled-small.svg) +* skin/classic/browser/zen-icons/selectable/boost.svg (../shared/zen-icons/common/selectable/boost.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) @@ -486,6 +488,7 @@ * skin/classic/browser/zen-icons/selectable/code.svg (../shared/zen-icons/common/selectable/code.svg) * skin/classic/browser/zen-icons/selectable/coins.svg (../shared/zen-icons/common/selectable/coins.svg) * skin/classic/browser/zen-icons/selectable/construct.svg (../shared/zen-icons/common/selectable/construct.svg) +* skin/classic/browser/zen-icons/selectable/controls.svg (../shared/zen-icons/common/selectable/controls.svg) * skin/classic/browser/zen-icons/selectable/cutlery.svg (../shared/zen-icons/common/selectable/cutlery.svg) * skin/classic/browser/zen-icons/selectable/egg.svg (../shared/zen-icons/common/selectable/egg.svg) * skin/classic/browser/zen-icons/selectable/extension-puzzle.svg (../shared/zen-icons/common/selectable/extension-puzzle.svg) @@ -521,7 +524,6 @@ * skin/classic/browser/zen-icons/selectable/navigate.svg (../shared/zen-icons/common/selectable/navigate.svg) * skin/classic/browser/zen-icons/selectable/nuclear.svg (../shared/zen-icons/common/selectable/nuclear.svg) * skin/classic/browser/zen-icons/selectable/page.svg (../shared/zen-icons/common/selectable/page.svg) -* skin/classic/browser/zen-icons/selectable/paintbrush.svg (../shared/zen-icons/common/selectable/paintbrush.svg) * skin/classic/browser/zen-icons/selectable/palette.svg (../shared/zen-icons/common/selectable/palette.svg) * skin/classic/browser/zen-icons/selectable/paw.svg (../shared/zen-icons/common/selectable/paw.svg) * skin/classic/browser/zen-icons/selectable/people.svg (../shared/zen-icons/common/selectable/people.svg) diff --git a/src/browser/themes/shared/zen-icons/lin/paintbrush.svg b/src/browser/themes/shared/zen-icons/lin/paintbrush.svg deleted file mode 100644 index 46d4247596..0000000000 --- a/src/browser/themes/shared/zen-icons/lin/paintbrush.svg +++ /dev/null @@ -1,2 +0,0 @@ -#filter dumbComments emptyLines substitution - \ No newline at end of file diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 8591b0a795..c1f258d3fd 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -31,8 +31,8 @@ } #PanelUI-zen-boost-head-wrapper { - margin-top: -10px; - margin-bottom: -10px; + margin-top: -14px; + margin-bottom: -14px; & input { height: 26px !important; @@ -118,6 +118,10 @@ width: auto; height: auto !important; + padding: 6px 4px 6px 4px; + + background-color: #AAAAAA22; + margin: auto; } } diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index d5e820bb1c..8c0ae29ded 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -25,7 +25,7 @@ const globalActionsTemplate = [ { label: 'Create New Boost', command: 'cmd_zenOpenBoostEditor', - icon: 'chrome://browser/skin/zen-icons/selectable/paintbrush.svg', + icon: 'chrome://browser/skin/zen-icons/selectable/boost.svg', isAvailable: (window) => { const tab = window.gBrowser.selectedTab; const url = new URL(tab.linkedBrowser.currentURI.spec); @@ -46,7 +46,7 @@ const globalActionsTemplate = [ { label: 'Edit Boost', command: 'cmd_zenOpenBoostEditor', - icon: 'chrome://browser/skin/zen-icons/selectable/paintbrush.svg', + icon: 'chrome://browser/skin/zen-icons/selectable/boost.svg', isAvailable: (window) => { const tab = window.gBrowser.selectedTab; const url = new URL(tab.linkedBrowser.currentURI.spec); From 4f337ed51f59edf2c05823822820961f4f62a158 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 15:45:25 +0100 Subject: [PATCH 12/33] feat: Listen for changes for the browser context, b=no-bug, c=common, glance --- src/docshell/base/BrowsingContext-h.patch | 10 +++++- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 38 ++++++++++++++++++++ src/zen/boosts/moz.build | 4 +++ src/zen/boosts/nsZenBoostsBackend.cpp | 16 +++++++-- src/zen/common/ZenActorsManager.sys.mjs | 11 ++++++ src/zen/glance/moz.build | 1 - 6 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 src/zen/boosts/actors/ZenBoostsChild.sys.mjs diff --git a/src/docshell/base/BrowsingContext-h.patch b/src/docshell/base/BrowsingContext-h.patch index 2124e4c1e6..77cdbe85b3 100644 --- a/src/docshell/base/BrowsingContext-h.patch +++ b/src/docshell/base/BrowsingContext-h.patch @@ -1,5 +1,5 @@ diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h -index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..7db009990170297b5758c47571c72a9a48b78db1 100644 +index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..68bcf7c87e41d08a71b000e4a7e4304814928c75 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h @@ -256,6 +256,7 @@ struct EmbedderColorSchemes { @@ -18,3 +18,11 @@ index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..7db009990170297b5758c47571c72a9a float FullZoom() const { return GetFullZoom(); } float TextZoom() const { return GetTextZoom(); } +@@ -1167,6 +1169,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { + } + + void DidSet(FieldIndex, uint32_t aOldValue); ++ void DidSet(FieldIndex, nscolor aOldValue); + + using CanSetResult = syncedcontext::CanSetResult; + diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs new file mode 100644 index 0000000000..3cda73e6ee --- /dev/null +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -0,0 +1,38 @@ +// 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/. + +export class ZenBoostsChild extends JSWindowActorChild { + constructor() { + super(); + } + + async handleEvent(event) { + switch (event.type) { + case 'load': + await this.#onLoad(event); + break; + default: + } + } + + /** + * Inverse of https://searchfox.org/firefox-main/rev/0b21972a78f8915f73ce5579eeee2aa8c9c7d67e/gfx/src/nsColor.h#18-21 + * Converts [r, g, b] array to NSColor integer + */ + #rgbToNSColor([r, g, b]) { + return (b << 16) | (g << 8) | r; + } + + async #getBoostForPage() { + const browsingContext = this.browsingContext; + if (!browsingContext) { + return null; + } + const url = browsingContext.currentWindowGlobal.documentURI.spec; + } + + async #onLoad(event) { + this.applyBoostIfAvailable(); + } +} diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index f9158b5668..35a95a8948 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -7,6 +7,10 @@ EXTRA_JS_MODULES += [ "ZenBoostsManager.sys.mjs", ] +FINAL_TARGET_FILES.actors += [ + "actors/ZenBoostsChild.sys.mjs", +] + XPIDL_SOURCES += [ "nsIZenBoostsBackend.idl", ] diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 6ab497bd1f..0046d5d2ec 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -16,8 +16,19 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/BrowsingContext.h" -namespace zen { +using BrowsingContext = mozilla::dom::BrowsingContext; +using BoostData = nscolor; // For now, Zen boosts data is just a color. + +void BrowsingContext::DidSet(FieldIndex, + BoostData aOldValue) { + MOZ_ASSERT(IsTop()); + if (ZenBoostsData() == aOldValue) { + return; + } + PresContextAffectingFieldChanged(); +} +namespace zen { namespace { // llvm x86 is poor at ternary operator, so use branchless min/max. @@ -25,11 +36,10 @@ static __inline int32_t clamp255(int32_t v) { return (((255 - (v)) >> 31) | (v)) & 255; } -} +} // namespace // Use the macro to inject all of the definitions for nsISupports. NS_IMPL_ISUPPORTS(nsZenBoostsBackend, nsIZenBoostsBackend) - nsZenBoostsBackend::nsZenBoostsBackend() {}; auto nsZenBoostsBackend::onPressShellEntered(nsPresContext* aPresContext) -> void { diff --git a/src/zen/common/ZenActorsManager.sys.mjs b/src/zen/common/ZenActorsManager.sys.mjs index 817cfef790..b286bec4a3 100644 --- a/src/zen/common/ZenActorsManager.sys.mjs +++ b/src/zen/common/ZenActorsManager.sys.mjs @@ -51,6 +51,17 @@ let JSWINDOWACTORS = { matches: ['*://*/*'], enablePreference: 'zen.glance.enabled', }, + ZenBoosts: { + child: { + esModuleURI: 'resource:///actors/ZenBoostsChild.sys.mjs', + events: { + load: { mozSystemGroup: true, capture: true }, + }, + }, + allFrames: true, + matches: ['*://*/*'], + enablePreference: 'zen.boosts.enabled', + }, }; export let gZenActorsManager = { diff --git a/src/zen/glance/moz.build b/src/zen/glance/moz.build index 9588459dc1..39567ca4cf 100644 --- a/src/zen/glance/moz.build +++ b/src/zen/glance/moz.build @@ -1,4 +1,3 @@ -# # 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/. From afd3b0da1d3dd8ee8d2459c99e2a74370f771294 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 15:49:26 +0100 Subject: [PATCH 13/33] feat: Propagate data into the current context, not the top one, b=no-bug, c=no-component --- src/layout/base/nsPresContext-cpp.patch | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/layout/base/nsPresContext-cpp.patch b/src/layout/base/nsPresContext-cpp.patch index e58bdea530..bf6a815236 100644 --- a/src/layout/base/nsPresContext-cpp.patch +++ b/src/layout/base/nsPresContext-cpp.patch @@ -1,5 +1,5 @@ diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp -index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..faf802c4ddef7d9d670242dec5b580c8e9b42111 100644 +index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..e1644feddd275d1ff9060832b70e810a4f25dbfd 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -20,6 +20,7 @@ @@ -10,17 +10,16 @@ index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..faf802c4ddef7d9d670242dec5b580c8 #include "mozilla/AnimationEventDispatcher.h" #include "mozilla/ContentBlockingAllowList.h" #include "mozilla/CycleCollectedJSContext.h" -@@ -915,6 +916,13 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { +@@ -914,6 +915,12 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { + SetOverrideDPPX(browsingContext->OverrideDPPX()); } - auto* top = browsingContext->Top(); -+ + nsCOMPtr zenBackend( + do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); + if (zenBackend) { -+ zenBackend->RecomputeBrowsingContextDependentData(this, top); ++ zenBackend->RecomputeBrowsingContextDependentData(this, browsingContext); + } + + auto* top = browsingContext->Top(); SetColorSchemeOverride([&] { auto overriden = top->PrefersColorSchemeOverride(); - if (overriden != PrefersColorSchemeOverride::None) { From b8ef44253fd33df60d349c4cc67db692e72f4d06 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 17:00:18 +0100 Subject: [PATCH 14/33] feat: Hint for a restyle after changing boost colors, b=no-bug, c=no-component --- src/layout/base/nsPresContext-cpp.patch | 11 ++++++----- src/zen/boosts/nsZenBoostsBackend.cpp | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/layout/base/nsPresContext-cpp.patch b/src/layout/base/nsPresContext-cpp.patch index bf6a815236..e58bdea530 100644 --- a/src/layout/base/nsPresContext-cpp.patch +++ b/src/layout/base/nsPresContext-cpp.patch @@ -1,5 +1,5 @@ diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp -index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..e1644feddd275d1ff9060832b70e810a4f25dbfd 100644 +index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..faf802c4ddef7d9d670242dec5b580c8e9b42111 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -20,6 +20,7 @@ @@ -10,16 +10,17 @@ index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..e1644feddd275d1ff9060832b70e810a #include "mozilla/AnimationEventDispatcher.h" #include "mozilla/ContentBlockingAllowList.h" #include "mozilla/CycleCollectedJSContext.h" -@@ -914,6 +915,12 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { - SetOverrideDPPX(browsingContext->OverrideDPPX()); +@@ -915,6 +916,13 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { } + auto* top = browsingContext->Top(); ++ + nsCOMPtr zenBackend( + do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); + if (zenBackend) { -+ zenBackend->RecomputeBrowsingContextDependentData(this, browsingContext); ++ zenBackend->RecomputeBrowsingContextDependentData(this, top); + } + - auto* top = browsingContext->Top(); SetColorSchemeOverride([&] { auto overriden = top->PrefersColorSchemeOverride(); + if (overriden != PrefersColorSchemeOverride::None) { diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 0046d5d2ec..eb8091c62d 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -12,6 +12,7 @@ #include "mozilla/ServoStyleConsts.h" #include "mozilla/ServoStyleConstsInlines.h" +#include "mozilla/MediaFeatureChange.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/BrowsingContext.h" @@ -65,7 +66,15 @@ auto nsZenBoostsBackend::RecomputeBrowsingContextDependentData( return; } + auto previousData = aPresContext->mZenBoostsPresContextData; aPresContext->mZenBoostsPresContextData = aBrowsingContext->ZenBoostsData(); + if (previousData != aPresContext->mZenBoostsPresContextData) { + // Lets ask the prescontext to restyle the document + aPresContext->MediaFeatureValuesChanged( + {mozilla::RestyleHint::RecascadeSubtree(), NS_STYLE_HINT_VISUAL, + mozilla::MediaFeatureChangeReason::PreferenceChange}, + mozilla::MediaFeatureChangePropagation::JustThisDocument); + } } auto nsZenBoostsBackend::ResolveStyleColor( From d8da71d03db6e8a83b98f673a9f5fcf4634f44c1 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 31 Oct 2025 19:17:46 +0100 Subject: [PATCH 15/33] feat: bridge communication using child actor --- src/zen/boosts/ZenBoostsEditor.sys.mjs | 32 +++++----- src/zen/boosts/ZenBoostsManager.sys.mjs | 35 +---------- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 62 +++++++++++++++++++- src/zen/common/ZenActorsManager.sys.mjs | 1 + 4 files changed, 77 insertions(+), 53 deletions(-) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 47dc3a1b5a..c2d21201fb 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -29,47 +29,47 @@ export class nsZenBoostEditor { this.window.addEventListener('unload', () => this.handleClose()); this.doc - .getElementById('PanelUI-zen-boost-font-arial') + .getElementById('zen-boost-font-arial') .addEventListener('click', (event) => this.onFontChange(event, 'Arial, sans-serif')); this.doc - .getElementById('PanelUI-zen-boost-font-serif') + .getElementById('zen-boost-font-serif') .addEventListener('click', (event) => this.onFontChange(event, "'Times New Roman', serif")); this.doc - .getElementById('PanelUI-zen-boost-font-mono') + .getElementById('zen-boost-font-mono') .addEventListener('click', (event) => this.onFontChange(event, "'Courier New', monospace")); this.doc - .getElementById('PanelUI-zen-boost-font-georgia') + .getElementById('zen-boost-font-georgia') .addEventListener('click', (event) => this.onFontChange(event, "'Georgia', serif")); this.doc - .getElementById('PanelUI-zen-boost-font-tahoma') + .getElementById('zen-boost-font-tahoma') .addEventListener('click', (event) => this.onFontChange(event, 'Tahoma')); this.doc - .getElementById('PanelUI-zen-boost-font-verdana') + .getElementById('zen-boost-font-verdana') .addEventListener('click', (event) => this.onFontChange(event, 'Verdana')); this.doc - .getElementById('PanelUI-zen-boost-font-comic') + .getElementById('zen-boost-font-comic') .addEventListener('click', (event) => this.onFontChange(event, "'Comic Sans MS'")); this.doc - .getElementById('PanelUI-zen-boost-font-corsiva') + .getElementById('zen-boost-font-corsiva') .addEventListener('click', (event) => this.onFontChange(event, "'Monotype Corsiva, cursive'") ); this.doc - .getElementById('PanelUI-zen-boost-zap') + .getElementById('zen-boost-zap') .addEventListener('click', () => console.error('Not implemented')); this.doc - .getElementById('PanelUI-zen-boost-disable') + .getElementById('zen-boost-disable') .addEventListener('click', this.onToggleDisable.bind(this)); this.doc - .getElementById('PanelUI-zen-boost-invert') + .getElementById('zen-boost-invert') .addEventListener('click', this.onToggleInvert.bind(this)); this.doc - .getElementById('PanelUI-zen-boost-delete') + .getElementById('zen-boost-delete') .addEventListener('click', this.onDeleteBoost.bind(this)); this.doc - .getElementById('PanelUI-zen-boost-name') + .getElementById('zen-boost-name') .addEventListener('input', (e) => (this.currentBoostData.boostName = e.target.value)); this.initialized = true; @@ -278,8 +278,8 @@ export class nsZenBoostEditor { } updateButtonToggleVisuals() { - const invertButton = this.doc.getElementById('PanelUI-zen-boost-invert'); - const disableButton = this.doc.getElementById('PanelUI-zen-boost-disable'); + const invertButton = this.doc.getElementById('zen-boost-invert'); + const disableButton = this.doc.getElementById('zen-boost-disable'); const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); if (this.currentBoostData.smartInvert) invertButton.classList.add('zen-boost-button-active'); @@ -328,7 +328,7 @@ export class nsZenBoostEditor { // Initial save to register the boost gZenBoostsManager.saveBoostToStore(null); - this.doc.getElementById('PanelUI-zen-boost-name').value = this.currentBoostData.boostName; + this.doc.getElementById('zen-boost-name').value = this.currentBoostData.boostName; const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); if (this.currentBoostData.dotPos.x == null || this.currentBoostData.dotPos.y == null) diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 1d86ac723b..e1e2afe06f 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -82,10 +82,7 @@ export class nsZenBoostsManager { if (this.registeredBoosts.has(dom)) { boostData = this.registeredBoosts.get(dom); - // console.log('Boost found for domain ', dom, boostData); } - // else - // console.log('Boost not found'); return boostData; } @@ -101,8 +98,7 @@ export class nsZenBoostsManager { `; } - // TODO: Send colors to boosts backend - + Services.obs.notifyObservers(null, 'zen-boosts-update'); this.registerCSSForDomain(fontFamily, boost.domain); } @@ -162,35 +158,6 @@ export class nsZenBoostsManager { canBoostSite(uri) { return uri.schemeIs('http') || uri.schemeIs('https'); } - - /** - * From ZenGradientGenerator.mjs - * Converts an HSL color value to RGB. Conversion formula - * adapted from https://en.wikipedia.org/wiki/HSL_color_space. - * Assumes h, s, and l are contained in the set [0, 1] and - * returns r, g, and b in the set [0, 255]. - * - * @param {number} h The hue - * @param {number} s The saturation - * @param {number} l The lightness - * @return {Array} The RGB representation - */ - hslToRgb(h, s, l) { - const { round } = Math; - let r, g, b; - - if (s === 0) { - r = g = b = l; // achromatic - } else { - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = this.hueToRgb(p, q, h + 1 / 3); - g = this.hueToRgb(p, q, h); - b = this.hueToRgb(p, q, h - 1 / 3); - } - - return [round(r * 255), round(g * 255), round(b * 255)]; - } } export const gZenBoostsManager = new nsZenBoostsManager(); diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 3cda73e6ee..150edd0c71 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -5,6 +5,14 @@ export class ZenBoostsChild extends JSWindowActorChild { constructor() { super(); + + Services.obs.addObserver(this, 'zen-boosts-update'); + } + + observe(subject, topic) { + if (topic === 'zen-boosts-update') { + this.#applyBoostForPageIfAvailable(); + } } async handleEvent(event) { @@ -24,15 +32,63 @@ export class ZenBoostsChild extends JSWindowActorChild { return (b << 16) | (g << 8) | r; } - async #getBoostForPage() { + /** + * From ZenGradientGenerator.mjs + * Converts an HSL color value to RGB. Conversion formula + * adapted from https://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param {number} h The hue + * @param {number} s The saturation + * @param {number} l The lightness + * @return {Array} The RGB representation + */ + #hslToRgb(h, s, l) { + const { round } = Math; + let r, g, b; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = this.hueToRgb(p, q, h + 1 / 3); + g = this.hueToRgb(p, q, h); + b = this.hueToRgb(p, q, h - 1 / 3); + } + + return [round(r * 255), round(g * 255), round(b * 255)]; + } + + async #applyBoostForPageIfAvailable() { const browsingContext = this.browsingContext; if (!browsingContext) { return null; } - const url = browsingContext.currentWindowGlobal.documentURI.spec; + + const url = new URL(browsingContext.currentWindowGlobal.documentURI.spec); + const domain = url.hostname; + + const { gZenBoostsManager } = ChromeUtils.importESModule( + 'resource:///modules/ZenBoostsManager.sys.mjs' + ); + + if(gZenBoostsManager.registeredBoostForDomain(domain)){ + const boostData = gZenBoostsManager.loadBoostFromStore(domain); + + window.gBrowser.selectedBrowser.browsingContext.prefersColorSchemeOverride = boostData.smartInvert ? "light" : "none"; + if(boostData.enableColorBoost){ + const hslColor = this.#hslToRgb(boostData.dotAngleDeg, boostData.dotDistance, 60); + const nsColor = this.#rgbToNSColor(hslColor[0], hslColor[1], hslColor[2]); + window.gBrowser.selectedBrowser.browsingContext.zenBoostsData = nsColor; + } + else + window.gBrowser.selectedBrowser.browsingContext.zenBoostsData = null; + } } async #onLoad(event) { - this.applyBoostIfAvailable(); + this.#applyBoostForPageIfAvailable(); } } diff --git a/src/zen/common/ZenActorsManager.sys.mjs b/src/zen/common/ZenActorsManager.sys.mjs index b286bec4a3..27c02dc4ea 100644 --- a/src/zen/common/ZenActorsManager.sys.mjs +++ b/src/zen/common/ZenActorsManager.sys.mjs @@ -57,6 +57,7 @@ let JSWINDOWACTORS = { events: { load: { mozSystemGroup: true, capture: true }, }, + observers: ["zen-boosts-update"], }, allFrames: true, matches: ['*://*/*'], From d0b806243f316d55b0d67e0fca061cd20431938f Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 31 Oct 2025 19:23:47 +0100 Subject: [PATCH 16/33] feat: Change UI --- locales/en-US/browser/browser/zen-boosts.ftl | 26 +++--- .../zen-icons/common/selectable/block.svg | 2 +- .../zen-icons/common/selectable/boost.svg | 2 +- .../zen-icons/common/selectable/bulb.svg | 2 +- .../zen-icons/common/selectable/controls.svg | 2 +- .../zen-icons/common/selectable/zap.svg | 2 +- src/browser/themes/shared/zen-icons/icons.css | 12 +-- src/zen/boosts/zen-boost-editor.xhtml | 52 +++++------ src/zen/boosts/zen-boosts.css | 86 +++++++++++-------- 9 files changed, 100 insertions(+), 86 deletions(-) diff --git a/locales/en-US/browser/browser/zen-boosts.ftl b/locales/en-US/browser/browser/zen-boosts.ftl index ea5ba791fe..8c252b4761 100644 --- a/locales/en-US/browser/browser/zen-boosts.ftl +++ b/locales/en-US/browser/browser/zen-boosts.ftl @@ -1,33 +1,33 @@ zen-panel-ui-boost-create = .label = New Boost -PanelUI-zen-boost-invert = +zen-boost-invert = .tooltiptext = Smart Invert Colors -PanelUI-zen-boost-zap = +zen-boost-zap = .tooltiptext = Zap Elements -PanelUI-zen-boost-disable = +zen-boost-disable = .tooltiptext = Disable Color Adjustments -PanelUI-zen-boost-font-arial = +zen-boost-font-arial = .tooltiptext = Arial -PanelUI-zen-boost-font-serif = +zen-boost-font-serif = .tooltiptext = Sans Serif -PanelUI-zen-boost-font-mono = +zen-boost-font-mono = .tooltiptext = Monospace -PanelUI-zen-boost-font-georgia = +zen-boost-font-georgia = .tooltiptext = Georgia -PanelUI-zen-boost-font-comic = +zen-boost-font-comic = .tooltiptext = Comic Sans MS -PanelUI-zen-boost-font-tahoma = +zen-boost-font-tahoma = .tooltiptext = Tahoma -PanelUI-zen-boost-font-verdana = +zen-boost-font-verdana = .tooltiptext = Verdana -PanelUI-zen-boost-font-corsiva = +zen-boost-font-corsiva = .tooltiptext = Corsiva -PanelUI-zen-boost-name = +zen-boost-name = .tooltiptext = Change Boost Name -PanelUI-zen-boost-delete = +zen-boost-delete = .tooltiptext = Delete Boost zen-panel-ui-boosts-saved-message = Successfully saved the boost! diff --git a/src/browser/themes/shared/zen-icons/common/selectable/block.svg b/src/browser/themes/shared/zen-icons/common/selectable/block.svg index 7795483b41..a4074ed6b4 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/block.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/block.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/boost.svg b/src/browser/themes/shared/zen-icons/common/selectable/boost.svg index 1dd394ea5b..9fb906dcbc 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/boost.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/boost.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg index 740ae9ba29..7dd48bdba2 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/controls.svg b/src/browser/themes/shared/zen-icons/common/selectable/controls.svg index ee82da27f9..6defe9db80 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/controls.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/controls.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg index efb3813698..6f90b8f1dd 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 8dc66e220c..51dd1ba8d8 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -50,15 +50,15 @@ .zen-glance-sidebar-close, .zen-theme-picker-custom-list-item-remove, #appMenu-quit-button2, -#PanelUI-zen-boost-delete { +#zen-boost-delete { list-style-image: url('close.svg') !important; } -#PanelUI-zen-emojis-picker-none { +#zen-emojis-picker-none { list-style-image: url('trash.svg'); } -#PanelUI-zen-gradient-generator-color-remove { +#zen-gradient-generator-color-remove { list-style-image: url('unpin.svg') !important; } @@ -978,14 +978,14 @@ fill-opacity: 0.7; } -#PanelUI-zen-boost-zap { +#zen-boost-zap { list-style-image: url('chrome://browser/skin/zen-icons/selectable/zap.svg') !important; } -#PanelUI-zen-boost-invert { +#zen-boost-invert { list-style-image: url('chrome://browser/skin/zen-icons/selectable/bulb.svg') !important; } -#PanelUI-zen-boost-disable { +#zen-boost-disable { list-style-image: url('chrome://browser/skin/zen-icons/selectable/block.svg') !important; } diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.xhtml index 2b6c8cfd20..4a948c651f 100644 --- a/src/zen/boosts/zen-boost-editor.xhtml +++ b/src/zen/boosts/zen-boost-editor.xhtml @@ -30,18 +30,18 @@ - - + + + id="zen-boost-name" /> @@ -50,37 +50,39 @@
- + - + - - - - - - - - - +
+ + + + + + + + + +
\ No newline at end of file diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index c1f258d3fd..226c282760 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -14,23 +14,23 @@ } } -#PanelUI-zen-boost-font-arial { +#zen-boost-font-arial { font-family: Arial, sans-serif; } -#PanelUI-zen-boost-font-serif { +#zen-boost-font-serif { font-family: serif; } -#PanelUI-zen-boost-font-mono { +#zen-boost-font-mono { font-family: monospace, monospace; } -#PanelUI-zen-boost-editor-view { +#zen-boost-editor-view { gap: 10px; } -#PanelUI-zen-boost-head-wrapper { +#zen-boost-head-wrapper { margin-top: -14px; margin-bottom: -14px; @@ -39,7 +39,17 @@ } } -#PanelUI-zen-boost-font-wrapper { +#zen-boost-font-wrapper { + filter: drop-shadow(0px 4px 8px #00000055); + background-color: #aaaaaa05; + + border-radius: 8px; + + padding-top: 4px; + padding-bottom: 4px; +} + +#zen-boost-font-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0px; @@ -51,7 +61,7 @@ } } -#PanelUI-zen-boost-filter-wrapper { +#zen-boost-filter-wrapper { width: 135px; & input { @@ -99,30 +109,27 @@ padding: 15px; } -#PanelUI-zen-boost-delete { +#zen-boost-delete { width: auto; } -#PanelUI-zen-boost-zap { +#zen-boost-zap { opacity: 0.5 !important; cursor: not-allowed !important; pointer-events: none !important; } -#PanelUI-zen-boost-toolbar-wrapper { +#zen-boost-toolbar-wrapper { width: auto; margin: auto; - gap: 1.15em; + gap: 1em; & button { width: auto; height: auto !important; - - padding: 6px 4px 6px 4px; - - background-color: #AAAAAA22; - margin: auto; + background-color: #AAAAAA22; + padding: 4px 5px 4px 5px !important; } } @@ -130,17 +137,17 @@ filter: grayscale(1); } -#PanelUI-zen-boost-filter-wrapper separator { +#zen-boost-filter-wrapper separator { height: 5px !important; } -#PanelUI-zen-boost-toolbar-wrapper button:hover { +#zen-boost-toolbar-wrapper button:hover { background-color: #ffffff11; opacity: 0.9; } .zen-boost-button-active { - background-color: white; + background-color: white !important; color: black !important; } @@ -148,27 +155,31 @@ background-color: white !important; } -.zen-boost-color-picker-gradient::after { +.zen-boost-color-picker-gradient::before { content: ''; position: absolute; inset: 0; border-radius: inherit; - border: 5px solid hsl(100 100% 60%); - border-image-slice: 1; - border-image-source: conic-gradient( - rgba(255, 0, 0, 1) 0%, - rgba(255, 162, 0, 1) 10%, - rgba(255, 242, 0, 1) 20%, - rgba(89, 255, 0, 1) 30%, - rgba(0, 255, 128, 1) 40%, - rgba(0, 255, 247, 1) 50%, - rgba(0, 89, 255, 1) 60%, - rgba(8, 0, 255, 1) 70%, - rgba(204, 0, 255, 1) 80%, - rgba(255, 0, 144, 1) 90%, - rgba(255, 0, 8, 1) 100% - ); + background: conic-gradient( + rgba(255, 0, 0, 1) 0%, + rgba(255, 162, 0, 1) 10%, + rgba(255, 242, 0, 1) 20%, + rgba(89, 255, 0, 1) 30%, + rgba(0, 255, 128, 1) 40%, + rgba(0, 255, 247, 1) 50%, + rgba(0, 89, 255, 1) 60%, + rgba(8, 0, 255, 1) 70%, + rgba(204, 0, 255, 1) 80%, + rgba(255, 0, 144, 1) 90%, + rgba(255, 0, 8, 1) 100% + ) + border-box; + + /* cut out the middle of the gradient */ + border: 6px solid transparent; + mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0); + mask-composite: exclude; pointer-events: none; } @@ -176,7 +187,9 @@ .zen-boost-color-picker-gradient { position: relative; overflow: hidden; - border-radius: calc(var(--panel-border-radius) - 4px); + border-radius: calc(var(--panel-border-radius) - 2px); + + filter: drop-shadow(0px 4px 8px #00000055); width: auto; height: 135px; @@ -227,7 +240,6 @@ width: 26px; height: 26px; border-width: 3px; - z-index: 2; pointer-events: all; transition: transform 0.2s; z-index: 999; From f2110806d6d3d32a780d3fd4a1bf71defb25e603 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 31 Oct 2025 19:36:35 +0100 Subject: [PATCH 17/33] fix: missing methods, simplify writing to disk --- src/zen/boosts/ZenBoostsEditor.sys.mjs | 2 +- src/zen/boosts/ZenBoostsManager.sys.mjs | 19 +++-------- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 35 ++++++++++++-------- src/zen/boosts/zen-boosts.css | 6 ++-- src/zen/common/ZenActorsManager.sys.mjs | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index c2d21201fb..68538d672d 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -324,7 +324,7 @@ export class nsZenBoostEditor { loadBoost(domain) { this.currentBoostData = gZenBoostsManager.loadBoostFromStore(domain); - + // Initial save to register the boost gZenBoostsManager.saveBoostToStore(null); diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index e1e2afe06f..aa679228ca 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -57,11 +57,9 @@ export class nsZenBoostsManager { } deleteBoost(domain) { - if (this.registeredSheets.has(domain)) - this.unregisterSheet(domain); + if (this.registeredSheets.has(domain)) this.unregisterSheet(domain); - if(this.registeredBoosts.has(domain)) - this.registeredBoosts.delete(domain); + if (this.registeredBoosts.has(domain)) this.registeredBoosts.delete(domain); } // Load a boost from a domain @@ -105,8 +103,7 @@ export class nsZenBoostsManager { // Save all boosts to the profile folder saveBoostToStore(boostData) { if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); - - (async () => this.writeToDisk(this.registeredBoosts))(); + this.writeToDisk(this.registeredBoosts); } // Reads all boosts from the profile folder @@ -138,15 +135,9 @@ export class nsZenBoostsManager { } // Helper method, map => json => disk - async writeToDisk(map) { - const encoder = new TextEncoder(); + writeToDisk(map) { const json = JSON.stringify([...map]); - const data = encoder.encode(json); - - const profilePath = PathUtils.profileDir; - const savePath = PathUtils.join(profilePath, this.saveFilename); - - await IOUtils.write(savePath, new Uint8Array(data)); + IOUtils.writeJSON(json, { compress: true }); } // Checks if there is a boost registered for the currently open tab diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 150edd0c71..3b97c35a95 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -53,38 +53,45 @@ export class ZenBoostsChild extends JSWindowActorChild { } else { const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; - r = this.hueToRgb(p, q, h + 1 / 3); - g = this.hueToRgb(p, q, h); - b = this.hueToRgb(p, q, h - 1 / 3); + r = this.#hueToRgb(p, q, h + 1 / 3); + g = this.#hueToRgb(p, q, h); + b = this.#hueToRgb(p, q, h - 1 / 3); } return [round(r * 255), round(g * 255), round(b * 255)]; } + #hueToRgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + async #applyBoostForPageIfAvailable() { const browsingContext = this.browsingContext; if (!browsingContext) { return null; } - const url = new URL(browsingContext.currentWindowGlobal.documentURI.spec); - const domain = url.hostname; + const domain = browsingContext.currentURI.host; const { gZenBoostsManager } = ChromeUtils.importESModule( 'resource:///modules/ZenBoostsManager.sys.mjs' ); - if(gZenBoostsManager.registeredBoostForDomain(domain)){ + if (gZenBoostsManager.registeredBoostForDomain(domain)) { const boostData = gZenBoostsManager.loadBoostFromStore(domain); - - window.gBrowser.selectedBrowser.browsingContext.prefersColorSchemeOverride = boostData.smartInvert ? "light" : "none"; - if(boostData.enableColorBoost){ + + browsingContext.prefersColorSchemeOverride = + boostData.smartInvert ? 'light' : 'none'; + if (boostData.enableColorBoost) { const hslColor = this.#hslToRgb(boostData.dotAngleDeg, boostData.dotDistance, 60); - const nsColor = this.#rgbToNSColor(hslColor[0], hslColor[1], hslColor[2]); - window.gBrowser.selectedBrowser.browsingContext.zenBoostsData = nsColor; - } - else - window.gBrowser.selectedBrowser.browsingContext.zenBoostsData = null; + const nsColor = this.#rgbToNSColor(hslColor[0], hslColor[1], hslColor[2]); + browsingContext.zenBoostsData = nsColor; + } else browsingContext.zenBoostsData = null; } } diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 226c282760..da8a591f44 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -128,7 +128,7 @@ width: auto; height: auto !important; margin: auto; - background-color: #AAAAAA22; + background-color: #aaaaaa22; padding: 4px 5px 4px 5px !important; } } @@ -178,7 +178,9 @@ /* cut out the middle of the gradient */ border: 6px solid transparent; - mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0); + mask: + linear-gradient(#000 0 0) padding-box, + linear-gradient(#000 0 0); mask-composite: exclude; pointer-events: none; diff --git a/src/zen/common/ZenActorsManager.sys.mjs b/src/zen/common/ZenActorsManager.sys.mjs index 27c02dc4ea..285ff67abe 100644 --- a/src/zen/common/ZenActorsManager.sys.mjs +++ b/src/zen/common/ZenActorsManager.sys.mjs @@ -57,7 +57,7 @@ let JSWINDOWACTORS = { events: { load: { mozSystemGroup: true, capture: true }, }, - observers: ["zen-boosts-update"], + observers: ['zen-boosts-update'], }, allFrames: true, matches: ['*://*/*'], From 4d03d8dd9389ddfa2b1d8aab392a518867458a8e Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 19:39:54 +0100 Subject: [PATCH 18/33] chore: Small fixes, b=no-bug, c=no-component --- src/zen/boosts/ZenBoostsManager.sys.mjs | 8 +++++++- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 11 ++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index e1e2afe06f..3d9e6c3e25 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -2,6 +2,12 @@ // 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/. +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", +}); + export class nsZenBoostsManager { initialized = false; registeredSheets = new Map(); @@ -98,7 +104,7 @@ export class nsZenBoostsManager { `; } - Services.obs.notifyObservers(null, 'zen-boosts-update'); + Services.obs.notifyObservers(lazy.BrowserWindowTracker.getTopWindow(), 'zen-boosts-update'); this.registerCSSForDomain(fontFamily, boost.domain); } diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 150edd0c71..1b9996cfde 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -5,8 +5,6 @@ export class ZenBoostsChild extends JSWindowActorChild { constructor() { super(); - - Services.obs.addObserver(this, 'zen-boosts-update'); } observe(subject, topic) { @@ -67,8 +65,7 @@ export class ZenBoostsChild extends JSWindowActorChild { return null; } - const url = new URL(browsingContext.currentWindowGlobal.documentURI.spec); - const domain = url.hostname; + const domain = browsingContext.topWindow.location.host; const { gZenBoostsManager } = ChromeUtils.importESModule( 'resource:///modules/ZenBoostsManager.sys.mjs' @@ -77,14 +74,14 @@ export class ZenBoostsChild extends JSWindowActorChild { if(gZenBoostsManager.registeredBoostForDomain(domain)){ const boostData = gZenBoostsManager.loadBoostFromStore(domain); - window.gBrowser.selectedBrowser.browsingContext.prefersColorSchemeOverride = boostData.smartInvert ? "light" : "none"; + browsingContext.prefersColorSchemeOverride = boostData.smartInvert ? "light" : "none"; if(boostData.enableColorBoost){ const hslColor = this.#hslToRgb(boostData.dotAngleDeg, boostData.dotDistance, 60); const nsColor = this.#rgbToNSColor(hslColor[0], hslColor[1], hslColor[2]); - window.gBrowser.selectedBrowser.browsingContext.zenBoostsData = nsColor; + browsingContext.zenBoostsData = nsColor; } else - window.gBrowser.selectedBrowser.browsingContext.zenBoostsData = null; + browsingContext.zenBoostsData = null; } } From eae5524d15cf128bb6aa64f1d2a781a22aa4d933 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Fri, 31 Oct 2025 19:51:05 +0100 Subject: [PATCH 19/33] chore: Clean up, b=no-bug, c=no-component --- src/zen/boosts/ZenBoostsEditor.sys.mjs | 2 +- src/zen/boosts/ZenBoostsManager.sys.mjs | 17 ++++++++--------- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 4 ---- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 68538d672d..1c6ca3e4c1 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -26,7 +26,7 @@ export class nsZenBoostEditor { } init() { - this.window.addEventListener('unload', () => this.handleClose()); + this.window.addEventListener('unload', () => this.handleClose(), { once: true }); this.doc .getElementById('zen-boost-font-arial') diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 8c5f555d98..27d939b28f 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -126,24 +126,23 @@ export class nsZenBoostsManager { }); } + get #storePath() { + const profilePath = PathUtils.profileDir; + return PathUtils.join(profilePath, this.saveFilename); + } + // Helper method, disk => json => map async readFromDisk() { - const profilePath = PathUtils.profileDir; - const savePath = PathUtils.join(profilePath, this.saveFilename); + const savePath = this.#storePath; if (!(await IOUtils.exists(savePath))) return new Map(); - const data = await IOUtils.read(savePath); - const decoder = new TextDecoder(); - const json = decoder.decode(data); - - return new Map(JSON.parse(json)); + return IOUtils.readJSON(savePath); } // Helper method, map => json => disk writeToDisk(map) { - const json = JSON.stringify([...map]); - IOUtils.writeJSON(json, { compress: true }); + IOUtils.writeJSON(this.#storePath, map, { compress: true }); } // Checks if there is a boost registered for the currently open tab diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 22e9d9076c..05b911ec52 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -76,10 +76,6 @@ export class ZenBoostsChild extends JSWindowActorChild { const domain = browsingContext.topWindow.location.host; - const { gZenBoostsManager } = ChromeUtils.importESModule( - 'resource:///modules/ZenBoostsManager.sys.mjs' - ); - if (gZenBoostsManager.registeredBoostForDomain(domain)) { const boostData = gZenBoostsManager.loadBoostFromStore(domain); browsingContext.prefersColorSchemeOverride = boostData.smartInvert ? "light" : "none"; From 0886619d98f7e32147fa52853c8141e19bac0293 Mon Sep 17 00:00:00 2001 From: FlorianButz Date: Sat, 1 Nov 2025 21:20:07 +0100 Subject: [PATCH 20/33] fix: change communication of actors, cleanup --- src/layout/painting/nsDisplayList-cpp.patch | 2 +- src/zen/boosts/ZenBoostsEditor.sys.mjs | 1 - src/zen/boosts/ZenBoostsManager.sys.mjs | 89 ++++--------------- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 40 +++++---- src/zen/boosts/actors/ZenBoostsParent.sys.mjs | 43 +++++++++ src/zen/boosts/moz.build | 1 + src/zen/common/ZenActorsManager.sys.mjs | 3 + 7 files changed, 90 insertions(+), 89 deletions(-) create mode 100644 src/zen/boosts/actors/ZenBoostsParent.sys.mjs diff --git a/src/layout/painting/nsDisplayList-cpp.patch b/src/layout/painting/nsDisplayList-cpp.patch index 9f2fc332a6..9ca16bb5d3 100644 --- a/src/layout/painting/nsDisplayList-cpp.patch +++ b/src/layout/painting/nsDisplayList-cpp.patch @@ -30,7 +30,7 @@ index 6c5b38eb3cdb08a8afd670705c75c052b85b6dd9..823df9ef5245dcffcbfaba1fafc6b15e + nsCOMPtr zenBackend( + do_GetService(ZEN_BOOSTS_BACKEND_CONTRACTID)); + if (zenBackend) { -+ zenBackend->onPressShellExited( ++ zenBackend->onPressShellLeave( + mPresShellStates.IsEmpty() + ? nullptr + : CurrentPresShellState()->mPresShell->GetPresContext()); diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 1c6ca3e4c1..8fd1096078 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -193,7 +193,6 @@ export class nsZenBoostEditor { this.currentBoostData.dotAngleDeg = 0; this.currentBoostData.dotDistance = 0; - this.currentBoostData.dotAngleRad = 0; } else { let distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); distance = Math.min(distance, radius); // Clamp distance diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 27d939b28f..2b154642bc 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -5,67 +5,26 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", + BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs', }); export class nsZenBoostsManager { initialized = false; - registeredSheets = new Map(); registeredBoosts = new Map(); saveFilename = 'zen-boosts.json'; constructor() { - this.init(); + this.#init(); } - init() { - this.readBoostsFromStore(() => (this.initialized = true)); - } - - // Store Firefox Style Sheet Service and IO Service for later use - sss = Components.classes['@mozilla.org/content/style-sheet-service;1'].getService( - Components.interfaces.nsIStyleSheetService - ); - ioService = Services.io; - - // TODO: Causes flickering when updating often - registerCSSForDomain(cssString, domain, sheetType = this.sss.USER_SHEET) { - // Make sure existing overrides get overwritten and not added on top - if (this.registeredSheets.has(domain)) { - this.unregisterSheet(this.registeredSheets.get(domain), sheetType); - } - - // Add the @-moz-document wrapper and specific domain attribute - const wrapped = `@-moz-document domain("${domain}") { ${cssString} }`; - const uri = this.ioService.newURI('data:text/css;charset=utf-8,' + encodeURIComponent(wrapped)); - - // Register and store in map - this.sss.loadAndRegisterSheet(uri, sheetType); - this.registeredSheets.set(domain, uri); - - return uri; - } - - // Unregisters a sheet based on either domain or uri - unregisterSheet(uriOrDomain, sheetType = this.sss.USER_SHEET) { - let uri = uriOrDomain; - - // Check if a uri or domain - if (typeof uriOrDomain === 'string' && this.registeredSheets.has(uriOrDomain)) { - uri = this.registeredSheets.get(uriOrDomain); - this.registeredSheets.delete(uriOrDomain); - } - - if (this.sss.sheetRegistered(uri, sheetType)) { - this.sss.unregisterSheet(uri, sheetType); - } + #init() { + this.#readBoostsFromStore(() => (this.initialized = true)); } deleteBoost(domain) { - if (this.registeredSheets.has(domain)) this.unregisterSheet(domain); - if (this.registeredBoosts.has(domain)) this.registeredBoosts.delete(domain); + Services.obs.notifyObservers(lazy.BrowserWindowTracker.getTopWindow(), 'zen-boosts-update'); } // Load a boost from a domain @@ -86,42 +45,28 @@ export class nsZenBoostsManager { if (this.registeredBoosts.has(dom)) { boostData = this.registeredBoosts.get(dom); + } else { + this.registeredBoosts.set(dom, boostData); } return boostData; } - // Injects css based on boost data - updateBoost(boost) { - let fontFamily = ''; - if (boost.fontFamily != '') { - fontFamily = ` - body, p, h1, h2, h3, h4, h5, a, span, textarea, input { - font-family: ${boost.fontFamily} !important; - } - `; - } - + updateBoost(boostData) { + this.registeredBoosts.set(boostData.domain, boostData); Services.obs.notifyObservers(lazy.BrowserWindowTracker.getTopWindow(), 'zen-boosts-update'); - this.registerCSSForDomain(fontFamily, boost.domain); } // Save all boosts to the profile folder saveBoostToStore(boostData) { if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); - this.writeToDisk(this.registeredBoosts); + this.#writeToDisk(this.registeredBoosts); } // Reads all boosts from the profile folder - readBoostsFromStore(done) { - this.readFromDisk().then((map) => { + #readBoostsFromStore(done) { + this.#readFromDisk().then((map) => { this.registeredBoosts = map; - - // Load in all boosts - for (const [key, value] of this.registeredBoosts) { - this.updateBoost(value); - } - done(); }); } @@ -132,17 +77,19 @@ export class nsZenBoostsManager { } // Helper method, disk => json => map - async readFromDisk() { + async #readFromDisk() { const savePath = this.#storePath; if (!(await IOUtils.exists(savePath))) return new Map(); - return IOUtils.readJSON(savePath); + const array = await IOUtils.readJSON(savePath, { decompress: true }); + return new Map(array); } // Helper method, map => json => disk - writeToDisk(map) { - IOUtils.writeJSON(this.#storePath, map, { compress: true }); + #writeToDisk(map) { + const array = Array.from(map.entries()); + IOUtils.writeJSON(this.#storePath, array, { compress: true }); } // Checks if there is a boost registered for the currently open tab diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 05b911ec52..cb0c127a15 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -7,12 +7,6 @@ export class ZenBoostsChild extends JSWindowActorChild { super(); } - observe(subject, topic) { - if (topic === 'zen-boosts-update') { - this.#applyBoostForPageIfAvailable(); - } - } - async handleEvent(event) { switch (event.type) { case 'load': @@ -22,6 +16,14 @@ export class ZenBoostsChild extends JSWindowActorChild { } } + async receiveMessage(message) { + switch (message.name) { + case 'ZenBoost:BoostDataUpdated': + this.#applyBoostForPageIfAvailable(); + return Promise.resolve(null); + } + } + /** * Inverse of https://searchfox.org/firefox-main/rev/0b21972a78f8915f73ce5579eeee2aa8c9c7d67e/gfx/src/nsColor.h#18-21 * Converts [r, g, b] array to NSColor integer @@ -76,17 +78,23 @@ export class ZenBoostsChild extends JSWindowActorChild { const domain = browsingContext.topWindow.location.host; - if (gZenBoostsManager.registeredBoostForDomain(domain)) { - const boostData = gZenBoostsManager.loadBoostFromStore(domain); - browsingContext.prefersColorSchemeOverride = boostData.smartInvert ? "light" : "none"; - if(boostData.enableColorBoost){ - const hslColor = this.#hslToRgb(boostData.dotAngleDeg, boostData.dotDistance, 60); - const nsColor = this.#rgbToNSColor(hslColor[0], hslColor[1], hslColor[2]); - browsingContext.zenBoostsData = nsColor; - } - else + this.sendQuery('ZenBoost:GetBoostForDomain', domain).then((boost) => { + if (boost != null) { + browsingContext.prefersColorSchemeOverride = boost.smartInvert ? 'light' : 'none'; + if (boost.enableColorBoost) { + const rgbColor = this.#hslToRgb( + boost.dotAngleDeg / 360, + boost.dotDistance /* already is [0, 1] */, + 0.2 + boost.dotDistance * 0.6 /* lightness range from [0.2, 0.8] */ + ); + const nsColor = this.#rgbToNSColor(rgbColor); + browsingContext.zenBoostsData = nsColor; + } else browsingContext.zenBoostsData = 0; + } else { + browsingContext.prefersColorSchemeOverride = 'none'; browsingContext.zenBoostsData = 0; - } + } + }); } async #onLoad(event) { diff --git a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs new file mode 100644 index 0000000000..8062e33d1d --- /dev/null +++ b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs @@ -0,0 +1,43 @@ +// 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/. + +export class ZenBoostsParent extends JSWindowActorParent { + canSendUpdate = true; + + constructor() { + super(); + + this._observe = this.observe.bind(this); + Services.obs.addObserver(this._observe, 'zen-boosts-update'); + } + + didDestroy() { + Services.obs.removeObserver(this._observe, 'zen-boosts-update'); + } + + observe(subject, topic, data) { + if (topic === 'zen-boosts-update') { + this.canSendUpdate = false; + + this.sendQuery('ZenBoost:BoostDataUpdated').then((x) => (this.canSendUpdate = true)); + } + } + + async receiveMessage(message) { + switch (message.name) { + case 'ZenBoost:GetBoostForDomain': { + const domain = message.data; + const { gZenBoostsManager } = ChromeUtils.importESModule( + 'resource:///modules/ZenBoostsManager.sys.mjs' + ); + + if (!gZenBoostsManager.registeredBoostForDomain(domain)) return Promise.resolve(null); + + return Promise.resolve(gZenBoostsManager.loadBoostFromStore(domain)); + } + default: + console.warn(`[ZenBoostsParent]: Unknown message: ${message.name}`); + } + } +} diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index 35a95a8948..6842675805 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -9,6 +9,7 @@ EXTRA_JS_MODULES += [ FINAL_TARGET_FILES.actors += [ "actors/ZenBoostsChild.sys.mjs", + "actors/ZenBoostsParent.sys.mjs", ] XPIDL_SOURCES += [ diff --git a/src/zen/common/ZenActorsManager.sys.mjs b/src/zen/common/ZenActorsManager.sys.mjs index 285ff67abe..01d09a8a91 100644 --- a/src/zen/common/ZenActorsManager.sys.mjs +++ b/src/zen/common/ZenActorsManager.sys.mjs @@ -52,6 +52,9 @@ let JSWINDOWACTORS = { enablePreference: 'zen.glance.enabled', }, ZenBoosts: { + parent: { + esModuleURI: 'resource:///actors/ZenBoostsParent.sys.mjs', + }, child: { esModuleURI: 'resource:///actors/ZenBoostsChild.sys.mjs', events: { From c0d04f915d42c837b8ca46676ca2c9f64b14ecfb Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sat, 1 Nov 2025 23:31:42 +0100 Subject: [PATCH 21/33] feat: Prevent flashing when going between pages, b=no-bug, c=common --- src/zen/boosts/ZenBoostsEditor.sys.mjs | 1 + src/zen/boosts/ZenBoostsManager.sys.mjs | 15 ++++++----- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 21 ++++++++++------ src/zen/boosts/actors/ZenBoostsParent.sys.mjs | 25 +++++++++++-------- src/zen/boosts/zen-boosts.css | 10 -------- src/zen/common/ZenActorsManager.sys.mjs | 3 +-- 6 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 8fd1096078..1e66754b48 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -270,6 +270,7 @@ export class nsZenBoostEditor { } onToggleInvert() { + this.currentBoostData.enableColorBoost = true; this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; this.updateButtonToggleVisuals(); diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 2b154642bc..62a855f8d9 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -12,7 +12,7 @@ export class nsZenBoostsManager { initialized = false; registeredBoosts = new Map(); - saveFilename = 'zen-boosts.json'; + #saveFilename = 'zen-boosts.jsonlz4'; constructor() { this.#init(); @@ -29,12 +29,11 @@ export class nsZenBoostsManager { // Load a boost from a domain loadBoostFromStore(domain) { - if (domain == null) console.error('[ZenBoostsManager] Domain expected but got null.'); - const dom = domain ?? ''; + if (!domain) console.error('[ZenBoostsManager] Domain expected but got null.'); let boostData = { + domain, boostName: 'New Boost', - domain: dom, dotAngleDeg: 0, dotPos: { x: null, y: null }, dotDistance: 0, @@ -43,10 +42,10 @@ export class nsZenBoostsManager { smartInvert: false, }; - if (this.registeredBoosts.has(dom)) { - boostData = this.registeredBoosts.get(dom); + if (this.registeredBoosts.has(domain)) { + boostData = this.registeredBoosts.get(domain); } else { - this.registeredBoosts.set(dom, boostData); + this.registeredBoosts.set(domain, boostData); } return boostData; @@ -73,7 +72,7 @@ export class nsZenBoostsManager { get #storePath() { const profilePath = PathUtils.profileDir; - return PathUtils.join(profilePath, this.saveFilename); + return PathUtils.join(profilePath, this.#saveFilename); } // Helper method, disk => json => map diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index cb0c127a15..9cfc1ad05b 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -7,10 +7,10 @@ export class ZenBoostsChild extends JSWindowActorChild { super(); } - async handleEvent(event) { + handleEvent(event) { switch (event.type) { - case 'load': - await this.#onLoad(event); + case 'DOMDocElementInserted': + this.#onLoad(event); break; default: } @@ -76,11 +76,18 @@ export class ZenBoostsChild extends JSWindowActorChild { return null; } - const domain = browsingContext.topWindow.location.host; + const domain = browsingContext.topWindow?.location?.host; + if (!domain) { + return null; + } this.sendQuery('ZenBoost:GetBoostForDomain', domain).then((boost) => { - if (boost != null) { - browsingContext.prefersColorSchemeOverride = boost.smartInvert ? 'light' : 'none'; + if (boost) { + let prefersColorSchemeOverride = 'none'; + if (boost.smartInvert) { + prefersColorSchemeOverride = boost.topWindowIsDarkMode ? 'light' : 'dark'; + } + browsingContext.prefersColorSchemeOverride = prefersColorSchemeOverride; if (boost.enableColorBoost) { const rgbColor = this.#hslToRgb( boost.dotAngleDeg / 360, @@ -97,7 +104,7 @@ export class ZenBoostsChild extends JSWindowActorChild { }); } - async #onLoad(event) { + #onLoad() { this.#applyBoostForPageIfAvailable(); } } diff --git a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs index 8062e33d1d..cac42ad1e0 100644 --- a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs @@ -2,6 +2,12 @@ // 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/. +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + gZenBoostsManager: 'resource:///modules/ZenBoostsManager.sys.mjs', +}); + export class ZenBoostsParent extends JSWindowActorParent { canSendUpdate = true; @@ -16,11 +22,9 @@ export class ZenBoostsParent extends JSWindowActorParent { Services.obs.removeObserver(this._observe, 'zen-boosts-update'); } - observe(subject, topic, data) { + observe(subject, topic) { if (topic === 'zen-boosts-update') { - this.canSendUpdate = false; - - this.sendQuery('ZenBoost:BoostDataUpdated').then((x) => (this.canSendUpdate = true)); + this.sendQuery('ZenBoost:BoostDataUpdated'); } } @@ -28,13 +32,12 @@ export class ZenBoostsParent extends JSWindowActorParent { switch (message.name) { case 'ZenBoost:GetBoostForDomain': { const domain = message.data; - const { gZenBoostsManager } = ChromeUtils.importESModule( - 'resource:///modules/ZenBoostsManager.sys.mjs' - ); - - if (!gZenBoostsManager.registeredBoostForDomain(domain)) return Promise.resolve(null); - - return Promise.resolve(gZenBoostsManager.loadBoostFromStore(domain)); + const embedder = this.browsingContext.top.embedderElement; + if (!embedder || !domain) return null; + if (!lazy.gZenBoostsManager.registeredBoostForDomain(domain)) return null; + const topWindowIsDarkMode = + embedder.ownerGlobal.getComputedStyle(embedder).colorScheme === 'dark'; + return { ...lazy.gZenBoostsManager.loadBoostFromStore(domain), topWindowIsDarkMode }; } default: console.warn(`[ZenBoostsParent]: Unknown message: ${message.name}`); diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index da8a591f44..77a0addb76 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -4,16 +4,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -:root { - @media (-moz-platform: macos) { - --panel-border-radius: 10px; - - @media (-moz-mac-tahoe-theme) { - --panel-border-radius: 12px; - } - } -} - #zen-boost-font-arial { font-family: Arial, sans-serif; } diff --git a/src/zen/common/ZenActorsManager.sys.mjs b/src/zen/common/ZenActorsManager.sys.mjs index 01d09a8a91..fb85625712 100644 --- a/src/zen/common/ZenActorsManager.sys.mjs +++ b/src/zen/common/ZenActorsManager.sys.mjs @@ -58,9 +58,8 @@ let JSWINDOWACTORS = { child: { esModuleURI: 'resource:///actors/ZenBoostsChild.sys.mjs', events: { - load: { mozSystemGroup: true, capture: true }, + DOMDocElementInserted: { capture: true }, }, - observers: ['zen-boosts-update'], }, allFrames: true, matches: ['*://*/*'], From f9c948aa686eb87cdf23bc003dd05af842696210 Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Tue, 4 Nov 2025 02:04:50 +0100 Subject: [PATCH 22/33] feat: Start taking into account contrast values, b=no-bug, c=no-component --- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 67 ++++++++++++-------- src/zen/boosts/nsZenBoostsBackend.cpp | 67 ++++++++++++++------ 2 files changed, 88 insertions(+), 46 deletions(-) diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 9cfc1ad05b..f405946394 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -7,29 +7,25 @@ export class ZenBoostsChild extends JSWindowActorChild { super(); } - handleEvent(event) { - switch (event.type) { - case 'DOMDocElementInserted': - this.#onLoad(event); - break; - default: - } - } - - async receiveMessage(message) { - switch (message.name) { - case 'ZenBoost:BoostDataUpdated': - this.#applyBoostForPageIfAvailable(); - return Promise.resolve(null); - } - } - /** - * Inverse of https://searchfox.org/firefox-main/rev/0b21972a78f8915f73ce5579eeee2aa8c9c7d67e/gfx/src/nsColor.h#18-21 - * Converts [r, g, b] array to NSColor integer + * Inverse of https://searchfox.org/firefox-main/rev/1a8c62b86277005f907151bc5389cf5c5091e76f/gfx/src/nsColor.h#23-27 + * + * > #define NS_RGBA(_r, _g, _b, _a) \ + * > ((nscolor)(((_a) << 24) | ((_b) << 16) | ((_g) << 8) | (_r))) + * + * Converts [r, g, b] array to NSColor + * Make a color out of r,g,b,a values. This assumes that the r,g,b,a + * values are properly constrained to 0-255. + * @param {Array} rgb - Array of red, green, blue values [0, 255] + * @param {number} contrast - Contrast value (default 255) + * @returns {number} NSColor integer representation */ - #rgbToNSColor([r, g, b]) { - return (b << 16) | (g << 8) | r; + #rgbToNSColor([r, g, b], contrast = 255) { + // Note will be using the alpha channel for contrast, since the colors will always + // be fully opaque and we need an extra byte to store the contrast value. This allows + // us to still use an nscolor as parameter instead of having to deal with WebIDL structs + // shenanigans. + return ((contrast & 0xff) << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff); } /** @@ -61,6 +57,27 @@ export class ZenBoostsChild extends JSWindowActorChild { return [round(r * 255), round(g * 255), round(b * 255)]; } + handleEvent(event) { + switch (event.type) { + case 'DOMDocElementInserted': + this.#applyBoostForPageIfAvailable(); + break; + default: + } + } + + async receiveMessage(message) { + switch (message.name) { + case 'ZenBoost:BoostDataUpdated': + this.#applyBoostForPageIfAvailable(); + return Promise.resolve(null); + } + } + + /** + * From ZenGradientGenerator.mjs + * Helper function for hslToRgb conversion + */ #hueToRgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; @@ -70,6 +87,10 @@ export class ZenBoostsChild extends JSWindowActorChild { return p; } + /** + * Applies the boost settings for the current page if available. + * @returns {Promise} + */ async #applyBoostForPageIfAvailable() { const browsingContext = this.browsingContext; if (!browsingContext) { @@ -103,8 +124,4 @@ export class ZenBoostsChild extends JSWindowActorChild { } }); } - - #onLoad() { - this.#applyBoostForPageIfAvailable(); - } } diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index eb8091c62d..08fbae7205 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -37,6 +37,50 @@ static __inline int32_t clamp255(int32_t v) { return (((255 - (v)) >> 31) | (v)) & 255; } +#define COLOR_CHANNEL_MIDPOINT 128 +#define APPLY_CONTRAST(channel, factor) \ + ((int32_t)((channel - COLOR_CHANNEL_MIDPOINT) * factor + COLOR_CHANNEL_MIDPOINT)) + +static nscolor zenFilterColorChannel(nscolor originalNS, nscolor accentNS) { + auto r1 = NS_GET_R(originalNS); + auto g1 = NS_GET_G(originalNS); + auto b1 = NS_GET_B(originalNS); + + // It's a bit of a hacky solution, but instead of using alpha as what it is + // (opacity), we use it to store contrast information for now. + // We do this primarily to avoid having to deal with WebIDL structs and + // serialization/deserialization between parent and content processes. + auto contrast = NS_GET_A(originalNS); + + auto r2 = NS_GET_R(accentNS); + auto g2 = NS_GET_G(accentNS); + auto b2 = NS_GET_B(accentNS); + + // Approximate perceived luminance in sRGB space + // Coefficients per Rec.709; gamma correction ignored for speed + double origLum = 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1; + double accentLum = 0.2126 * r2 + 0.7152 * g2 + 0.0722 * b2; + + double scale = accentLum > 0.0 ? (origLum / accentLum) : 1.0; + + uint8_t fr = clamp255(r2 * scale); + uint8_t fg = clamp255(g2 * scale); + uint8_t fb = clamp255(b2 * scale); + + // Apply contrast adjustment: map contrast from 0-255 to 0.0-2.0 + // contrast = 0: reduce contrast (factor = 0.0, moves toward middle gray) + // contrast = 127.5: no change (factor = 1.0) + // contrast = 255: increase contrast (factor = 2.0, lighter/darker extremes) + double contrastFactor = contrast / 127.5; + + // Apply contrast: adjust each channel relative to middle gray + fr = clamp255(APPLY_CONTRAST(fr, contrastFactor)); + fg = clamp255(APPLY_CONTRAST(fg, contrastFactor)); + fb = clamp255(APPLY_CONTRAST(fb, contrastFactor)); + + return NS_RGBA(fr, fg, fb, 1); +} + } // namespace // Use the macro to inject all of the definitions for nsISupports. @@ -93,27 +137,8 @@ auto nsZenBoostsBackend::ResolveStyleColor( // Convert both colors to nscolor to access channels nscolor originalNS = aColor.ToColor(); - - auto r1 = NS_GET_R(originalNS); - auto g1 = NS_GET_G(originalNS); - auto b1 = NS_GET_B(originalNS); - - auto r2 = NS_GET_R(accentNS); - auto g2 = NS_GET_G(accentNS); - auto b2 = NS_GET_B(accentNS); - - // Approximate perceived luminance in sRGB space - // Coefficients per Rec.709; gamma correction ignored for speed - double origLum = 0.2126 * r1 + 0.7152 * g1 + 0.0722 * b1; - double accentLum = 0.2126 * r2 + 0.7152 * g2 + 0.0722 * b2; - - double scale = accentLum > 0.0 ? (origLum / accentLum) : 1.0; - - uint8_t fr = clamp255(r2 * scale); - uint8_t fg = clamp255(g2 * scale); - uint8_t fb = clamp255(b2 * scale); - - nscolor filteredNS = NS_RGBA(fr, fg, fb, 1); + nscolor filteredNS = zenFilterColorChannel(originalNS, accentNS); + auto filtered = mozilla::StyleAbsoluteColor::FromColor(filteredNS); filtered.alpha = aColor.alpha; return filtered; From 1ac6a40251b63137204a0a36eb1033eac30d204b Mon Sep 17 00:00:00 2001 From: "mr. m" Date: Tue, 4 Nov 2025 15:30:00 +0100 Subject: [PATCH 23/33] feat: Correctly calculate color lightness based on contrast, b=no-bug, c=no-component --- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 2 +- src/zen/boosts/nsZenBoostsBackend.cpp | 70 ++++++++++++-------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index f405946394..ac98656325 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -25,7 +25,7 @@ export class ZenBoostsChild extends JSWindowActorChild { // be fully opaque and we need an extra byte to store the contrast value. This allows // us to still use an nscolor as parameter instead of having to deal with WebIDL structs // shenanigans. - return ((contrast & 0xff) << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff); + return (contrast << 24) | (b << 16) | (g << 8) | r; } /** diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 08fbae7205..2579d1834e 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -38,23 +38,21 @@ static __inline int32_t clamp255(int32_t v) { } #define COLOR_CHANNEL_MIDPOINT 128 -#define APPLY_CONTRAST(channel, factor) \ - ((int32_t)((channel - COLOR_CHANNEL_MIDPOINT) * factor + COLOR_CHANNEL_MIDPOINT)) -static nscolor zenFilterColorChannel(nscolor originalNS, nscolor accentNS) { - auto r1 = NS_GET_R(originalNS); - auto g1 = NS_GET_G(originalNS); - auto b1 = NS_GET_B(originalNS); +static nscolor zenFilterColorChannel(nscolor aOriginalColor, nscolor aAccentColor) { + auto r1 = NS_GET_R(aOriginalColor); + auto g1 = NS_GET_G(aOriginalColor); + auto b1 = NS_GET_B(aOriginalColor); + + auto r2 = NS_GET_R(aAccentColor); + auto g2 = NS_GET_G(aAccentColor); + auto b2 = NS_GET_B(aAccentColor); // It's a bit of a hacky solution, but instead of using alpha as what it is // (opacity), we use it to store contrast information for now. // We do this primarily to avoid having to deal with WebIDL structs and // serialization/deserialization between parent and content processes. - auto contrast = NS_GET_A(originalNS); - - auto r2 = NS_GET_R(accentNS); - auto g2 = NS_GET_G(accentNS); - auto b2 = NS_GET_B(accentNS); + auto contrast = NS_GET_A(aAccentColor); // Approximate perceived luminance in sRGB space // Coefficients per Rec.709; gamma correction ignored for speed @@ -63,22 +61,40 @@ static nscolor zenFilterColorChannel(nscolor originalNS, nscolor accentNS) { double scale = accentLum > 0.0 ? (origLum / accentLum) : 1.0; - uint8_t fr = clamp255(r2 * scale); - uint8_t fg = clamp255(g2 * scale); - uint8_t fb = clamp255(b2 * scale); - - // Apply contrast adjustment: map contrast from 0-255 to 0.0-2.0 - // contrast = 0: reduce contrast (factor = 0.0, moves toward middle gray) - // contrast = 127.5: no change (factor = 1.0) - // contrast = 255: increase contrast (factor = 2.0, lighter/darker extremes) - double contrastFactor = contrast / 127.5; - - // Apply contrast: adjust each channel relative to middle gray - fr = clamp255(APPLY_CONTRAST(fr, contrastFactor)); - fg = clamp255(APPLY_CONTRAST(fg, contrastFactor)); - fb = clamp255(APPLY_CONTRAST(fb, contrastFactor)); - - return NS_RGBA(fr, fg, fb, 1); + double fr = r2 * scale; + double fg = g2 * scale; + double fb = b2 * scale; + + // Apply contrast adjustment: map contrast from 0–255 to -1.0–+1.0 + // contrast = 0: maximum darkening (mix toward black) + // contrast = 127.5: no change + // contrast = 255: maximum lightening (mix toward white) + double contrastFactor = (contrast - 128.0) / 128.0; + + // Compute perceived luminance for the filtered color + double lum = 0.2126 * fr + 0.7152 * fg + 0.0722 * fb; + + // If it's bright, mix toward white; if dark, mix toward black + if (lum >= COLOR_CHANNEL_MIDPOINT) { + double mix = (lum - COLOR_CHANNEL_MIDPOINT) / COLOR_CHANNEL_MIDPOINT; + double amount = contrastFactor * mix; + fr = fr + (255.0 - fr) * amount; + fg = fg + (255.0 - fg) * amount; + fb = fb + (255.0 - fb) * amount; + } else { + double mix = (COLOR_CHANNEL_MIDPOINT - lum) / COLOR_CHANNEL_MIDPOINT; + double amount = -contrastFactor * mix; + fr = fr * (1.0 - amount); + fg = fg * (1.0 - amount); + fb = fb * (1.0 - amount); + } + + // Clamp to [0,255] using fast branchless clamp + uint8_t fr8 = clamp255(fr); + uint8_t fg8 = clamp255(fg); + uint8_t fb8 = clamp255(fb); + + return NS_RGB(fr8, fg8, fb8); } } // namespace From a554c6cd433fff0a09149de898a50f00cb8bf0a1 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 7 Nov 2025 20:41:43 +0100 Subject: [PATCH 24/33] feat: update editor UI to look more like Arc's --- .../base/content/zen-assets.jar.inc.mn | 1 + .../base/content/zen-panels/site-data.inc | 2 + .../selectable/arrow-rotate-anticlockwise.svg | 2 + .../zen-icons/common/selectable/block.svg | 2 +- .../zen-icons/common/selectable/bolt.svg | 2 + .../common/selectable/boost-filled-small.svg | 2 - .../common/selectable/brackets-curly.svg | 2 + .../zen-icons/common/selectable/brush.svg | 5 - .../zen-icons/common/selectable/bulb.svg | 2 - .../common/selectable/close-filled-round.svg | 2 + .../zen-icons/common/selectable/controls.svg | 2 - .../zen-icons/common/selectable/lightbulb.svg | 2 + .../common/selectable/paintbrush-fill.svg | 2 + .../common/selectable/paintbrush.svg | 2 + .../zen-icons/common/selectable/sliders.svg | 2 + .../common/selectable/square-wand-sparkle.svg | 2 + .../common/selectable/text-lowercase.svg | 2 + .../zen-icons/common/selectable/text-size.svg | 2 + .../common/selectable/text-title-case.svg | 2 + .../common/selectable/text-uppercase.svg | 2 + .../common/selectable/wand-sparkle.svg | 2 + .../zen-icons/common/selectable/zap.svg | 2 - src/browser/themes/shared/zen-icons/icons.css | 87 +++- .../themes/shared/zen-icons/jar.inc.mn | 19 +- src/zen/boosts/ZenBoostsEditor.sys.mjs | 240 +++++++-- src/zen/boosts/ZenBoostsManager.sys.mjs | 42 +- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 6 +- src/zen/boosts/actors/ZenBoostsParent.sys.mjs | 2 - src/zen/boosts/zen-advanced-color-options.css | 53 ++ src/zen/boosts/zen-boost-editor.xhtml | 104 ++-- src/zen/boosts/zen-boosts.css | 458 ++++++++++++++---- src/zen/common/ZenUIManager.mjs | 52 +- src/zen/common/styles/zen-animations.css | 12 + .../common/styles/zen-single-components.css | 2 + src/zen/urlbar/ZenSiteDataPanel.sys.mjs | 111 ++--- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 6 +- 36 files changed, 877 insertions(+), 363 deletions(-) create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/arrow-rotate-anticlockwise.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/bolt.svg delete mode 100644 src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/brackets-curly.svg delete mode 100644 src/browser/themes/shared/zen-icons/common/selectable/brush.svg delete mode 100644 src/browser/themes/shared/zen-icons/common/selectable/bulb.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/close-filled-round.svg delete mode 100644 src/browser/themes/shared/zen-icons/common/selectable/controls.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/lightbulb.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/paintbrush-fill.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/sliders.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/square-wand-sparkle.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/text-lowercase.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/text-size.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/text-title-case.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/text-uppercase.svg create mode 100644 src/browser/themes/shared/zen-icons/common/selectable/wand-sparkle.svg delete mode 100644 src/browser/themes/shared/zen-icons/common/selectable/zap.svg create mode 100644 src/zen/boosts/zen-advanced-color-options.css diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index 8bc7ca603b..e294bfd9b3 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -73,6 +73,7 @@ content/browser/zen-styles/zen-download-box-animation.css (../../zen/downloads/zen-download-box-animation.css) content/browser/zen-styles/zen-boosts.css (../../zen/boosts/zen-boosts.css) + content/browser/zen-styles/zen-advanced-color-options.css (../../zen/boosts/zen-advanced-color-options.css) # Windows content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.xhtml) diff --git a/src/browser/base/content/zen-panels/site-data.inc b/src/browser/base/content/zen-panels/site-data.inc index bf310982be..b1c03ed685 100644 --- a/src/browser/base/content/zen-panels/site-data.inc +++ b/src/browser/base/content/zen-panels/site-data.inc @@ -77,6 +77,8 @@ + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/block.svg b/src/browser/themes/shared/zen-icons/common/selectable/block.svg index a4074ed6b4..68825ac937 100644 --- a/src/browser/themes/shared/zen-icons/common/selectable/block.svg +++ b/src/browser/themes/shared/zen-icons/common/selectable/block.svg @@ -1,2 +1,2 @@ #filter dumbComments emptyLines substitution - \ No newline at end of file + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/bolt.svg b/src/browser/themes/shared/zen-icons/common/selectable/bolt.svg new file mode 100644 index 0000000000..0ded312ec8 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/bolt.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg b/src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg deleted file mode 100644 index cce6691a1f..0000000000 --- a/src/browser/themes/shared/zen-icons/common/selectable/boost-filled-small.svg +++ /dev/null @@ -1,2 +0,0 @@ -#filter dumbComments emptyLines substitution - \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/brackets-curly.svg b/src/browser/themes/shared/zen-icons/common/selectable/brackets-curly.svg new file mode 100644 index 0000000000..53b939cfef --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/brackets-curly.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/brush.svg b/src/browser/themes/shared/zen-icons/common/selectable/brush.svg deleted file mode 100644 index 9bf05281fa..0000000000 --- a/src/browser/themes/shared/zen-icons/common/selectable/brush.svg +++ /dev/null @@ -1,5 +0,0 @@ -#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/. - \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg b/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg deleted file mode 100644 index 7dd48bdba2..0000000000 --- a/src/browser/themes/shared/zen-icons/common/selectable/bulb.svg +++ /dev/null @@ -1,2 +0,0 @@ -#filter dumbComments emptyLines substitution - \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/close-filled-round.svg b/src/browser/themes/shared/zen-icons/common/selectable/close-filled-round.svg new file mode 100644 index 0000000000..88fe91a301 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/close-filled-round.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/controls.svg b/src/browser/themes/shared/zen-icons/common/selectable/controls.svg deleted file mode 100644 index 6defe9db80..0000000000 --- a/src/browser/themes/shared/zen-icons/common/selectable/controls.svg +++ /dev/null @@ -1,2 +0,0 @@ -#filter dumbComments emptyLines substitution - \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/lightbulb.svg b/src/browser/themes/shared/zen-icons/common/selectable/lightbulb.svg new file mode 100644 index 0000000000..0df3ba4544 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/lightbulb.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/paintbrush-fill.svg b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush-fill.svg new file mode 100644 index 0000000000..eb5bebb415 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush-fill.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg new file mode 100644 index 0000000000..935eda10f4 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/paintbrush.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/sliders.svg b/src/browser/themes/shared/zen-icons/common/selectable/sliders.svg new file mode 100644 index 0000000000..7f5e255ebc --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/sliders.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/square-wand-sparkle.svg b/src/browser/themes/shared/zen-icons/common/selectable/square-wand-sparkle.svg new file mode 100644 index 0000000000..d9d3d2c6a8 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/square-wand-sparkle.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/text-lowercase.svg b/src/browser/themes/shared/zen-icons/common/selectable/text-lowercase.svg new file mode 100644 index 0000000000..7165501ab1 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/text-lowercase.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/text-size.svg b/src/browser/themes/shared/zen-icons/common/selectable/text-size.svg new file mode 100644 index 0000000000..39659f9338 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/text-size.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/text-title-case.svg b/src/browser/themes/shared/zen-icons/common/selectable/text-title-case.svg new file mode 100644 index 0000000000..211cd86440 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/text-title-case.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/text-uppercase.svg b/src/browser/themes/shared/zen-icons/common/selectable/text-uppercase.svg new file mode 100644 index 0000000000..dd643f8e40 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/text-uppercase.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/wand-sparkle.svg b/src/browser/themes/shared/zen-icons/common/selectable/wand-sparkle.svg new file mode 100644 index 0000000000..62777c2db3 --- /dev/null +++ b/src/browser/themes/shared/zen-icons/common/selectable/wand-sparkle.svg @@ -0,0 +1,2 @@ +#filter dumbComments emptyLines substitution + \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg b/src/browser/themes/shared/zen-icons/common/selectable/zap.svg deleted file mode 100644 index 6f90b8f1dd..0000000000 --- a/src/browser/themes/shared/zen-icons/common/selectable/zap.svg +++ /dev/null @@ -1,2 +0,0 @@ -#filter dumbComments emptyLines substitution - \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 51dd1ba8d8..4c35f4a3a0 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -16,22 +16,10 @@ list-style-image: url('account-private.svg') !important; border-radius: 100% !important; } - -.zen-boost { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/boost-filled-small.svg') !important; -} - -.zen-boost::after { - content: ''; - position: absolute; - width: 85%; - height: 85%; - opacity: 1; - background: url('chrome://browser/content/zen-images/boost-indicator.svg') no-repeat; - z-index: 0; - pointer-events: none; - filter: invert(); -} +/* +.down-arrow-icon { + list-style-image: url('chrome://browser/skin/zen-icons/arrow-down.svg') !important; +} */ #back-button { list-style-image: url('back.svg') !important; @@ -534,6 +522,9 @@ &[open] image { list-style-image: url('permissions-fill.svg'); } + &[boosting] image { + list-style-image: url('permissions-fill.svg'); + } position: relative; } @@ -918,6 +909,34 @@ fill: currentColor; } +#zen-site-data-boost { + -moz-context-properties: fill, fill-opacity; + fill: currentColor; + border-radius: 6px; + appearance: none; + padding: 6px 6px 6px 6px; + + position: relative; + + list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush.svg') !important; +} + +#zen-site-data-boost[boosting] { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush-fill.svg') !important; +} + +#zen-site-data-boost[boosting]::after { + content: ''; + position: absolute; + width: 90%; + height: 90%; + opacity: 1; + translate: -5px 0px; + background: url('chrome://browser/content/zen-images/boost-indicator.svg') no-repeat; + z-index: 0; + pointer-events: none; +} + #zen-site-data-security-info { -moz-context-properties: fill, fill-opacity; fill: currentColor; @@ -978,14 +997,46 @@ fill-opacity: 0.7; } +#zen-boost-text-case-toggle { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-title-case.svg') !important; +} + +#zen-boost-text-case-toggle[mode='upper'] { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-uppercase.svg') !important; +} + +#zen-boost-text-case-toggle[mode='lower'] { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-lowercase.svg') !important; +} + +#zen-boost-code { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/brackets-curly.svg') !important; +} + #zen-boost-zap { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/zap.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/bolt.svg') !important; +} + +#zen-boost-controls { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/sliders.svg') !important; } #zen-boost-invert { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/bulb.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/lightbulb.svg') !important; } #zen-boost-disable { list-style-image: url('chrome://browser/skin/zen-icons/selectable/block.svg') !important; } + +#zen-boost-close { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/close-filled-round.svg') !important; +} + +#zen-boost-magic-theme { + list-style-image: url('chrome://browser/skin/zen-icons/selectable/square-wand-sparkle.svg') !important; +} + +#zen-boost-shuffle { +list-style-image: url('chrome://browser/skin/zen-icons/selectable/arrow-rotate-anticlockwise.svg') !important; +} \ No newline at end of file diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn index 4ea9779d6a..8353b4e2e3 100644 --- a/src/browser/themes/shared/zen-icons/jar.inc.mn +++ b/src/browser/themes/shared/zen-icons/jar.inc.mn @@ -464,31 +464,31 @@ * skin/classic/browser/zen-icons/urlbar-arrow.svg (../shared/zen-icons/common/urlbar-arrow.svg) * skin/classic/browser/zen-icons/selectable/airplane.svg (../shared/zen-icons/common/selectable/airplane.svg) * skin/classic/browser/zen-icons/selectable/american-football.svg (../shared/zen-icons/common/selectable/american-football.svg) +* skin/classic/browser/zen-icons/selectable/arrow-rotate-anticlockwise.svg (../shared/zen-icons/common/selectable/arrow-rotate-anticlockwise.svg) * skin/classic/browser/zen-icons/selectable/baseball.svg (../shared/zen-icons/common/selectable/baseball.svg) * 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/block.svg (../shared/zen-icons/common/selectable/block.svg) +* skin/classic/browser/zen-icons/selectable/bolt.svg (../shared/zen-icons/common/selectable/bolt.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/boost-filled-small.svg (../shared/zen-icons/common/selectable/boost-filled-small.svg) * skin/classic/browser/zen-icons/selectable/boost.svg (../shared/zen-icons/common/selectable/boost.svg) +* skin/classic/browser/zen-icons/selectable/brackets-curly.svg (../shared/zen-icons/common/selectable/brackets-curly.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) * skin/classic/browser/zen-icons/selectable/build.svg (../shared/zen-icons/common/selectable/build.svg) -* skin/classic/browser/zen-icons/selectable/bulb.svg (../shared/zen-icons/common/selectable/bulb.svg) * skin/classic/browser/zen-icons/selectable/cafe.svg (../shared/zen-icons/common/selectable/cafe.svg) * skin/classic/browser/zen-icons/selectable/call.svg (../shared/zen-icons/common/selectable/call.svg) * skin/classic/browser/zen-icons/selectable/card.svg (../shared/zen-icons/common/selectable/card.svg) * skin/classic/browser/zen-icons/selectable/chat.svg (../shared/zen-icons/common/selectable/chat.svg) * skin/classic/browser/zen-icons/selectable/checkbox.svg (../shared/zen-icons/common/selectable/checkbox.svg) * skin/classic/browser/zen-icons/selectable/circle.svg (../shared/zen-icons/common/selectable/circle.svg) +* skin/classic/browser/zen-icons/selectable/close-filled-round.svg (../shared/zen-icons/common/selectable/close-filled-round.svg) * skin/classic/browser/zen-icons/selectable/cloud.svg (../shared/zen-icons/common/selectable/cloud.svg) * skin/classic/browser/zen-icons/selectable/code.svg (../shared/zen-icons/common/selectable/code.svg) * skin/classic/browser/zen-icons/selectable/coins.svg (../shared/zen-icons/common/selectable/coins.svg) * skin/classic/browser/zen-icons/selectable/construct.svg (../shared/zen-icons/common/selectable/construct.svg) -* skin/classic/browser/zen-icons/selectable/controls.svg (../shared/zen-icons/common/selectable/controls.svg) * skin/classic/browser/zen-icons/selectable/cutlery.svg (../shared/zen-icons/common/selectable/cutlery.svg) * skin/classic/browser/zen-icons/selectable/egg.svg (../shared/zen-icons/common/selectable/egg.svg) * skin/classic/browser/zen-icons/selectable/extension-puzzle.svg (../shared/zen-icons/common/selectable/extension-puzzle.svg) @@ -511,6 +511,7 @@ * skin/classic/browser/zen-icons/selectable/key.svg (../shared/zen-icons/common/selectable/key.svg) * skin/classic/browser/zen-icons/selectable/layers.svg (../shared/zen-icons/common/selectable/layers.svg) * skin/classic/browser/zen-icons/selectable/leaf.svg (../shared/zen-icons/common/selectable/leaf.svg) +* skin/classic/browser/zen-icons/selectable/lightbulb.svg (../shared/zen-icons/common/selectable/lightbulb.svg) * skin/classic/browser/zen-icons/selectable/lightning.svg (../shared/zen-icons/common/selectable/lightning.svg) * skin/classic/browser/zen-icons/selectable/location.svg (../shared/zen-icons/common/selectable/location.svg) * skin/classic/browser/zen-icons/selectable/lock-closed.svg (../shared/zen-icons/common/selectable/lock-closed.svg) @@ -524,6 +525,8 @@ * skin/classic/browser/zen-icons/selectable/navigate.svg (../shared/zen-icons/common/selectable/navigate.svg) * skin/classic/browser/zen-icons/selectable/nuclear.svg (../shared/zen-icons/common/selectable/nuclear.svg) * skin/classic/browser/zen-icons/selectable/page.svg (../shared/zen-icons/common/selectable/page.svg) +* skin/classic/browser/zen-icons/selectable/paintbrush-fill.svg (../shared/zen-icons/common/selectable/paintbrush-fill.svg) +* skin/classic/browser/zen-icons/selectable/paintbrush.svg (../shared/zen-icons/common/selectable/paintbrush.svg) * skin/classic/browser/zen-icons/selectable/palette.svg (../shared/zen-icons/common/selectable/palette.svg) * skin/classic/browser/zen-icons/selectable/paw.svg (../shared/zen-icons/common/selectable/paw.svg) * skin/classic/browser/zen-icons/selectable/people.svg (../shared/zen-icons/common/selectable/people.svg) @@ -535,6 +538,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/sliders.svg (../shared/zen-icons/common/selectable/sliders.svg) +* skin/classic/browser/zen-icons/selectable/square-wand-sparkle.svg (../shared/zen-icons/common/selectable/square-wand-sparkle.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/star-1.svg (../shared/zen-icons/common/selectable/star-1.svg) @@ -543,6 +548,10 @@ * skin/classic/browser/zen-icons/selectable/sun.svg (../shared/zen-icons/common/selectable/sun.svg) * skin/classic/browser/zen-icons/selectable/tada.svg (../shared/zen-icons/common/selectable/tada.svg) * skin/classic/browser/zen-icons/selectable/terminal.svg (../shared/zen-icons/common/selectable/terminal.svg) +* skin/classic/browser/zen-icons/selectable/text-lowercase.svg (../shared/zen-icons/common/selectable/text-lowercase.svg) +* skin/classic/browser/zen-icons/selectable/text-size.svg (../shared/zen-icons/common/selectable/text-size.svg) +* skin/classic/browser/zen-icons/selectable/text-title-case.svg (../shared/zen-icons/common/selectable/text-title-case.svg) +* skin/classic/browser/zen-icons/selectable/text-uppercase.svg (../shared/zen-icons/common/selectable/text-uppercase.svg) * skin/classic/browser/zen-icons/selectable/ticket.svg (../shared/zen-icons/common/selectable/ticket.svg) * skin/classic/browser/zen-icons/selectable/time.svg (../shared/zen-icons/common/selectable/time.svg) * skin/classic/browser/zen-icons/selectable/trash.svg (../shared/zen-icons/common/selectable/trash.svg) @@ -550,8 +559,8 @@ * skin/classic/browser/zen-icons/selectable/video.svg (../shared/zen-icons/common/selectable/video.svg) * skin/classic/browser/zen-icons/selectable/volume-high.svg (../shared/zen-icons/common/selectable/volume-high.svg) * skin/classic/browser/zen-icons/selectable/wallet.svg (../shared/zen-icons/common/selectable/wallet.svg) +* skin/classic/browser/zen-icons/selectable/wand-sparkle.svg (../shared/zen-icons/common/selectable/wand-sparkle.svg) * skin/classic/browser/zen-icons/selectable/warning.svg (../shared/zen-icons/common/selectable/warning.svg) * skin/classic/browser/zen-icons/selectable/water.svg (../shared/zen-icons/common/selectable/water.svg) * skin/classic/browser/zen-icons/selectable/weight.svg (../shared/zen-icons/common/selectable/weight.svg) -* skin/classic/browser/zen-icons/selectable/zap.svg (../shared/zen-icons/common/selectable/zap.svg) skin/classic/browser/zen-icons/icons.css (../shared/zen-icons/icons.css) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 1e66754b48..5e8ab781a7 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -22,6 +22,7 @@ export class nsZenBoostEditor { this.init(); this.initColorPicker(); + this.initFonts(); this.loadBoost(domain); } @@ -29,32 +30,21 @@ export class nsZenBoostEditor { this.window.addEventListener('unload', () => this.handleClose(), { once: true }); this.doc - .getElementById('zen-boost-font-arial') - .addEventListener('click', (event) => this.onFontChange(event, 'Arial, sans-serif')); + .getElementById('zen-boost-color-contrast') + .addEventListener('onchange', this.onColorOptionChange.bind(this)); this.doc - .getElementById('zen-boost-font-serif') - .addEventListener('click', (event) => this.onFontChange(event, "'Times New Roman', serif")); + .getElementById('zen-boost-color-brightness') + .addEventListener('onchange', this.onColorOptionChange.bind(this)); this.doc - .getElementById('zen-boost-font-mono') - .addEventListener('click', (event) => this.onFontChange(event, "'Courier New', monospace")); - this.doc - .getElementById('zen-boost-font-georgia') - .addEventListener('click', (event) => this.onFontChange(event, "'Georgia', serif")); - this.doc - .getElementById('zen-boost-font-tahoma') - .addEventListener('click', (event) => this.onFontChange(event, 'Tahoma')); + .getElementById('zen-boost-color-saturation') + .addEventListener('onchange', this.onColorOptionChange.bind(this)); + this.doc - .getElementById('zen-boost-font-verdana') - .addEventListener('click', (event) => this.onFontChange(event, 'Verdana')); + .getElementById('zen-boost-text-case-toggle') + .addEventListener('click', this.onBoostCasePressed.bind(this)); this.doc - .getElementById('zen-boost-font-comic') - .addEventListener('click', (event) => this.onFontChange(event, "'Comic Sans MS'")); - this.doc - .getElementById('zen-boost-font-corsiva') - .addEventListener('click', (event) => - this.onFontChange(event, "'Monotype Corsiva, cursive'") - ); - + .getElementById('zen-boost-size') + .addEventListener('click', this.onBoostSizePressed.bind(this)); this.doc .getElementById('zen-boost-zap') .addEventListener('click', () => console.error('Not implemented')); @@ -65,8 +55,11 @@ export class nsZenBoostEditor { .getElementById('zen-boost-invert') .addEventListener('click', this.onToggleInvert.bind(this)); this.doc - .getElementById('zen-boost-delete') - .addEventListener('click', this.onDeleteBoost.bind(this)); + .getElementById('zen-boost-controls') + .addEventListener('click', (event) => this.openAdvancedColorOptions(event)); + this.doc + .getElementById('zen-boost-close') + .addEventListener('click', this.onClosePressed.bind(this)); this.doc .getElementById('zen-boost-name') @@ -99,6 +92,39 @@ export class nsZenBoostEditor { }); } + initFonts() { + const fonts = [ + 'Arial, sans-serif', + "'Times New Roman', serif", + "'Courier New', monospace", + "'Georgia', serif", + "'Comic Sans MS'", + ]; + + const fontButtonGroup = this.doc.getElementById('zen-boost-font-grid'); + const fontSelect = this.doc.getElementById('zen-boost-font-select'); + const buttonCount = 10; + + for (let i = 0; i < Math.min(buttonCount, fonts.length); i++) { + const fontButton = this.doc.createElement('button'); + fontButton.setAttribute('font-data', `${fonts[i]}`); + fontButton.classList.add('subviewbutton'); + fontButton.style.fontFamily = fonts[i]; + fontButton.innerHTML = 'Aa'; + fontButton.addEventListener('click', this.onFontChange.bind(this)); + + fontButtonGroup.appendChild(fontButton); + } + + for (let j = 0; j < fonts.length; j++) { + const font = fonts[j]; + const select = this.doc.createElement('option'); + select.value = font; + select.innerHTML = font; + fontSelect.appendChild(select); + } + } + initColorPicker() { const themePicker = this.doc.querySelector('.zen-boost-color-picker-gradient'); this._onMouseMove = this.onMouseMove.bind(this); @@ -127,7 +153,9 @@ export class nsZenBoostEditor { if (this.isMouseDown) { this.wasDragging = true; event.preventDefault(); - this.setDotPos(event.clientX, event.clientY, false); + + if (event.target.id != 'zen-boost-magic-theme') + this.setDotPos(event.clientX, event.clientY, false); } } @@ -148,6 +176,54 @@ export class nsZenBoostEditor { this.wasDragging = false; } + onBoostSizePressed(event) { + const sizeValue = this.doc.getElementById('zen-boost-size-value'); + + if (this.currentBoostData.siteSizeOverride >= 150) this.currentBoostData.siteSizeOverride = 90; + else if (this.currentBoostData.siteSizeOverride >= 125) + this.currentBoostData.siteSizeOverride = 150; + else if (this.currentBoostData.siteSizeOverride >= 110) + this.currentBoostData.siteSizeOverride = 125; + else if (this.currentBoostData.siteSizeOverride >= 100) + this.currentBoostData.siteSizeOverride = 110; + else if (this.currentBoostData.siteSizeOverride >= 90) + this.currentBoostData.siteSizeOverride = -1; + else this.currentBoostData.siteSizeOverride = 110; + + if (this.currentBoostData.siteSizeOverride == -1) sizeValue.innerHTML = ``; + else sizeValue.innerHTML = `${this.currentBoostData.siteSizeOverride}%`; + + this.updateSizeButtonVisuals(); + this.updateCurrentBoost(); + } + + onBoostCasePressed(event) { + const caseButton = this.doc.getElementById('zen-boost-text-case-toggle'); + + if(this.currentBoostData.textCaseOverride == 'lower') + this.currentBoostData.textCaseOverride = 'upper'; + else if(this.currentBoostData.textCaseOverride == 'upper') + this.currentBoostData.textCaseOverride = 'none'; + else + this.currentBoostData.textCaseOverride = 'lower'; + + this.updateCaseButtonVisuals(); + this.updateCurrentBoost(); + } + + onColorOptionChange(event) { + this.currentBoostData.contrast = this.doc.getElementById('zen-boost-color-contrast').value; + this.currentBoostData.brightness = this.doc.getElementById('zen-boost-color-brightness').value; + this.currentBoostData.saturation = this.doc.getElementById('zen-boost-color-saturation').value; + + this.updateCurrentBoost(); + } + + openAdvancedColorOptions(event) { + const panel = this.doc.getElementById('zen-boost-advanced-color-options-panel'); + panel.openPopup(event.target, 'bottomcenter topcenter', 0, 5); + } + resetDotPosition() { this.setDotPos(null, null); } @@ -155,7 +231,13 @@ export class nsZenBoostEditor { onThemePickerClick(event) { event.preventDefault(); - this.setDotPos(event.clientX, event.clientY, !this.wasDragging); + this.currentBoostData.changeWasMade = true; + + if (event.target.id == 'zen-boost-magic-theme') { + this.currentBoostData.autoTheme = !this.currentBoostData.autoTheme; + this.updateButtonToggleVisuals(); + this.updateCurrentBoost(); + } else this.setDotPos(event.clientX, event.clientY, !this.wasDragging); this.wasDragging = false; } @@ -165,7 +247,7 @@ export class nsZenBoostEditor { const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); const rect = gradient.getBoundingClientRect(); - const padding = 40; + const padding = 50; const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; @@ -238,8 +320,10 @@ export class nsZenBoostEditor { // } // Enable color boosting again - if (!this.currentBoostData.enableColorBoost) this.onToggleDisable(null); + if (!this.currentBoostData.enableColorBoost) this.onToggleDisable(false); + this.currentBoostData.autoTheme = false; + this.updateButtonToggleVisuals(); this.updateDot(); this.updateCurrentBoost(); } @@ -253,7 +337,7 @@ export class nsZenBoostEditor { const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); const rect = gradient.getBoundingClientRect(); - const padding = 40; + const padding = 50; const radius = (rect.width - padding) / 2; const circle = this.doc.querySelector('.zen-boost-color-picker-circle'); @@ -262,26 +346,61 @@ export class nsZenBoostEditor { } // This toggles the color changes - onToggleDisable() { + onToggleDisable(userAction = true) { this.currentBoostData.enableColorBoost = !this.currentBoostData.enableColorBoost; + if (userAction) this.currentBoostData.changeWasMade = true; + this.updateButtonToggleVisuals(); this.updateCurrentBoost(); } - onToggleInvert() { + onToggleInvert(userAction = true) { this.currentBoostData.enableColorBoost = true; this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; + if (userAction) this.currentBoostData.changeWasMade = true; + this.updateButtonToggleVisuals(); this.updateCurrentBoost(); } + updateSizeButtonVisuals() { + const sizeValue = this.doc.getElementById('zen-boost-size'); + + if (this.currentBoostData.siteSizeOverride >= 150) sizeValue.setAttribute('mode', 'red'); + else if (this.currentBoostData.siteSizeOverride >= 125) + sizeValue.setAttribute('mode', 'orange-red'); + else if (this.currentBoostData.siteSizeOverride >= 110) + sizeValue.setAttribute('mode', 'orange'); + else if (this.currentBoostData.siteSizeOverride >= 100) sizeValue.setAttribute('mode', 'none'); + else if (this.currentBoostData.siteSizeOverride >= 90) sizeValue.setAttribute('mode', 'blue'); + else sizeValue.setAttribute('mode', 'none'); + } + + updateCaseButtonVisuals() { + const sizeValue = this.doc.getElementById('zen-boost-text-case-toggle'); + + if (this.currentBoostData.textCaseOverride == 'none') + sizeValue.setAttribute('mode', 'none'); + else if (this.currentBoostData.textCaseOverride == 'upper') + sizeValue.setAttribute('mode', 'upper'); + else if (this.currentBoostData.textCaseOverride == 'lower') + sizeValue.setAttribute('mode', 'lower'); + } + updateButtonToggleVisuals() { const invertButton = this.doc.getElementById('zen-boost-invert'); const disableButton = this.doc.getElementById('zen-boost-disable'); + const autoThemeButton = this.doc.getElementById('zen-boost-magic-theme'); const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); + if (this.currentBoostData.autoTheme) autoThemeButton.classList.add('zen-boost-button-active'); + else autoThemeButton.classList.remove('zen-boost-button-active'); + + if (this.currentBoostData.smartInvert) invertButton.classList.add('zen-boost-button-active'); + else invertButton.classList.remove('zen-boost-button-active'); + if (this.currentBoostData.smartInvert) invertButton.classList.add('zen-boost-button-active'); else invertButton.classList.remove('zen-boost-button-active'); @@ -289,61 +408,92 @@ export class nsZenBoostEditor { disableButton.classList.add('zen-boost-button-active'); else disableButton.classList.remove('zen-boost-button-active'); - if (!this.currentBoostData.enableColorBoost) gradient.classList.add('zen-boost-panel-disabled'); + // Give the gradient a grayscale effect + // when the color boosting is disabled + // or the theme is set automatically + if (!this.currentBoostData.enableColorBoost || this.currentBoostData.autoTheme) + gradient.classList.add('zen-boost-panel-disabled'); else gradient.classList.remove('zen-boost-panel-disabled'); } - onFontChange(event, fontFamily) { - if (this.currentBoostData.fontFamily == fontFamily) this.currentBoostData.fontFamily = ''; - else this.currentBoostData.fontFamily = fontFamily; + onFontChange(event) { + const font = event?.target?.getAttribute('font-data') ?? ''; + if (this.currentBoostData.fontFamily == font) this.currentBoostData.fontFamily = ''; + else this.currentBoostData.fontFamily = font; + this.updateFontButtonVisuals(); + this.currentBoostData.changeWasMade = true; this.updateCurrentBoost(); } + updateFontButtonVisuals() { + const fontButtonGroup = this.doc.getElementById('zen-boost-font-grid'); + for (let i = 0; i < fontButtonGroup.children.length; i++) { + const fontButton = fontButtonGroup.children[i]; + if (fontButton.getAttribute('font-data') == this.currentBoostData.fontFamily) + fontButton.classList.add('zen-boost-font-button-active'); + else fontButton.classList.remove('zen-boost-font-button-active'); + } + } + updateCurrentBoost() { gZenBoostsManager.updateBoost(this.currentBoostData); } onDeleteBoost() { - this.window.prompt; - gZenBoostsManager.deleteBoost(this.currentBoostData.domain); this.currentBoostData = null; - - // Still write modifications to disk - gZenBoostsManager.saveBoostToStore(null); this.window.gZenUIManager.showToast('zen-panel-ui-boosts-deleted-message'); this.window.close(); } + onClosePressed() { + this.window.close(); + } + handleClose() { this.uninit(); - if (this.currentBoostData != null) this.saveBoost(); + if (this.currentBoostData != null && this.currentBoostData.changeWasMade) this.saveBoost(); + else if (this.currentBoostData != null && !this.currentBoostData.changeWasMade) + gZenBoostsManager.deleteBoost(this.currentBoostData.domain); } loadBoost(domain) { this.currentBoostData = gZenBoostsManager.loadBoostFromStore(domain); // Initial save to register the boost - gZenBoostsManager.saveBoostToStore(null); + gZenBoostsManager.saveBoostToStore(this.currentBoostData); - this.doc.getElementById('zen-boost-name').value = this.currentBoostData.boostName; + this.doc.getElementById('zen-boost-name-text').innerHTML = domain; const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); + const contrastSlider = this.doc.getElementById('zen-boost-color-contrast'); + const brightnessSlider = this.doc.getElementById('zen-boost-color-brightness'); + const saturationSlider = this.doc.getElementById('zen-boost-color-saturation'); + if (this.currentBoostData.dotPos.x == null || this.currentBoostData.dotPos.y == null) this.resetDotPosition(); else { dot.style.left = `${this.currentBoostData.dotPos.x}px`; dot.style.top = `${this.currentBoostData.dotPos.y}px`; + this.updateFontButtonVisuals(); + this.updateSizeButtonVisuals(); + this.updateCaseButtonVisuals(); + + contrastSlider.value = this.currentBoostData.contrast; + brightnessSlider.value = this.currentBoostData.brightness; + saturationSlider.value = this.currentBoostData.saturation; } this.updateDot(); this.updateButtonToggleVisuals(); } - saveBoost() { + saveBoost(showToast = true) { + if (this.currentBoostData == null || !this.currentBoostData.changeWasMade) return; + gZenBoostsManager.saveBoostToStore(this.currentBoostData); - this.window.gZenUIManager.showToast('zen-panel-ui-boosts-saved-message'); + if (showToast) this.window.gZenUIManager.showToast('zen-panel-ui-boosts-saved-message'); } } diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 62a855f8d9..5cb58bd28c 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -2,14 +2,7 @@ // 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/. -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - BrowserWindowTracker: 'resource:///modules/BrowserWindowTracker.sys.mjs', -}); - export class nsZenBoostsManager { - initialized = false; registeredBoosts = new Map(); #saveFilename = 'zen-boosts.jsonlz4'; @@ -19,12 +12,13 @@ export class nsZenBoostsManager { } #init() { - this.#readBoostsFromStore(() => (this.initialized = true)); + this.#readBoostsFromStore(this.notify); } deleteBoost(domain) { if (this.registeredBoosts.has(domain)) this.registeredBoosts.delete(domain); - Services.obs.notifyObservers(lazy.BrowserWindowTracker.getTopWindow(), 'zen-boosts-update'); + this.#writeToDisk(this.registeredBoosts); + this.notify(); } // Load a boost from a domain @@ -33,13 +27,29 @@ export class nsZenBoostsManager { let boostData = { domain, - boostName: 'New Boost', + boostName: 'My Boost', + dotAngleDeg: 0, dotPos: { x: null, y: null }, dotDistance: 0, + + brightness: 128, + contrast: 128, + saturation: 128, + fontFamily: '', + enableColorBoost: false, smartInvert: false, + + // Choses theme based on Zen's workspace theme + autoTheme: false, + + // Default to 100% scale + siteSizeOverride: 100, + textCaseOverride: 'none', + + changeWasMade: false }; if (this.registeredBoosts.has(domain)) { @@ -53,13 +63,19 @@ export class nsZenBoostsManager { updateBoost(boostData) { this.registeredBoosts.set(boostData.domain, boostData); - Services.obs.notifyObservers(lazy.BrowserWindowTracker.getTopWindow(), 'zen-boosts-update'); + this.notify(); + } + + notify() { + Services.obs.notifyObservers(null, 'zen-boosts-update'); } // Save all boosts to the profile folder - saveBoostToStore(boostData) { - if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); + saveBoostToStore(boostData) { + if (boostData != null) + this.registeredBoosts.set(boostData.domain, boostData); this.#writeToDisk(this.registeredBoosts); + this.notify(); } // Reads all boosts from the profile folder diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index ac98656325..d55f9f9c67 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -112,10 +112,10 @@ export class ZenBoostsChild extends JSWindowActorChild { if (boost.enableColorBoost) { const rgbColor = this.#hslToRgb( boost.dotAngleDeg / 360, - boost.dotDistance /* already is [0, 1] */, - 0.2 + boost.dotDistance * 0.6 /* lightness range from [0.2, 0.8] */ + boost.dotDistance * (boost.saturation / 255).toFixed(4) /* already is [0, 1] */, + 0.2 + boost.dotDistance * 0.8 * (boost.brightness / 255).toFixed(4) /* lightness range from [0.2, 0.8] */ ); - const nsColor = this.#rgbToNSColor(rgbColor); + const nsColor = this.#rgbToNSColor(rgbColor, boost.contrast); browsingContext.zenBoostsData = nsColor; } else browsingContext.zenBoostsData = 0; } else { diff --git a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs index cac42ad1e0..7f5be44d86 100644 --- a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs @@ -9,8 +9,6 @@ ChromeUtils.defineESModuleGetters(lazy, { }); export class ZenBoostsParent extends JSWindowActorParent { - canSendUpdate = true; - constructor() { super(); diff --git a/src/zen/boosts/zen-advanced-color-options.css b/src/zen/boosts/zen-advanced-color-options.css new file mode 100644 index 0000000000..8a050af8d9 --- /dev/null +++ b/src/zen/boosts/zen-advanced-color-options.css @@ -0,0 +1,53 @@ +/* + * 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/. + */ + +#zen-boost-advanced-color-options-panel { + color-scheme: light; + -zen-window-transform-origin: 50% 0%; + padding: 8px 8px; + + & p { + color: #3a3a3b; + } + + & input { + width: 185px; + appearance: none !important; + height: 4px; + + background-color: #b5b5b9; + border-radius: 2px; + + margin-top: 10px; + } + + & separator { + height: 24px !important; + } + + @media (-moz-platform: macos) { + &[animate='open'] { + animation: zen-color-options-panel-animation-macos 0.35s cubic-bezier(.29,1.37,.87,1) forwards !important; + } + } +} + +#zen-boost-advanced-color-options-panel input::-moz-range-thumb { + width: 18px; + height: 18px; + background-color: #e6e5ea; + border: solid 1px #d1d0d5; + border-radius: 100%; +} + +#zen-boost-advanced-color-options-panel input::-moz-range-thumb:active { + background-color: #cbcad0; +} + +#zen-boost-advanced-color-options-container { + display: flex !important; + flex-direction: column !important; +} diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.xhtml index 4a948c651f..e1393777ea 100644 --- a/src/zen/boosts/zen-boost-editor.xhtml +++ b/src/zen/boosts/zen-boost-editor.xhtml @@ -10,7 +10,9 @@ + + @@ -19,70 +21,94 @@ xmlns:html="http://www.w3.org/1999/xhtml" id="zenSettingsWindow" alwaysontop="true" - style="min-width: 135px; height: 28em"> + customtitlebar="true"> - + +
+ + + +
- - - - +
+ + - - - + + + - + +
- - - - - - - - + +
+ + + + + +
+ + + + + + + + + + + + + + +
- \ No newline at end of file + + +
+

Contrast

+ + +

Brightness

+ + +

Original Saturation

+ +
+
+ diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 77a0addb76..1e20dbd1cf 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -4,6 +4,62 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#zenSettingsWindow { + animation: 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) zen-boost-window-in; + border-radius: 14px !important; +} + +#zen-boost-editor-root { + border-radius: 14px !important; + background-color: #fcfcfe; + width: 185px; + + & button { + border-radius: 10px !important; + + padding: 0 !important; + min-width: fit-content !important; + + border: solid 6px transparent !important; + width: 26px; + height: 26px; + + transition: background 0.1s cubic-bezier(0.075, 0.82, 0.165, 1); + appearance: none; + + &:active { + scale: 1; + transform: none; + } + } + + & .button-text { + display: none; + } + + &[disabled] { + opacity: 0.5; + cursor: not-allowed; + } +} + +@keyframes zen-boost-window-in { + from { + transform: scale(0.9) translateY(20px); + opacity: 0; + filter: blur(6px); + } + to { + transform: scale(1) translateY(0); + opacity: 1; + filter: none; + } +} + +.subviewbutton { + color: #3a3a3b; +} + #zen-boost-font-arial { font-family: Arial, sans-serif; } @@ -21,30 +77,70 @@ } #zen-boost-head-wrapper { - margin-top: -14px; - margin-bottom: -14px; + -moz-window-dragging: drag; /* Allow dragging the window with the custom titlebar */ - & input { - height: 26px !important; + margin-bottom: -10px; + height: 40px !important; + min-height: 40px; + max-height: 40px; + align-items: center; + background-color: #f6f6f8; + border: solid 1px #ededef; + + display: flex; + + flex-direction: row-reverse; + @media (-moz-platform: macos) { + flex-direction: row; } -} -#zen-boost-font-wrapper { - filter: drop-shadow(0px 4px 8px #00000055); - background-color: #aaaaaa05; + & button { + height: 24px !important; + width: 24px !important; + aspect-ratio: 1/1; + background-color: transparent !important; + opacity: 0.45; - border-radius: 8px; + &:hover { + opacity: 0.6; + } + } - padding-top: 4px; - padding-bottom: 4px; + & #zen-boost-name { + height: 24px !important; + width: 90px !important; + color: black !important; + background-color: transparent; + border: none; + outline: none; + + display: flex; + align-items: center; + + opacity: 0.85; + + &:hover { + background-color: #ebebed; + opacity: 1; + } + } +} + +#zen-boost-name-text { + width: 100%; + text-align: center; + justify-content: center; + margin: auto; } #zen-boost-font-grid { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(5, 1fr); gap: 0px; width: 100%; + padding: 2px; + & button { padding: auto !important; margin: auto !important; @@ -52,97 +148,267 @@ } #zen-boost-filter-wrapper { - width: 135px; + padding: 20px; +} - & input { - height: 26px; +.big-button { + width: 100% !important; + margin: auto; + text-indent: 6px; + vertical-align: middle; + font-size: 10pt; - border: none; - background-color: transparent; - outline: none; + list-style-position: right; - & :active { - border: solid 1px #ffffff99; - } + & p { + width: 75%; + margin: auto; } +} - & button { - border-radius: 5px; +#zen-boost-size { + list-style-type: none !important; - padding: 0 !important; - min-width: fit-content !important; + & #zen-boost-size-value { + text-align: right; + right: 0; + margin: auto; + } +} - border: solid 6px transparent !important; - width: 26px; - height: 26px; +/* +#zen-boost-code { + opacity: 0.5 !important; + cursor: not-allowed !important; + pointer-events: none !important; +} - transition: background 0.2s; - appearance: none; - color: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.9)); +#zen-boost-zap { + opacity: 0.5 !important; + cursor: not-allowed !important; + pointer-events: none !important; +} */ - & .button-text { - display: none; - } +.zen-boost-panel-disabled { + filter: grayscale(1); +} - &:hover { - background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); - } +#zen-boost-filter-wrapper separator { + height: 7px !important; +} - &[disabled] { - opacity: 0.5; - cursor: not-allowed; - } +.title-button { + scale: 0.9; +} + +.mod-button { + height: 38px !important; + + transition: + 0.35s opacity cubic-bezier(0.075, 0.82, 0.165, 1), + 0.35s filter cubic-bezier(0.075, 0.82, 0.165, 1) !important; + + background-color: #ebebed; + &:hover { + opacity: 0.9; + } + + &:active { + scale: 1 !important; + transform: none !important; + + opacity: 0.9; + filter: brightness(0.9); } } -#zen-boost-editor-root { - padding: 15px; +#zen-boost-name { + height: 26px !important; + font-size: 8pt !important; + text-indent: 2px; + vertical-align: middle; + color: #3a3a3b !important; + + justify-content: center; + + & #zen-boost-name-text { + translate: 0px -2px; + } } -#zen-boost-delete { - width: auto; +#zen-boost-shuffle { + margin-left: 6px; + margin-right: 6px; } -#zen-boost-zap { - opacity: 0.5 !important; - cursor: not-allowed !important; - pointer-events: none !important; +#zen-boost-close { + margin-right: 10px; +} + +.zen-boost-font-button-active { + background-color: #5b5b5c22 !important; +} + +.zen-boost-font-button-active:hover { + background-color: #5b5b5c32 !important; +} + +.zen-boost-button-active { + background-color: #3a3a3a !important; + color: #fcfcfe !important; +} + +.zen-boost-button-active:hover { + background-color: #5b5b5c !important; +} + +#zen-boost-magic-theme { + width: 18px !important; + height: 18px !important; + margin-top: 12px; + padding: 2px; + + z-index: 4; + + position: relative; + margin-left: auto; + margin-right: auto; } #zen-boost-toolbar-wrapper { width: auto; margin: auto; - gap: 1em; + gap: 0.75em; - & button { - width: auto; - height: auto !important; - margin: auto; - background-color: #aaaaaa22; - padding: 4px 5px 4px 5px !important; + & .small { + width: 40px; + margin: auto !important; + padding: 5px 5px 5px 5px !important; + } + + & .med { + width: 70px; + margin: auto !important; + padding: 3px 5px 5px 3px !important; + + display: flex; + text-align: center; + + vertical-align: middle; + justify-content: center; + align-items: center; + font-size: 10pt; } } -.zen-boost-panel-disabled { - filter: grayscale(1); +#zen-boost-font-select { + width: 85px !important; + height: 20px !important; + + color: #727272 !important; + background-color: transparent; + background: none; + + font-size: 9pt; + + padding: 0px !important; + text-indent: 2px; + + margin-left: 8px !important; + margin-right: 8px !important; + margin-top: 8px !important; + + &:hover { + background-color: #9a9a9a44; + } } -#zen-boost-filter-wrapper separator { - height: 5px !important; +#zen-boost-font-toolbar { + display: flex; + flex-direction: row; + + & button { + border-radius: 6px !important; + min-width: fit-content !important; + height: 20px !important; + + margin-left: 8px; + margin-right: 8px; + margin-top: 8px; + background-color: transparent !important; + } } -#zen-boost-toolbar-wrapper button:hover { - background-color: #ffffff11; - opacity: 0.9; +#zen-boost-font-wrapper { + box-shadow: 0px 4px 8px #00000020; + background-color: #ffffff; + border-radius: 8px; + padding-top: 4px; + padding-bottom: 4px; + + display: flex; + flex-direction: column; + + & .visible-separator { + content: ''; + height: 1px; + width: auto; + + margin-right: 12px; + margin-left: 12px; + + opacity: 0.25; + + border-top: solid 1px #9a9a9a !important; + } + + & button { + filter: none; + color: #9a9a9a !important; + border-radius: 99px !important; + transition: + 0.15s transform cubic-bezier(0.075, 0.82, 0.165, 1), + 0.15s background-color cubic-bezier(0.075, 0.82, 0.165, 1); + + padding: 2px; + + &:hover { + transform: scale(1.1); + background-color: #9a9a9a30; + } + } } -.zen-boost-button-active { - background-color: white !important; - color: black !important; +.mod-button[mode='orange'] { + color: #e3e9e4 !important; + background: linear-gradient(180deg, #ffab35 0%, #ffa01d 100%) border-box; +} +.mod-button[mode='orange-red'] { + color: #e3e9e4 !important; + background: linear-gradient(180deg, #ff723b 0%, #ff5b1b 100%) border-box; +} +.mod-button[mode='red'] { + color: #e3e9e4 !important; + background: linear-gradient(180deg, #ff3e45 0%, #ff121b 100%) border-box; +} +.mod-button[mode='blue'] { + color: #e3e9e4 !important; + background: linear-gradient(180deg, #5d45ff 0%, #4125ff 100%) border-box; } -.zen-boost-button-active:hover { - background-color: white !important; +.zen-boost-color-picker-gradient::after { + content: ''; + position: absolute; + inset: 0; + border-radius: 8px; + margin: 8px; + + z-index: 2; + + background: #fbfbfdf2; + background-image: radial-gradient(#e3e9e4 0.5px, transparent 0); + background-position: -23px -23px; + background-size: 4px 4px; } .zen-boost-color-picker-gradient::before { @@ -151,6 +417,8 @@ inset: 0; border-radius: inherit; + z-index: 1; + background: conic-gradient( rgba(255, 0, 0, 1) 0%, rgba(255, 162, 0, 1) 10%, @@ -166,54 +434,40 @@ ) border-box; - /* cut out the middle of the gradient */ - border: 6px solid transparent; - mask: - linear-gradient(#000 0 0) padding-box, - linear-gradient(#000 0 0); - mask-composite: exclude; - pointer-events: none; } .zen-boost-color-picker-gradient { position: relative; overflow: hidden; - border-radius: calc(var(--panel-border-radius) - 2px); - - filter: drop-shadow(0px 4px 8px #00000055); - - width: auto; - height: 135px; - + border-radius: 16px; + box-shadow: 0px 4px 8px #00000021; + width: 100%; + aspect-ratio: 1 /1; margin: 10px 0px 10px 0px; - + transition: filter 0.5s cubic-bezier(0.075, 0.82, 0.165, 1) !important; min-height: calc(var(--panel-width) - var(--panel-padding) * 2 - 2px); - background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.03)); - background-image: radial-gradient( - light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.1)) 1px, - transparent 0 - ); - background-position: -23px -23px; - background-size: 6px 6px; & .zen-boost-color-picker-circle { position: absolute; - z-index: 1; - opacity: 0; - width: 100px; - height: 100px; - + z-index: 3; transform-origin: center center; transform: translate(-50%, -50%); - outline: solid 1px white; + opacity: 0; + transition: opacity 0.4s ease; + + left: 50%; + top: 50%; + + outline: solid 1px #9a9a9a88; border-radius: 100%; } & .zen-boost-color-picker-dot { + box-shadow: 0px 2px 4px #00000022; position: absolute; - z-index: 2; + z-index: 4; width: 18px; height: 18px; border-radius: 50%; @@ -229,8 +483,8 @@ transform-origin: center center; &:first-of-type { - width: 26px; - height: 26px; + width: 34px; + height: 34px; border-width: 3px; pointer-events: all; transition: transform 0.2s; @@ -245,3 +499,9 @@ } } } + +.zen-boost-color-picker-gradient:hover { + & .zen-boost-color-picker-circle { + opacity: 0.4 !important; + } +} diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index bfc793c13f..395a338b8c 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -50,14 +50,6 @@ var gZenUIManager = { this.onUrlbarSearchModeChanged.bind(this) ); - window.gBrowser.addProgressListener({ - onLocationChange: (aWebProgress) => { - if (aWebProgress.isTopLevel) { - this.onShownTabChange(); - } - }, - }); - gZenMediaController.init(); gZenVerticalTabsManager.init(); @@ -177,8 +169,8 @@ var gZenUIManager = { const height = window.outerHeight; // TODO: This needs to be changed to exact values - const editorWidth = 175; - const editorHeight = 435; + const editorWidth = 185; + const editorHeight = 575; const pad = 20; const animationTarget = 25; @@ -193,15 +185,16 @@ var gZenUIManager = { const editor = window.openDialog( 'chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml', '', - `left=${left},top=${top + animationTarget},chrome,titlebar,alwaysontop` + `left=${left},top=${top + animationTarget},chrome,alwaysontop,resizable=no` ); // Close the editor if the tab is switched window.gBrowser.tabContainer.addEventListener( 'TabSelect', (event) => { - const tab = event.target; - const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + // This seems to be a safer way than doing currentURI.host + const url = new URL(event.target.linkedBrowser.currentURI.spec); + const domain = url.hostname; // Close if domain doesn't match if (domain != editor.domain) { @@ -217,31 +210,8 @@ var gZenUIManager = { this.checkIsTabBoosted(); }); - // Rather inconvenient window animation using moveTo - // editor.addEventListener('load', () => { - // const interval = 16; - // let value = animationTarget; - - // let anim = null; - // anim = setInterval(() => { - // if (window.closed) { clearInterval(anim); return; } - // if (!editor || typeof editor.moveTo !== 'function') { clearInterval(anim); return; } - - // if ((value - 1) <= 0) { - // clearInterval(anim); - // editor.moveTo(left, top); - // return; - // } - - // value = value + (0 - value) * 0.25; - // editor.moveTo(left, top + value); - - // }, interval); - // }); - // Give the domain - const tab = window.gBrowser.selectedTab; - const domain = new URL(tab.linkedBrowser.currentURI.spec).hostname; + const domain = window.gBrowser.selectedTab.linkedBrowser.currentURI.host; editor.domain = domain; // Give the animator @@ -284,8 +254,8 @@ var gZenUIManager = { checkIsTabBoosted() { const button = document.getElementById('zen-site-data-icon-button'); - const tab = window.gBrowser.selectedTab; - const url = new URL(tab.linkedBrowser.currentURI.spec); + // This seems to be a safer way than doing currentURI.host + const url = new URL(window.gBrowser.selectedTab.linkedBrowser.currentURI.spec); const domain = url.hostname; const { gZenBoostsManager } = ChromeUtils.importESModule( @@ -296,10 +266,6 @@ var gZenUIManager = { else button.removeAttribute('boosting'); }, - onShownTabChange() { - this.checkIsTabBoosted(); - }, - onTabClose(event = undefined) { if (!event?.target?._closedInMultiselection) { this.updateTabsToolbar(); diff --git a/src/zen/common/styles/zen-animations.css b/src/zen/common/styles/zen-animations.css index 14167ad830..b05610cf23 100644 --- a/src/zen/common/styles/zen-animations.css +++ b/src/zen/common/styles/zen-animations.css @@ -16,6 +16,18 @@ } } +@keyframes zen-color-options-panel-animation-macos { + 0% { + -moz-window-opacity: 0; + -moz-window-transform: scale(0); + } + + 100% { + -moz-window-opacity: 1; + -moz-window-transform: scale(1); + } +} + @keyframes zen-theme-picker-dot-animation { from { transform: scale(0.8) translate(-50%, -50%); diff --git a/src/zen/common/styles/zen-single-components.css b/src/zen/common/styles/zen-single-components.css index 4c5473872b..ebce75683c 100644 --- a/src/zen/common/styles/zen-single-components.css +++ b/src/zen/common/styles/zen-single-components.css @@ -482,6 +482,8 @@ body > #confetti { padding-top: 8px; margin: 2px 8px 8px 8px; + gap: 10px; + & toolbarbutton { margin: 0; } diff --git a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs index c124f69833..34cab36718 100644 --- a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs +++ b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs @@ -45,6 +45,7 @@ export class nsZenSiteDataPanel { this.#initCopyUrlButton(); this.#initEventListeners(); + this.#initBrowserListeners(); this.#maybeShowFeatureCallout(); } @@ -56,6 +57,7 @@ export class nsZenSiteDataPanel { 'zen-site-data-header-share', 'zen-site-data-header-bookmark', 'zen-site-data-security-info', + 'zen-site-data-boost', 'zen-site-data-actions', 'zen-site-data-new-addon-button', ]; @@ -67,6 +69,25 @@ export class nsZenSiteDataPanel { this.#initContextMenuEventListener(); } + #initBrowserListeners() { + Services.obs.addObserver(this, 'zen-boosts-update'); + this.window.gBrowser.addProgressListener({ + onLocationChange: (aWebProgress) => { + if (aWebProgress.isTopLevel) { + this.window.gZenUIManager.checkIsTabBoosted(); + } + }, + }); + } + + observe(subject, topic) { + switch(topic) { + case 'zen-boosts-update': + this.window.gZenUIManager.checkIsTabBoosted(); + break; + } + } + #initCopyUrlButton() { // This function is a bit out of place, but it's related enough to the panel // that it's easier to do it here than in a separate module. @@ -133,9 +154,8 @@ export class nsZenSiteDataPanel { } #setSiteBoost() { - const list = this.document.getElementById('zen-site-data-settings-list'); - const tab = this.window.gBrowser.selectedTab; - const url = new URL(tab.linkedBrowser.currentURI.spec); + const boostButton = this.document.getElementById('zen-site-data-boost'); + const url = new URL(this.window.gBrowser.selectedTab.linkedBrowser.currentURI.spec); const domain = url.hostname; const uri = this.window.gBrowser.currentURI; @@ -143,70 +163,14 @@ export class nsZenSiteDataPanel { 'resource:///modules/ZenBoostsManager.sys.mjs' ); - if (!gZenBoostsManager.canBoostSite(uri)) return; - - if (gZenBoostsManager.registeredBoostForDomain(domain)) { - let boostData = gZenBoostsManager.loadBoostFromStore(domain); - - list.appendChild( - this.#createGenericPanelItem( - 'zen-boost', - boostData.boostName, - 'Enabled', - 'zen-site-data-edit-boost' - ) - ); - } else { - list.appendChild( - this.#createGenericPanelItem( - 'zen-create-new-boost', - 'Create Boost', - 'For ' + domain, - 'zen-site-data-edit-boost' - ) - ); - } - } - - #createGenericPanelItem(iconClass, title, description, actionId) { - const container = this.document.createXULElement('hbox'); - container.classList.add('permission-popup-permission-item'); - container.id = actionId; - - container.setAttribute('align', 'center'); - container.setAttribute('role', 'group'); - container.setAttribute('state', 'custom'); - container.setAttribute('data-action-id', actionId); - - const img = this.document.createXULElement('toolbarbutton'); - img.classList.add('permission-popup-permission-icon', 'zen-site-data-permission-icon'); - img.setAttribute('closemenu', 'none'); - if (iconClass) { - img.classList.add(iconClass); + if (!gZenBoostsManager.canBoostSite(uri)) { + boostButton.removeAttribute('boosting'); + return; } - const labelContainer = this.document.createXULElement('vbox'); - labelContainer.setAttribute('flex', '1'); - labelContainer.setAttribute('align', 'start'); - labelContainer.classList.add('permission-popup-permission-label-container'); - - const nameLabel = this.document.createXULElement('label'); - nameLabel.setAttribute('flex', '1'); - nameLabel.setAttribute('class', 'permission-popup-permission-label'); - nameLabel.textContent = title || ''; - labelContainer.appendChild(nameLabel); - - const stateLabel = this.document.createXULElement('label'); - stateLabel.setAttribute('class', 'zen-permission-popup-permission-state-label'); - stateLabel.textContent = description || ''; - labelContainer.appendChild(stateLabel); - - container.appendChild(img); - container.appendChild(labelContainer); - - container.addEventListener('click', this); - - return container; + if (gZenBoostsManager.registeredBoostForDomain(domain)) + boostButton.setAttribute('boosting', 'true'); + else boostButton.removeAttribute('boosting'); } #setAddonsOverflow() { @@ -539,6 +503,10 @@ export class nsZenSiteDataPanel { this.window.gIdentityHandler._openPopup(event); break; } + case 'zen-site-data-boost': { + this.window.gZenUIManager.openBoostWindow(); + break; + } case 'zen-site-data-actions': { const button = this.document.getElementById('zen-site-data-actions'); const popup = this.document.getElementById('zenSiteDataActions'); @@ -618,17 +586,6 @@ export class nsZenSiteDataPanel { } } - #onGenericClick(item) { - const id = item.id; - switch (id) { - case 'zen-site-data-edit-boost': { - this.window.gZenUIManager.openBoostWindow(); - this.panel.hidePopup(); - break; - } - } - } - #onClickEvent(event) { const id = event.target.id; switch (id) { @@ -652,8 +609,6 @@ export class nsZenSiteDataPanel { const label = item.querySelector('.permission-popup-permission-label-container'); if (label?._permission) { this.#onPermissionClick(label); - } else { - this.#onGenericClick(item); } break; } diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 8c0ae29ded..8049a0e8ac 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -28,8 +28,7 @@ const globalActionsTemplate = [ icon: 'chrome://browser/skin/zen-icons/selectable/boost.svg', isAvailable: (window) => { const tab = window.gBrowser.selectedTab; - const url = new URL(tab.linkedBrowser.currentURI.spec); - const domain = url.hostname; + const domain = tab.linkedBrowser.currentURI.host; const uri = window.gBrowser.currentURI; const { gZenBoostsManager } = ChromeUtils.importESModule( @@ -49,8 +48,7 @@ const globalActionsTemplate = [ icon: 'chrome://browser/skin/zen-icons/selectable/boost.svg', isAvailable: (window) => { const tab = window.gBrowser.selectedTab; - const url = new URL(tab.linkedBrowser.currentURI.spec); - const domain = url.hostname; + const domain = tab.linkedBrowser.currentURI.host; const uri = window.gBrowser.currentURI; const { gZenBoostsManager } = ChromeUtils.importESModule( From bb2dc093adabcd5ce9a15fa92cd430607908f3e0 Mon Sep 17 00:00:00 2001 From: fen4flo Date: Fri, 7 Nov 2025 21:23:37 +0100 Subject: [PATCH 25/33] fix: remove logs, update select properly --- src/zen/boosts/ZenBoostsEditor.sys.mjs | 46 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 5e8ab781a7..017b37a3d4 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -31,13 +31,13 @@ export class nsZenBoostEditor { this.doc .getElementById('zen-boost-color-contrast') - .addEventListener('onchange', this.onColorOptionChange.bind(this)); + .addEventListener('input', this.onColorOptionChange.bind(this)); this.doc .getElementById('zen-boost-color-brightness') - .addEventListener('onchange', this.onColorOptionChange.bind(this)); + .addEventListener('input', this.onColorOptionChange.bind(this)); this.doc .getElementById('zen-boost-color-saturation') - .addEventListener('onchange', this.onColorOptionChange.bind(this)); + .addEventListener('input', this.onColorOptionChange.bind(this)); this.doc .getElementById('zen-boost-text-case-toggle') @@ -111,7 +111,7 @@ export class nsZenBoostEditor { fontButton.classList.add('subviewbutton'); fontButton.style.fontFamily = fonts[i]; fontButton.innerHTML = 'Aa'; - fontButton.addEventListener('click', this.onFontChange.bind(this)); + fontButton.addEventListener('click', this.onFontButtonClick.bind(this)); fontButtonGroup.appendChild(fontButton); } @@ -123,6 +123,8 @@ export class nsZenBoostEditor { select.innerHTML = font; fontSelect.appendChild(select); } + + fontSelect.addEventListener('change', this.onFontDropdownSelect.bind(this)); } initColorPicker() { @@ -200,13 +202,12 @@ export class nsZenBoostEditor { onBoostCasePressed(event) { const caseButton = this.doc.getElementById('zen-boost-text-case-toggle'); - if(this.currentBoostData.textCaseOverride == 'lower') + if (this.currentBoostData.textCaseOverride == 'lower') this.currentBoostData.textCaseOverride = 'upper'; - else if(this.currentBoostData.textCaseOverride == 'upper') + else if (this.currentBoostData.textCaseOverride == 'upper') this.currentBoostData.textCaseOverride = 'none'; - else - this.currentBoostData.textCaseOverride = 'lower'; - + else this.currentBoostData.textCaseOverride = 'lower'; + this.updateCaseButtonVisuals(); this.updateCurrentBoost(); } @@ -381,11 +382,10 @@ export class nsZenBoostEditor { updateCaseButtonVisuals() { const sizeValue = this.doc.getElementById('zen-boost-text-case-toggle'); - if (this.currentBoostData.textCaseOverride == 'none') - sizeValue.setAttribute('mode', 'none'); - else if (this.currentBoostData.textCaseOverride == 'upper') + if (this.currentBoostData.textCaseOverride == 'none') sizeValue.setAttribute('mode', 'none'); + else if (this.currentBoostData.textCaseOverride == 'upper') sizeValue.setAttribute('mode', 'upper'); - else if (this.currentBoostData.textCaseOverride == 'lower') + else if (this.currentBoostData.textCaseOverride == 'lower') sizeValue.setAttribute('mode', 'lower'); } @@ -416,8 +416,17 @@ export class nsZenBoostEditor { else gradient.classList.remove('zen-boost-panel-disabled'); } - onFontChange(event) { + onFontButtonClick(event) { const font = event?.target?.getAttribute('font-data') ?? ''; + this.onFontChange(font); + } + + onFontDropdownSelect(event) { + const select = event.target; + this.onFontChange(select.value); + } + + onFontChange(font) { if (this.currentBoostData.fontFamily == font) this.currentBoostData.fontFamily = ''; else this.currentBoostData.fontFamily = font; this.updateFontButtonVisuals(); @@ -434,6 +443,15 @@ export class nsZenBoostEditor { fontButton.classList.add('zen-boost-font-button-active'); else fontButton.classList.remove('zen-boost-font-button-active'); } + + const fontSelect = this.doc.getElementById('zen-boost-font-select'); + for (let i = 0; i < fontSelect.options.length; i++) { + const option = fontSelect.options[i]; + if (option.value == this.currentBoostData.fontFamily) { + fontSelect.value = option.value; + break; + } + } } updateCurrentBoost() { From 383520b44e872ed08387d15f6724e2bde49ffb77 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sat, 8 Nov 2025 12:16:52 +0100 Subject: [PATCH 26/33] chore: Small changes, b=no-bug, c=common --- .../base/content/zen-commands.inc.xhtml | 2 - src/browser/themes/shared/zen-icons/icons.css | 10 +++-- src/docshell/base/BrowsingContext-h.patch | 8 ++-- src/layout/base/nsPresContext-cpp.patch | 4 +- src/layout/base/nsPresContext-h.patch | 4 +- src/layout/painting/nsDisplayList-cpp.patch | 4 +- .../common/styles/zen-single-components.css | 2 +- src/zen/common/zen-sets.js | 3 -- src/zen/urlbar/ZenUBGlobalActions.sys.mjs | 40 ------------------- 9 files changed, 18 insertions(+), 59 deletions(-) diff --git a/src/browser/base/content/zen-commands.inc.xhtml b/src/browser/base/content/zen-commands.inc.xhtml index 791c25c9c4..66cfe1c5bc 100644 --- a/src/browser/base/content/zen-commands.inc.xhtml +++ b/src/browser/base/content/zen-commands.inc.xhtml @@ -18,8 +18,6 @@ - - diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 8dc8eb1fd7..04fe58ca8a 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -918,15 +918,19 @@ fill: currentColor; border-radius: 6px; appearance: none; - padding: 6px 6px 6px 6px; + padding: 6px 10px 6px 10px; position: relative; - list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush.svg'); + + & .toolbarbutton-text { + display: none; + } } #zen-site-data-boost[boosting] { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush-fill.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/paintbrush-fill.svg'); } #zen-site-data-boost[boosting]::after { diff --git a/src/docshell/base/BrowsingContext-h.patch b/src/docshell/base/BrowsingContext-h.patch index 77cdbe85b3..8f61b93180 100644 --- a/src/docshell/base/BrowsingContext-h.patch +++ b/src/docshell/base/BrowsingContext-h.patch @@ -1,8 +1,8 @@ diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h -index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..68bcf7c87e41d08a71b000e4a7e4304814928c75 100644 +index 2d3a14d59922045197e474f5d097d7e16ce6ad8c..2e86f53edcd1288d3ca2e0292409aec8ee5926da 100644 --- a/docshell/base/BrowsingContext.h +++ b/docshell/base/BrowsingContext.h -@@ -256,6 +256,7 @@ struct EmbedderColorSchemes { +@@ -258,6 +258,7 @@ struct EmbedderColorSchemes { FIELD(IsInBFCache, bool) \ FIELD(HasRestoreData, bool) \ FIELD(SessionStoreEpoch, uint32_t) \ @@ -10,7 +10,7 @@ index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..68bcf7c87e41d08a71b000e4a7e43048 /* Whether we can execute scripts in this BrowsingContext. Has no effect \ * unless scripts are also allowed in the parent WindowContext. */ \ FIELD(AllowJavascript, bool) \ -@@ -650,6 +651,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -652,6 +653,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { bool FullscreenAllowed() const; @@ -18,7 +18,7 @@ index 331d7a5a0f0956ff35c8a5a703b1e089b19bd7fe..68bcf7c87e41d08a71b000e4a7e43048 float FullZoom() const { return GetFullZoom(); } float TextZoom() const { return GetTextZoom(); } -@@ -1167,6 +1169,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { +@@ -1175,6 +1177,7 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache { } void DidSet(FieldIndex, uint32_t aOldValue); diff --git a/src/layout/base/nsPresContext-cpp.patch b/src/layout/base/nsPresContext-cpp.patch index e58bdea530..26768bf1d5 100644 --- a/src/layout/base/nsPresContext-cpp.patch +++ b/src/layout/base/nsPresContext-cpp.patch @@ -1,5 +1,5 @@ diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp -index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..faf802c4ddef7d9d670242dec5b580c8e9b42111 100644 +index edae3bacba3a029cfb4038e1de4b36b366488385..40b7b7b66edeeb99a5208d989077e6a39162c7a4 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -20,6 +20,7 @@ @@ -10,7 +10,7 @@ index c1cc9a6505ffb3f0019ce007c8d17366364bdd86..faf802c4ddef7d9d670242dec5b580c8 #include "mozilla/AnimationEventDispatcher.h" #include "mozilla/ContentBlockingAllowList.h" #include "mozilla/CycleCollectedJSContext.h" -@@ -915,6 +916,13 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { +@@ -917,6 +918,13 @@ void nsPresContext::RecomputeBrowsingContextDependentData() { } auto* top = browsingContext->Top(); diff --git a/src/layout/base/nsPresContext-h.patch b/src/layout/base/nsPresContext-h.patch index 88202ff07b..51ec6cbf66 100644 --- a/src/layout/base/nsPresContext-h.patch +++ b/src/layout/base/nsPresContext-h.patch @@ -1,8 +1,8 @@ diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h -index e1186f0b9f25f5c78f2cb3dc4d2a607c1ede04a9..03f338d61c0a550407025b6736f80829d6f574de 100644 +index 659c2a237280794c19de2fbb54b3b3070590cc30..b47ef9d53ddcdd904c1383fd136cf13a2224f347 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h -@@ -560,6 +560,7 @@ class nsPresContext : public nsISupports, +@@ -566,6 +566,7 @@ class nsPresContext : public nsISupports, void UpdateForcedColors(bool aNotify = true); public: diff --git a/src/layout/painting/nsDisplayList-cpp.patch b/src/layout/painting/nsDisplayList-cpp.patch index 9ca16bb5d3..33a5d40eb5 100644 --- a/src/layout/painting/nsDisplayList-cpp.patch +++ b/src/layout/painting/nsDisplayList-cpp.patch @@ -1,5 +1,5 @@ diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp -index 6c5b38eb3cdb08a8afd670705c75c052b85b6dd9..823df9ef5245dcffcbfaba1fafc6b15ece174676 100644 +index 43532357fc9ff86813af8b7bd2ec267b2c23b9c1..da905de5dfcea1c42271dd7c7d0bde52e8704cd7 100644 --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -85,6 +85,7 @@ @@ -23,7 +23,7 @@ index 6c5b38eb3cdb08a8afd670705c75c052b85b6dd9..823df9ef5245dcffcbfaba1fafc6b15e ScrollContainerFrame* sf = state->mPresShell->GetRootScrollContainerFrame(); if (sf && IsInSubdocument()) { // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure -@@ -1221,6 +1228,15 @@ void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame, +@@ -1231,6 +1238,15 @@ void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame, ResetMarkedFramesForDisplayList(aReferenceFrame); mPresShellStates.RemoveLastElement(); diff --git a/src/zen/common/styles/zen-single-components.css b/src/zen/common/styles/zen-single-components.css index b1da974a82..8d9a8269d2 100644 --- a/src/zen/common/styles/zen-single-components.css +++ b/src/zen/common/styles/zen-single-components.css @@ -487,7 +487,7 @@ body > #confetti { padding-top: 8px; margin: 2px 8px 8px 8px; - gap: 10px; + gap: 8px; & toolbarbutton { margin: 0; diff --git a/src/zen/common/zen-sets.js b/src/zen/common/zen-sets.js index 892a66ca82..b7b6c00ad6 100644 --- a/src/zen/common/zen-sets.js +++ b/src/zen/common/zen-sets.js @@ -23,9 +23,6 @@ document.addEventListener( case 'cmd_zenWorkspaceForward': gZenWorkspaces.changeWorkspaceShortcut(); break; - case 'cmd_zenOpenBoostEditor': - gZenUIManager.openBoostWindow(); - break; case 'cmd_zenWorkspaceBackward': gZenWorkspaces.changeWorkspaceShortcut(-1); break; diff --git a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs index 8049a0e8ac..5f09607d41 100644 --- a/src/zen/urlbar/ZenUBGlobalActions.sys.mjs +++ b/src/zen/urlbar/ZenUBGlobalActions.sys.mjs @@ -22,46 +22,6 @@ const globalActionsTemplate = [ command: 'cmd_zenNewEmptySplit', icon: 'chrome://browser/skin/zen-icons/split.svg', }, - { - label: 'Create New Boost', - command: 'cmd_zenOpenBoostEditor', - icon: 'chrome://browser/skin/zen-icons/selectable/boost.svg', - isAvailable: (window) => { - const tab = window.gBrowser.selectedTab; - const domain = tab.linkedBrowser.currentURI.host; - const uri = window.gBrowser.currentURI; - - const { gZenBoostsManager } = ChromeUtils.importESModule( - 'resource:///modules/ZenBoostsManager.sys.mjs' - ); - - return ( - !tab.hasAttribute('zen-empty-tab') && - !gZenBoostsManager.registeredBoostForDomain(domain) && - gZenBoostsManager.canBoostSite(uri) - ); - }, - }, - { - label: 'Edit Boost', - command: 'cmd_zenOpenBoostEditor', - icon: 'chrome://browser/skin/zen-icons/selectable/boost.svg', - isAvailable: (window) => { - const tab = window.gBrowser.selectedTab; - const domain = tab.linkedBrowser.currentURI.host; - const uri = window.gBrowser.currentURI; - - const { gZenBoostsManager } = ChromeUtils.importESModule( - 'resource:///modules/ZenBoostsManager.sys.mjs' - ); - - return ( - !tab.hasAttribute('zen-empty-tab') && - gZenBoostsManager.registeredBoostForDomain(domain) && - gZenBoostsManager.canBoostSite(uri) - ); - }, - }, { label: 'New Folder', command: 'cmd_zenOpenFolderCreation', From 54b45ebded6b08ffdc2d1164e07619248ba27dd4 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sat, 8 Nov 2025 13:27:37 +0100 Subject: [PATCH 27/33] feat: Convert the window into a proper XUL doc and implement scale, b=no-bug, c=no-component --- .../base/content/zen-assets.jar.inc.mn | 2 +- src/browser/themes/shared/zen-icons/icons.css | 28 ++++--- src/zen/boosts/ZenBoostsEditor.sys.mjs | 35 ++++---- src/zen/boosts/ZenBoostsManager.sys.mjs | 9 +- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 7 +- src/zen/boosts/zen-advanced-color-options.css | 5 +- src/zen/boosts/zen-boost-editor.xhtml | 83 +++++++++---------- src/zen/boosts/zen-boosts.css | 22 +++-- src/zen/urlbar/ZenSiteDataPanel.sys.mjs | 2 +- 9 files changed, 102 insertions(+), 91 deletions(-) diff --git a/src/browser/base/content/zen-assets.jar.inc.mn b/src/browser/base/content/zen-assets.jar.inc.mn index e294bfd9b3..a6b80e5151 100644 --- a/src/browser/base/content/zen-assets.jar.inc.mn +++ b/src/browser/base/content/zen-assets.jar.inc.mn @@ -76,7 +76,7 @@ content/browser/zen-styles/zen-advanced-color-options.css (../../zen/boosts/zen-advanced-color-options.css) # Windows - content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.xhtml) +* content/browser/zen-components/windows/zen-boost-editor.xhtml (../../zen/boosts/zen-boost-editor.xhtml) # Images content/browser/zen-images/brand-header.svg (../../zen/images/brand-header.svg) diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 04fe58ca8a..e2631d8627 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -527,22 +527,26 @@ list-style-image: url('permissions-fill.svg'); } &[boosting] image { + color: var(--color-accent-primary); list-style-image: url('permissions-fill.svg'); } position: relative; } -#zen-site-data-icon-button[boosting]::after { - content: ''; - position: absolute; - width: 100%; - height: 100%; - opacity: 1; - background: url('chrome://browser/content/zen-images/boost-indicator.svg') no-repeat; - transform: translateX(-20%); - z-index: 0; - pointer-events: none; +@media not (prefers-reduced-motion: reduce) { + #zen-site-data-icon-button[boosting]::after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + opacity: 1; + color: var(--color-accent-primary); + background: url('chrome://browser/content/zen-images/boost-indicator.svg') no-repeat; + transform: translateX(-20%); + z-index: 0; + pointer-events: none; + } } .geo-icon { @@ -1046,5 +1050,5 @@ } #zen-boost-shuffle { -list-style-image: url('chrome://browser/skin/zen-icons/selectable/arrow-rotate-anticlockwise.svg') !important; -} \ No newline at end of file + list-style-image: url('chrome://browser/skin/zen-icons/selectable/arrow-rotate-anticlockwise.svg') !important; +} diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 017b37a3d4..acac32bffc 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -181,19 +181,18 @@ export class nsZenBoostEditor { onBoostSizePressed(event) { const sizeValue = this.doc.getElementById('zen-boost-size-value'); - if (this.currentBoostData.siteSizeOverride >= 150) this.currentBoostData.siteSizeOverride = 90; - else if (this.currentBoostData.siteSizeOverride >= 125) - this.currentBoostData.siteSizeOverride = 150; - else if (this.currentBoostData.siteSizeOverride >= 110) - this.currentBoostData.siteSizeOverride = 125; - else if (this.currentBoostData.siteSizeOverride >= 100) - this.currentBoostData.siteSizeOverride = 110; - else if (this.currentBoostData.siteSizeOverride >= 90) - this.currentBoostData.siteSizeOverride = -1; - else this.currentBoostData.siteSizeOverride = 110; - - if (this.currentBoostData.siteSizeOverride == -1) sizeValue.innerHTML = ``; - else sizeValue.innerHTML = `${this.currentBoostData.siteSizeOverride}%`; + if (this.currentBoostData.siteSizeOverride >= 1.5) this.currentBoostData.siteSizeOverride = 0.9; + else if (this.currentBoostData.siteSizeOverride >= 1.25) + this.currentBoostData.siteSizeOverride = 1.5; + else if (this.currentBoostData.siteSizeOverride >= 1.1) + this.currentBoostData.siteSizeOverride = 1.25; + else if (this.currentBoostData.siteSizeOverride >= 1) + this.currentBoostData.siteSizeOverride = 1.1; + else if (this.currentBoostData.siteSizeOverride >= 0.9) + this.currentBoostData.siteSizeOverride = 1; + else this.currentBoostData.siteSizeOverride = 1.1; + + sizeValue.innerHTML = `${Math.round(this.currentBoostData.siteSizeOverride * 100)}%`; this.updateSizeButtonVisuals(); this.updateCurrentBoost(); @@ -369,13 +368,13 @@ export class nsZenBoostEditor { updateSizeButtonVisuals() { const sizeValue = this.doc.getElementById('zen-boost-size'); - if (this.currentBoostData.siteSizeOverride >= 150) sizeValue.setAttribute('mode', 'red'); - else if (this.currentBoostData.siteSizeOverride >= 125) + if (this.currentBoostData.siteSizeOverride >= 1.5) sizeValue.setAttribute('mode', 'red'); + else if (this.currentBoostData.siteSizeOverride >= 1.25) sizeValue.setAttribute('mode', 'orange-red'); - else if (this.currentBoostData.siteSizeOverride >= 110) + else if (this.currentBoostData.siteSizeOverride >= 1.1) sizeValue.setAttribute('mode', 'orange'); - else if (this.currentBoostData.siteSizeOverride >= 100) sizeValue.setAttribute('mode', 'none'); - else if (this.currentBoostData.siteSizeOverride >= 90) sizeValue.setAttribute('mode', 'blue'); + else if (this.currentBoostData.siteSizeOverride >= 1) sizeValue.setAttribute('mode', 'none'); + else if (this.currentBoostData.siteSizeOverride >= 0.9) sizeValue.setAttribute('mode', 'blue'); else sizeValue.setAttribute('mode', 'none'); } diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 5cb58bd28c..f7df446829 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -46,10 +46,10 @@ export class nsZenBoostsManager { autoTheme: false, // Default to 100% scale - siteSizeOverride: 100, + siteSizeOverride: 1, textCaseOverride: 'none', - changeWasMade: false + changeWasMade: false, }; if (this.registeredBoosts.has(domain)) { @@ -71,9 +71,8 @@ export class nsZenBoostsManager { } // Save all boosts to the profile folder - saveBoostToStore(boostData) { - if (boostData != null) - this.registeredBoosts.set(boostData.domain, boostData); + saveBoostToStore(boostData) { + if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); this.#writeToDisk(this.registeredBoosts); this.notify(); } diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index d55f9f9c67..9d83435d06 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -109,11 +109,16 @@ export class ZenBoostsChild extends JSWindowActorChild { prefersColorSchemeOverride = boost.topWindowIsDarkMode ? 'light' : 'dark'; } browsingContext.prefersColorSchemeOverride = prefersColorSchemeOverride; + // Has to be a finite value for zoom to work correctly + browsingContext.fullZoom = boost.siteSizeOverride; if (boost.enableColorBoost) { const rgbColor = this.#hslToRgb( boost.dotAngleDeg / 360, boost.dotDistance * (boost.saturation / 255).toFixed(4) /* already is [0, 1] */, - 0.2 + boost.dotDistance * 0.8 * (boost.brightness / 255).toFixed(4) /* lightness range from [0.2, 0.8] */ + 0.2 + + boost.dotDistance * + 0.8 * + (boost.brightness / 255).toFixed(4) /* lightness range from [0.2, 0.8] */ ); const nsColor = this.#rgbToNSColor(rgbColor, boost.contrast); browsingContext.zenBoostsData = nsColor; diff --git a/src/zen/boosts/zen-advanced-color-options.css b/src/zen/boosts/zen-advanced-color-options.css index 8a050af8d9..aa89016bf7 100644 --- a/src/zen/boosts/zen-advanced-color-options.css +++ b/src/zen/boosts/zen-advanced-color-options.css @@ -30,7 +30,8 @@ @media (-moz-platform: macos) { &[animate='open'] { - animation: zen-color-options-panel-animation-macos 0.35s cubic-bezier(.29,1.37,.87,1) forwards !important; + animation: zen-color-options-panel-animation-macos 0.35s cubic-bezier(0.29, 1.37, 0.87, 1) + forwards !important; } } } @@ -44,7 +45,7 @@ } #zen-boost-advanced-color-options-panel input::-moz-range-thumb:active { - background-color: #cbcad0; + background-color: #cbcad0; } #zen-boost-advanced-color-options-container { diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.xhtml index e1393777ea..25110039d1 100644 --- a/src/zen/boosts/zen-boost-editor.xhtml +++ b/src/zen/boosts/zen-boost-editor.xhtml @@ -1,27 +1,35 @@ +#filter substitution - - - - - - - - - - - - +# -*- Mode: HTML -*- +# +# 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/. - + customtitlebar="true" + sizemode="normal" + scrolling="false" + macanimationtype="document" + data-l10n-sync="true"> + + + + + + + + + + + - + + -
+ - -
+
-
- - - - - - -
@@ -75,26 +74,17 @@
- - - - - - - - -
@@ -111,4 +101,5 @@ - + + diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 1e20dbd1cf..7edccc7b65 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -6,13 +6,20 @@ #zenSettingsWindow { animation: 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) zen-boost-window-in; - border-radius: 14px !important; +} + +body { + overflow: clip; + display: flex; + margin: 0; + width: 100%; } #zen-boost-editor-root { border-radius: 14px !important; background-color: #fcfcfe; width: 185px; + user-select: none; & button { border-radius: 10px !important; @@ -149,6 +156,11 @@ #zen-boost-filter-wrapper { padding: 20px; + gap: 14px; + -moz-window-dragging: drag; + & > * { + -moz-window-dragging: no-drag; + } } .big-button { @@ -204,19 +216,19 @@ .mod-button { height: 38px !important; - transition: - 0.35s opacity cubic-bezier(0.075, 0.82, 0.165, 1), + transition: + 0.35s opacity cubic-bezier(0.075, 0.82, 0.165, 1), 0.35s filter cubic-bezier(0.075, 0.82, 0.165, 1) !important; background-color: #ebebed; &:hover { opacity: 0.9; } - + &:active { scale: 1 !important; transform: none !important; - + opacity: 0.9; filter: brightness(0.9); } diff --git a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs index 3a2acab08e..5789990459 100644 --- a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs +++ b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs @@ -82,7 +82,7 @@ export class nsZenSiteDataPanel { } observe(subject, topic) { - switch(topic) { + switch (topic) { case 'zen-boosts-update': this.window.gZenUIManager.checkIsTabBoosted(); break; From e1c6e28fd004e8421be3889b7a3ef75c03ec92a0 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sat, 8 Nov 2025 17:17:29 +0100 Subject: [PATCH 28/33] feat: Finish migration to xhtml window, b=no-bug, c=common --- src/browser/themes/shared/zen-icons/icons.css | 4 +++ src/zen/boosts/ZenBoostsEditor.sys.mjs | 23 +++++++++------- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 3 ++- src/zen/boosts/zen-boost-editor.xhtml | 26 ++++++++++--------- src/zen/boosts/zen-boosts.css | 6 +++-- src/zen/common/ZenUIManager.mjs | 8 +++--- src/zen/urlbar/ZenSiteDataPanel.sys.mjs | 9 +++---- 7 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index e2631d8627..29fc3af8ec 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -931,6 +931,10 @@ & .toolbarbutton-text { display: none; } + + @media not (-moz-pref('zen.boosts.enabled', true)) { + display: none; + } } #zen-site-data-boost[boosting] { diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index acac32bffc..89af66af67 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -57,14 +57,21 @@ export class nsZenBoostEditor { this.doc .getElementById('zen-boost-controls') .addEventListener('click', (event) => this.openAdvancedColorOptions(event)); - this.doc - .getElementById('zen-boost-close') - .addEventListener('click', this.onClosePressed.bind(this)); this.doc .getElementById('zen-boost-name') .addEventListener('input', (e) => (this.currentBoostData.boostName = e.target.value)); + this.doc + .getElementById('zen-boost-close') + .addEventListener('click', this.onClosePressed.bind(this)); + + this.doc.addEventListener('keydown', (event) => { + if (event.key === 'Escape' || (event.key === 'w' && (event.ctrlKey || event.metaKey))) { + this.onClosePressed(); + } + }); + this.initialized = true; } @@ -178,7 +185,7 @@ export class nsZenBoostEditor { this.wasDragging = false; } - onBoostSizePressed(event) { + onBoostSizePressed() { const sizeValue = this.doc.getElementById('zen-boost-size-value'); if (this.currentBoostData.siteSizeOverride >= 1.5) this.currentBoostData.siteSizeOverride = 0.9; @@ -198,9 +205,7 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } - onBoostCasePressed(event) { - const caseButton = this.doc.getElementById('zen-boost-text-case-toggle'); - + onBoostCasePressed() { if (this.currentBoostData.textCaseOverride == 'lower') this.currentBoostData.textCaseOverride = 'upper'; else if (this.currentBoostData.textCaseOverride == 'upper') @@ -211,7 +216,7 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } - onColorOptionChange(event) { + onColorOptionChange() { this.currentBoostData.contrast = this.doc.getElementById('zen-boost-color-contrast').value; this.currentBoostData.brightness = this.doc.getElementById('zen-boost-color-brightness').value; this.currentBoostData.saturation = this.doc.getElementById('zen-boost-color-saturation').value; @@ -221,7 +226,7 @@ export class nsZenBoostEditor { openAdvancedColorOptions(event) { const panel = this.doc.getElementById('zen-boost-advanced-color-options-panel'); - panel.openPopup(event.target, 'bottomcenter topcenter', 0, 5); + panel.openPopup(event.target, 'bottomcenter topcenter', 0, 2); } resetDotPosition() { diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 9d83435d06..ce2376b8b8 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -110,7 +110,8 @@ export class ZenBoostsChild extends JSWindowActorChild { } browsingContext.prefersColorSchemeOverride = prefersColorSchemeOverride; // Has to be a finite value for zoom to work correctly - browsingContext.fullZoom = boost.siteSizeOverride; + // TODO: Figure out something better for site size override + // browsingContext.fullZoom = boost.siteSizeOverride; if (boost.enableColorBoost) { const rgbColor = this.#hslToRgb( boost.dotAngleDeg / 360, diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.xhtml index 25110039d1..41d24d970d 100644 --- a/src/zen/boosts/zen-boost-editor.xhtml +++ b/src/zen/boosts/zen-boost-editor.xhtml @@ -89,17 +89,19 @@ - -
-

Contrast

- - -

Brightness

- - -

Original Saturation

- -
-
+ + +
+

Contrast

+ + +

Brightness

+ + +

Original Saturation

+ +
+
+
diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index 7edccc7b65..db8e658516 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -6,6 +6,10 @@ #zenSettingsWindow { animation: 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) zen-boost-window-in; + max-width: 185px; + min-width: 185px; + min-height: 528px; + max-height: 528px; } body { @@ -16,9 +20,7 @@ body { } #zen-boost-editor-root { - border-radius: 14px !important; background-color: #fcfcfe; - width: 185px; user-select: none; & button { diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index c6efe50cdb..60669eb7a7 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -182,10 +182,12 @@ var gZenUIManager = { left = screenX + width - (editorWidth + pad); } - const editor = window.openDialog( + const editor = Services.ww.openWindow( + window, 'chrome://browser/content/zen-components/windows/zen-boost-editor.xhtml', - '', - `left=${left},top=${top + animationTarget},chrome,alwaysontop,resizable=no` + null, + `left=${left},top=${top + animationTarget},chrome,alwaysontop,resizable=no`, + null ); // Close the editor if the tab is switched diff --git a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs index 5789990459..76b810d172 100644 --- a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs +++ b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs @@ -8,6 +8,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { FeatureCallout: 'resource:///modules/asrouter/FeatureCallout.sys.mjs', + gZenBoostsManager: 'resource:///modules/ZenBoostsManager.sys.mjs', }); export class nsZenSiteDataPanel { @@ -160,16 +161,12 @@ export class nsZenSiteDataPanel { const domain = url.hostname; const uri = this.window.gBrowser.currentURI; - const { gZenBoostsManager } = ChromeUtils.importESModule( - 'resource:///modules/ZenBoostsManager.sys.mjs' - ); - - if (!gZenBoostsManager.canBoostSite(uri)) { + if (!lazy.gZenBoostsManager.canBoostSite(uri)) { boostButton.removeAttribute('boosting'); return; } - if (gZenBoostsManager.registeredBoostForDomain(domain)) + if (lazy.gZenBoostsManager.registeredBoostForDomain(domain)) boostButton.setAttribute('boosting', 'true'); else boostButton.removeAttribute('boosting'); } From 27e35016cc22b816f8b467adc35da0e32d9e0ab7 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sun, 9 Nov 2025 14:22:26 +0100 Subject: [PATCH 29/33] chore: Cleanups, b=no-bug, c=common --- src/browser/themes/shared/zen-icons/icons.css | 28 ++-- src/zen/boosts/ZenBoostStyles.sys.mjs | 7 + src/zen/boosts/ZenBoostsEditor.sys.mjs | 153 +++++++++++++++++- src/zen/boosts/ZenBoostsManager.sys.mjs | 84 ++++++++-- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 13 ++ src/zen/boosts/actors/ZenBoostsParent.sys.mjs | 19 +++ src/zen/boosts/moz.build | 1 + src/zen/boosts/nsZenBoostsBackend.cpp | 24 ++- src/zen/boosts/nsZenBoostsBackend.h | 18 ++- src/zen/common/ZenUIManager.mjs | 23 --- src/zen/urlbar/ZenSiteDataPanel.sys.mjs | 32 +++- 11 files changed, 340 insertions(+), 62 deletions(-) create mode 100644 src/zen/boosts/ZenBoostStyles.sys.mjs diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index 29fc3af8ec..a3a486649d 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -16,10 +16,6 @@ list-style-image: url('account-private.svg') !important; border-radius: 100% !important; } -/* -.down-arrow-icon { - list-style-image: url('chrome://browser/skin/zen-icons/arrow-down.svg') !important; -} */ #back-button { list-style-image: url('back.svg') !important; @@ -42,10 +38,12 @@ list-style-image: url('close.svg') !important; } +#PanelUI-zen-emojis-picker-none, #zen-emojis-picker-none { list-style-image: url('trash.svg'); } +#PanelUI-zen-gradient-generator-color-remove, #zen-gradient-generator-color-remove { list-style-image: url('unpin.svg') !important; } @@ -1014,45 +1012,45 @@ } #zen-boost-text-case-toggle { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-title-case.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-title-case.svg'); } #zen-boost-text-case-toggle[mode='upper'] { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-uppercase.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-uppercase.svg'); } #zen-boost-text-case-toggle[mode='lower'] { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-lowercase.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-lowercase.svg'); } #zen-boost-code { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/brackets-curly.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/brackets-curly.svg'); } #zen-boost-zap { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/bolt.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/bolt.svg'); } #zen-boost-controls { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/sliders.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/sliders.svg'); } #zen-boost-invert { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/lightbulb.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/lightbulb.svg'); } #zen-boost-disable { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/block.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/block.svg'); } #zen-boost-close { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/close-filled-round.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/close-filled-round.svg'); } #zen-boost-magic-theme { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/square-wand-sparkle.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/square-wand-sparkle.svg'); } #zen-boost-shuffle { - list-style-image: url('chrome://browser/skin/zen-icons/selectable/arrow-rotate-anticlockwise.svg') !important; + list-style-image: url('chrome://browser/skin/zen-icons/selectable/arrow-rotate-anticlockwise.svg'); } diff --git a/src/zen/boosts/ZenBoostStyles.sys.mjs b/src/zen/boosts/ZenBoostStyles.sys.mjs new file mode 100644 index 0000000000..05d5f84d84 --- /dev/null +++ b/src/zen/boosts/ZenBoostStyles.sys.mjs @@ -0,0 +1,7 @@ +/* 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/. */ + +export class nsZenBoostStyles { + #stylesCache = new Map(); +} diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 89af66af67..a63f3ff6a4 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -1,6 +1,6 @@ -// 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/. +/* 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/. */ import { gZenBoostsManager } from './ZenBoostsManager.sys.mjs'; @@ -8,6 +8,12 @@ export class nsZenBoostEditor { doc = null; window = null; + /** + * Creates a new boost editor instance for the specified domain. + * @param {Document} doc - The document object for the editor window. + * @param {string} domain - The domain for which to edit the boost. + * @param {Window} window - The window object for the editor. + */ constructor(doc, domain, window) { this.doc = doc; this.window = window; @@ -26,6 +32,9 @@ export class nsZenBoostEditor { this.loadBoost(domain); } + /** + * Initializes the boost editor by setting up event listeners for all UI controls. + */ init() { this.window.addEventListener('unload', () => this.handleClose(), { once: true }); @@ -75,21 +84,38 @@ export class nsZenBoostEditor { this.initialized = true; } + /** + * Uninitializes the boost editor by cleaning up event listeners and observers. + */ uninit() { this.uninitColorPicker(); Services.obs.removeObserver(this, 'zen-boosts-kill-editor'); } + /** + * Kills other editor instances by sending a notification to close them. + * This ensures only one editor instance is open at a time. + */ killOtherEditorInstances() { Services.obs.notifyObservers(null, 'zen-boosts-kill-editor'); } + /** + * Observer callback that handles notifications from the observer service. + * Closes the editor window when a 'zen-boosts-kill-editor' notification is received. + * @param {Object} subject - The subject of the notification. + * @param {string} topic - The topic of the notification. + */ observe(subject, topic) { if (topic === 'zen-boosts-kill-editor') { this.window.close(); } } + /** + * Registers an event listener to close the editor when the active tab changes + * to a different domain than the one being edited. + */ registerTabChangedEvent() { this.window.gBrowser.tabContainer.addEventListener('TabSelect', (event) => { const tab = event.target; @@ -99,6 +125,10 @@ export class nsZenBoostEditor { }); } + /** + * Initializes the font selection UI by creating font buttons and dropdown options + * for the available font families. + */ initFonts() { const fonts = [ 'Arial, sans-serif', @@ -134,6 +164,10 @@ export class nsZenBoostEditor { fontSelect.addEventListener('change', this.onFontDropdownSelect.bind(this)); } + /** + * Initializes the color picker by setting up mouse event listeners for + * interactive color selection on the gradient picker. + */ initColorPicker() { const themePicker = this.doc.querySelector('.zen-boost-color-picker-gradient'); this._onMouseMove = this.onMouseMove.bind(this); @@ -146,6 +180,9 @@ export class nsZenBoostEditor { themePicker.addEventListener('click', this._onThemePickerClick); } + /** + * Uninitializes the color picker by removing all mouse event listeners. + */ uninitColorPicker() { const themePicker = this.doc.querySelector('.zen-boost-color-picker-gradient'); this.doc.removeEventListener('mousemove', this._onMouseMove); @@ -158,6 +195,10 @@ export class nsZenBoostEditor { this._onMouseDown = null; } + /** + * Handles mouse move events to update the color picker dot position while dragging. + * @param {MouseEvent} event - The mouse move event. + */ onMouseMove(event) { if (this.isMouseDown) { this.wasDragging = true; @@ -168,6 +209,10 @@ export class nsZenBoostEditor { } } + /** + * Handles mouse down events to initiate color picker dragging. + * @param {MouseEvent} event - The mouse down event. + */ onMouseDown(event) { if (event.button === 2) { return; @@ -176,6 +221,10 @@ export class nsZenBoostEditor { this.isMouseDown = true; } + /** + * Handles mouse up events to end color picker dragging. + * @param {MouseEvent} event - The mouse up event. + */ onMouseUp(event) { if (event.button === 2) { return; @@ -185,6 +234,10 @@ export class nsZenBoostEditor { this.wasDragging = false; } + /** + * Handles the boost size button press, cycling through size override values + * (0.9, 1.0, 1.1, 1.25, 1.5) and updating the UI accordingly. + */ onBoostSizePressed() { const sizeValue = this.doc.getElementById('zen-boost-size-value'); @@ -205,6 +258,10 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Handles the text case toggle button press, cycling through case override options + * (none, lower, upper) and updating the UI accordingly. + */ onBoostCasePressed() { if (this.currentBoostData.textCaseOverride == 'lower') this.currentBoostData.textCaseOverride = 'upper'; @@ -216,6 +273,10 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Handles changes to color option sliders (contrast, brightness, saturation) + * and updates the current boost data accordingly. + */ onColorOptionChange() { this.currentBoostData.contrast = this.doc.getElementById('zen-boost-color-contrast').value; this.currentBoostData.brightness = this.doc.getElementById('zen-boost-color-brightness').value; @@ -224,15 +285,27 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Opens the advanced color options popup panel. + * @param {Event} event - The click event that triggered this action. + */ openAdvancedColorOptions(event) { const panel = this.doc.getElementById('zen-boost-advanced-color-options-panel'); panel.openPopup(event.target, 'bottomcenter topcenter', 0, 2); } + /** + * Resets the color picker dot to the center position (default state). + */ resetDotPosition() { this.setDotPos(null, null); } + /** + * Handles clicks on the theme picker gradient or magic theme button. + * Updates the dot position or toggles auto-theme mode based on the click target. + * @param {MouseEvent} event - The click event. + */ onThemePickerClick(event) { event.preventDefault(); @@ -246,7 +319,13 @@ export class nsZenBoostEditor { this.wasDragging = false; } - // Sets the position of the dot + /** + * Sets the position of the color picker dot on the gradient and updates + * the boost data with the corresponding angle and distance values. + * @param {number|null} pixelX - The X coordinate in pixels, or null to center the dot. + * @param {number|null} pixelY - The Y coordinate in pixels, or null to center the dot. + * @param {boolean} animate - Whether to animate the dot movement (currently not implemented). + */ setDotPos(pixelX, pixelY, animate = true) { const gradient = this.doc.querySelector('.zen-boost-color-picker-gradient'); const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); @@ -333,6 +412,10 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Updates the visual appearance of the color picker dot and circle + * based on the current boost data's angle and distance values. + */ updateDot() { const dot = this.doc.querySelector('.zen-boost-color-picker-dot'); dot.style.setProperty( @@ -350,7 +433,10 @@ export class nsZenBoostEditor { circle.style.height = `${this.currentBoostData.dotDistance * radius * 2}px`; } - // This toggles the color changes + /** + * Toggles the color boost enable/disable state. + * @param {boolean} userAction - Whether this was triggered by a user action (default: true). + */ onToggleDisable(userAction = true) { this.currentBoostData.enableColorBoost = !this.currentBoostData.enableColorBoost; @@ -360,6 +446,11 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Toggles the smart invert feature, which automatically inverts colors + * based on the window's color scheme. + * @param {boolean} userAction - Whether this was triggered by a user action (default: true). + */ onToggleInvert(userAction = true) { this.currentBoostData.enableColorBoost = true; this.currentBoostData.smartInvert = !this.currentBoostData.smartInvert; @@ -370,6 +461,10 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Updates the visual state of the size button based on the current + * site size override value, setting appropriate color modes. + */ updateSizeButtonVisuals() { const sizeValue = this.doc.getElementById('zen-boost-size'); @@ -383,6 +478,10 @@ export class nsZenBoostEditor { else sizeValue.setAttribute('mode', 'none'); } + /** + * Updates the visual state of the text case toggle button based on the current + * text case override value (none, upper, or lower). + */ updateCaseButtonVisuals() { const sizeValue = this.doc.getElementById('zen-boost-text-case-toggle'); @@ -393,6 +492,10 @@ export class nsZenBoostEditor { sizeValue.setAttribute('mode', 'lower'); } + /** + * Updates the visual state of all toggle buttons (invert, disable, auto-theme) + * and applies grayscale effect to the gradient when color boosting is disabled. + */ updateButtonToggleVisuals() { const invertButton = this.doc.getElementById('zen-boost-invert'); const disableButton = this.doc.getElementById('zen-boost-disable'); @@ -420,16 +523,29 @@ export class nsZenBoostEditor { else gradient.classList.remove('zen-boost-panel-disabled'); } + /** + * Handles font button clicks to change the selected font family. + * @param {Event} event - The click event from a font button. + */ onFontButtonClick(event) { const font = event?.target?.getAttribute('font-data') ?? ''; this.onFontChange(font); } + /** + * Handles font dropdown selection changes to change the selected font family. + * @param {Event} event - The change event from the font dropdown. + */ onFontDropdownSelect(event) { const select = event.target; this.onFontChange(select.value); } + /** + * Changes the font family for the boost. If the same font is selected again, + * it clears the font override (sets to empty string). + * @param {string} font - The font family string to apply. + */ onFontChange(font) { if (this.currentBoostData.fontFamily == font) this.currentBoostData.fontFamily = ''; else this.currentBoostData.fontFamily = font; @@ -439,6 +555,10 @@ export class nsZenBoostEditor { this.updateCurrentBoost(); } + /** + * Updates the visual state of font selection buttons and dropdown + * to reflect the currently selected font family. + */ updateFontButtonVisuals() { const fontButtonGroup = this.doc.getElementById('zen-boost-font-grid'); for (let i = 0; i < fontButtonGroup.children.length; i++) { @@ -458,10 +578,17 @@ export class nsZenBoostEditor { } } + /** + * Updates the boost data in the boosts manager with the current boost data. + * This triggers notifications to observers but does not persist to disk. + */ updateCurrentBoost() { gZenBoostsManager.updateBoost(this.currentBoostData); } + /** + * Deletes the current boost for the domain and closes the editor window. + */ onDeleteBoost() { gZenBoostsManager.deleteBoost(this.currentBoostData.domain); this.currentBoostData = null; @@ -470,10 +597,17 @@ export class nsZenBoostEditor { this.window.close(); } + /** + * Handles the close button press by closing the editor window. + */ onClosePressed() { this.window.close(); } + /** + * Handles the editor window close event. Saves the boost if changes were made, + * or deletes it if no changes were made (temporary boost). + */ handleClose() { this.uninit(); if (this.currentBoostData != null && this.currentBoostData.changeWasMade) this.saveBoost(); @@ -481,6 +615,11 @@ export class nsZenBoostEditor { gZenBoostsManager.deleteBoost(this.currentBoostData.domain); } + /** + * Loads boost data for the specified domain and initializes the editor UI + * with the boost settings (dot position, sliders, buttons, etc.). + * @param {string} domain - The domain for which to load the boost. + */ loadBoost(domain) { this.currentBoostData = gZenBoostsManager.loadBoostFromStore(domain); @@ -512,6 +651,10 @@ export class nsZenBoostEditor { this.updateButtonToggleVisuals(); } + /** + * Saves the current boost data to persistent storage if changes were made. + * @param {boolean} showToast - Whether to show a toast notification on save (default: true). + */ saveBoost(showToast = true) { if (this.currentBoostData == null || !this.currentBoostData.changeWasMade) return; diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index f7df446829..29ecf97d2d 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -1,27 +1,43 @@ -// 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/. +/* 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/. */ -export class nsZenBoostsManager { +import { nsZenBoostStyles } from 'resource:///modules/ZenBoostStyles.sys.mjs'; + +class nsZenBoostsManager { registeredBoosts = new Map(); + #stylesManager = new nsZenBoostStyles(); #saveFilename = 'zen-boosts.jsonlz4'; constructor() { this.#init(); } + /** + * Initializes the boosts manager by reading boosts from persistent storage. + * @private + */ #init() { this.#readBoostsFromStore(this.notify); } + /** + * Deletes a boost for the specified domain and persists the change to disk. + * @param {string} domain - The domain for which to delete the boost. + */ deleteBoost(domain) { if (this.registeredBoosts.has(domain)) this.registeredBoosts.delete(domain); this.#writeToDisk(this.registeredBoosts); this.notify(); } - // Load a boost from a domain + /** + * Loads a boost configuration for the specified domain from storage. + * If no boost exists for the domain, creates and returns a new default boost configuration. + * @param {string} domain - The domain for which to load the boost. + * @returns {Object} The boost data object containing all boost settings for the domain. + */ loadBoostFromStore(domain) { if (!domain) console.error('[ZenBoostsManager] Domain expected but got null.'); @@ -61,23 +77,38 @@ export class nsZenBoostsManager { return boostData; } + /** + * Updates the boost data for a domain in memory and notifies observers of the change. + * @param {Object} boostData - The boost data object to update. + */ updateBoost(boostData) { this.registeredBoosts.set(boostData.domain, boostData); this.notify(); } + /** + * Notifies all observers that boost data has been updated. + * This triggers a 'zen-boosts-update' notification event. + */ notify() { Services.obs.notifyObservers(null, 'zen-boosts-update'); } - // Save all boosts to the profile folder + /** + * Saves a boost configuration to persistent storage and notifies observers. + * @param {Object|null} boostData - The boost data object to save. If null, only saves existing boosts. + */ saveBoostToStore(boostData) { if (boostData != null) this.registeredBoosts.set(boostData.domain, boostData); this.#writeToDisk(this.registeredBoosts); this.notify(); } - // Reads all boosts from the profile folder + /** + * Reads all boosts from persistent storage and updates the registered boosts map. + * @param {Function} done - Callback function to execute after reading is complete. + * @private + */ #readBoostsFromStore(done) { this.#readFromDisk().then((map) => { this.registeredBoosts = map; @@ -85,12 +116,21 @@ export class nsZenBoostsManager { }); } + /** + * Gets the file path where boost data is stored in the user's profile directory. + * @returns {string} The full path to the boost storage file. + * @private + */ get #storePath() { const profilePath = PathUtils.profileDir; return PathUtils.join(profilePath, this.#saveFilename); } - // Helper method, disk => json => map + /** + * Reads boost data from disk, decompresses it, and converts it to a Map. + * @returns {Promise} A promise that resolves to a Map of domain to boost data. + * @private + */ async #readFromDisk() { const savePath = this.#storePath; @@ -100,21 +140,43 @@ export class nsZenBoostsManager { return new Map(array); } - // Helper method, map => json => disk + /** + * Writes boost data to disk by converting the Map to JSON and compressing it. + * @param {Map} map - The Map of domain to boost data to write to disk. + * @private + */ #writeToDisk(map) { const array = Array.from(map.entries()); IOUtils.writeJSON(this.#storePath, array, { compress: true }); } - // Checks if there is a boost registered for the currently open tab + /** + * Checks if a boost is registered for the specified domain. + * @param {string} domain - The domain to check for a registered boost. + * @returns {boolean} True if a boost exists for the domain, false otherwise. + */ registeredBoostForDomain(domain) { return this.registeredBoosts.has(domain); } - // Checks if a boost can be created + /** + * Determines if a boost can be created for the given URI. + * Only HTTP and HTTPS schemes are supported for boosting. + * @param {nsIURI} uri - The URI to check for boost eligibility. + * @returns {boolean} True if the URI scheme is http or https, false otherwise. + */ canBoostSite(uri) { return uri.schemeIs('http') || uri.schemeIs('https'); } + + /** + * @brief Gets from cache or creates and caches a new style sheet for the given boost data. + * @param {Object} boostData - The boost data object containing all boost settings for the domain. + * @returns {nsIStyleSheet} The style sheet corresponding to the boost data. + */ + getStyleSheetForBoost(boostData) { + //return + } } export const gZenBoostsManager = new nsZenBoostsManager(); diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index ce2376b8b8..5d1349cfb1 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -3,6 +3,9 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. export class ZenBoostsChild extends JSWindowActorChild { + /** + * Creates a new ZenBoostsChild actor instance. + */ constructor() { super(); } @@ -57,6 +60,11 @@ export class ZenBoostsChild extends JSWindowActorChild { return [round(r * 255), round(g * 255), round(b * 255)]; } + /** + * Handles DOM events for the actor. Applies boost settings when a document + * element is inserted. + * @param {Event} event - The DOM event to handle. + */ handleEvent(event) { switch (event.type) { case 'DOMDocElementInserted': @@ -66,6 +74,11 @@ export class ZenBoostsChild extends JSWindowActorChild { } } + /** + * Handles messages received from the parent actor. + * @param {Object} message - The message object containing name and data. + * @returns {Promise} A promise that resolves when the message is handled. + */ async receiveMessage(message) { switch (message.name) { case 'ZenBoost:BoostDataUpdated': diff --git a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs index 7f5be44d86..da821b1ba9 100644 --- a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs @@ -9,6 +9,10 @@ ChromeUtils.defineESModuleGetters(lazy, { }); export class ZenBoostsParent extends JSWindowActorParent { + /** + * Creates a new ZenBoostsParent actor instance and sets up an observer + * for boost update notifications. + */ constructor() { super(); @@ -16,16 +20,31 @@ export class ZenBoostsParent extends JSWindowActorParent { Services.obs.addObserver(this._observe, 'zen-boosts-update'); } + /** + * Called when the actor is destroyed. Cleans up the observer. + */ didDestroy() { Services.obs.removeObserver(this._observe, 'zen-boosts-update'); } + /** + * Observer callback that handles boost update notifications. + * Sends a message to child actors when boosts are updated. + * @param {Object} subject - The subject of the notification. + * @param {string} topic - The topic of the notification. + */ observe(subject, topic) { if (topic === 'zen-boosts-update') { this.sendQuery('ZenBoost:BoostDataUpdated'); } } + /** + * Handles messages received from child actors. + * Retrieves boost data for a domain when requested. + * @param {Object} message - The message object containing name and data. + * @returns {Promise} A promise that resolves to the boost data or null. + */ async receiveMessage(message) { switch (message.name) { case 'ZenBoost:GetBoostForDomain': { diff --git a/src/zen/boosts/moz.build b/src/zen/boosts/moz.build index 6842675805..68d62ac632 100644 --- a/src/zen/boosts/moz.build +++ b/src/zen/boosts/moz.build @@ -5,6 +5,7 @@ EXTRA_JS_MODULES += [ "ZenBoostsEditor.sys.mjs", "ZenBoostsManager.sys.mjs", + "ZenBoostStyles.sys.mjs", ] FINAL_TARGET_FILES.actors += [ diff --git a/src/zen/boosts/nsZenBoostsBackend.cpp b/src/zen/boosts/nsZenBoostsBackend.cpp index 2579d1834e..6b1fb07ddc 100644 --- a/src/zen/boosts/nsZenBoostsBackend.cpp +++ b/src/zen/boosts/nsZenBoostsBackend.cpp @@ -17,9 +17,16 @@ #include "mozilla/dom/Document.h" #include "mozilla/dom/BrowsingContext.h" +#define COLOR_CHANNEL_MIDPOINT 128 + using BrowsingContext = mozilla::dom::BrowsingContext; using BoostData = nscolor; // For now, Zen boosts data is just a color. +/** + * @brief Called when the ZenBoostsData field is set on a browsing context. + * Triggers a restyle if the boost data has changed. + * @param aOldValue The previous value of the boost data. + */ void BrowsingContext::DidSet(FieldIndex, BoostData aOldValue) { MOZ_ASSERT(IsTop()); @@ -32,13 +39,24 @@ void BrowsingContext::DidSet(FieldIndex, namespace zen { namespace { -// llvm x86 is poor at ternary operator, so use branchless min/max. +/** + * @brief Clamps a value to the range [0, 255] using branchless operations. + * @param v The value to clamp. + * @return The clamped value in the range [0, 255]. + */ static __inline int32_t clamp255(int32_t v) { + // llvm x86 is poor at ternary operator, so use branchless min/max. return (((255 - (v)) >> 31) | (v)) & 255; } -#define COLOR_CHANNEL_MIDPOINT 128 - +/** + * @brief Applies a color filter to transform an original color toward an accent color. + * Preserves the original color's perceived luminance while shifting hue/chroma toward the accent. + * Uses the alpha channel of the accent color to store contrast information. + * @param aOriginalColor The original color to filter. + * @param aAccentColor The accent color to filter toward (alpha channel contains contrast value). + * @return The filtered color with transformations applied. + */ static nscolor zenFilterColorChannel(nscolor aOriginalColor, nscolor aAccentColor) { auto r1 = NS_GET_R(aOriginalColor); auto g1 = NS_GET_G(aOriginalColor); diff --git a/src/zen/boosts/nsZenBoostsBackend.h b/src/zen/boosts/nsZenBoostsBackend.h index 3d7667561b..861e22c73f 100644 --- a/src/zen/boosts/nsZenBoostsBackend.h +++ b/src/zen/boosts/nsZenBoostsBackend.h @@ -25,13 +25,29 @@ class nsZenBoostsBackend final : public nsIZenBoostsBackend { /** * @brief Resolve a StyleAbsoluteColor to take into account Zen boosts. * @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; - // Overrides + /** + * @brief Called when a presshell is entered during rendering. + * @param aPresContext The presentation context that was entered. + */ auto onPressShellEntered(nsPresContext* aPresContext) -> void; + + /** + * @brief Called when a presshell is left during rendering. + * @param aPresContext The presentation context that was left. + */ auto onPressShellLeave(nsPresContext* aPresContext) -> void; + + /** + * @brief Recomputes browsing context dependent data, including Zen boost data. + * Triggers a restyle if the boost data has changed. + * @param aPresContext The presentation context to update. + * @param aBrowsingContext The browsing context containing the boost data. + */ void RecomputeBrowsingContextDependentData(nsPresContext* aPresContext, mozilla::dom::BrowsingContext* aBrowsingContext); NS_DECL_NSIZENBOOSTSBACKEND diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index 60669eb7a7..c76d3d4024 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -207,11 +207,6 @@ var gZenUIManager = { { once: true } ); - // Cleaning up on close - editor.window.addEventListener('unload', () => { - this.checkIsTabBoosted(); - }); - // Give the domain const domain = window.gBrowser.selectedTab.linkedBrowser.currentURI.host; editor.domain = domain; @@ -219,9 +214,6 @@ var gZenUIManager = { // Give the animator editor.gZenUIManager = this; - // Update icon - this.checkIsTabBoosted(); - return editor; }, @@ -253,21 +245,6 @@ var gZenUIManager = { return this._tabsWrapper; }, - checkIsTabBoosted() { - const button = document.getElementById('zen-site-data-icon-button'); - - // This seems to be a safer way than doing currentURI.host - const url = new URL(window.gBrowser.selectedTab.linkedBrowser.currentURI.spec); - const domain = url.hostname; - - const { gZenBoostsManager } = ChromeUtils.importESModule( - 'resource:///modules/ZenBoostsManager.sys.mjs' - ); - - if (gZenBoostsManager.registeredBoostForDomain(domain)) button.setAttribute('boosting', 'true'); - else button.removeAttribute('boosting'); - }, - onTabClose(event = undefined) { if (!event?.target?._closedInMultiselection) { this.updateTabsToolbar(); diff --git a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs index 76b810d172..5a2e6474ce 100644 --- a/src/zen/urlbar/ZenSiteDataPanel.sys.mjs +++ b/src/zen/urlbar/ZenSiteDataPanel.sys.mjs @@ -76,20 +76,45 @@ export class nsZenSiteDataPanel { this.window.gBrowser.addProgressListener({ onLocationChange: (aWebProgress) => { if (aWebProgress.isTopLevel) { - this.window.gZenUIManager.checkIsTabBoosted(); + this.checkIfTabIsBoosted(); } }, }); + this.window.addEventListener( + 'unload', + () => { + Services.obs.removeObserver(this, 'zen-boosts-update'); + }, + { once: true } + ); } observe(subject, topic) { switch (topic) { case 'zen-boosts-update': - this.window.gZenUIManager.checkIsTabBoosted(); + this.checkIfTabIsBoosted(); break; } } + #getCurrentDomain() { + try { + return this.window.gBrowser.currentURI.host; + } catch { + return ''; + } + } + + checkIfTabIsBoosted() { + const domain = this.#getCurrentDomain(); + const isBoosted = lazy.gZenBoostsManager.registeredBoostForDomain(domain); + if (isBoosted) { + this.anchor.setAttribute('boosting', 'true'); + } else { + this.anchor.removeAttribute('boosting'); + } + } + #initCopyUrlButton() { // This function is a bit out of place, but it's related enough to the panel // that it's easier to do it here than in a separate module. @@ -157,8 +182,7 @@ export class nsZenSiteDataPanel { #setSiteBoost() { const boostButton = this.document.getElementById('zen-site-data-boost'); - const url = new URL(this.window.gBrowser.selectedTab.linkedBrowser.currentURI.spec); - const domain = url.hostname; + const domain = this.#getCurrentDomain(); const uri = this.window.gBrowser.currentURI; if (!lazy.gZenBoostsManager.canBoostSite(uri)) { From 4e12ac434eddb450cd7d57b039a44552600265a7 Mon Sep 17 00:00:00 2001 From: FlorianButz Date: Sun, 9 Nov 2025 14:47:54 +0100 Subject: [PATCH 30/33] feat: add system font fetching --- src/zen/boosts/ZenBoostsEditor.sys.mjs | 62 +++++++++++++++----- src/zen/boosts/ZenBoostsManager.sys.mjs | 6 +- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 11 ++-- src/zen/boosts/zen-boost-editor.xhtml | 6 +- src/zen/boosts/zen-boosts.css | 19 +----- 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index a63f3ff6a4..c74f7b91d2 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -130,38 +130,68 @@ export class nsZenBoostEditor { * for the available font families. */ initFonts() { - const fonts = [ - 'Arial, sans-serif', - "'Times New Roman', serif", - "'Courier New', monospace", - "'Georgia', serif", - "'Comic Sans MS'", + const commonFonts = [ + 'Arial', + 'Times New Roman', + 'Courier New', + 'Georgia', + 'Comic Sans MS', + 'Verdana', + 'Trebuchet MS', + 'Impact', + 'Palatino Linotype', + 'Tahoma', ]; + const fonts = this.fetchFontList(); const fontButtonGroup = this.doc.getElementById('zen-boost-font-grid'); - const fontSelect = this.doc.getElementById('zen-boost-font-select'); + const fontList = this.doc.getElementById('zen-boost-font-select'); const buttonCount = 10; - for (let i = 0; i < Math.min(buttonCount, fonts.length); i++) { + for (let i = 0; i < Math.min(commonFonts.length, buttonCount); i++) { + let font = fonts[i]; // Fallback + if(fonts.includes(commonFonts[i])){ + font = commonFonts[i]; + } + const fontButton = this.doc.createElement('button'); - fontButton.setAttribute('font-data', `${fonts[i]}`); + fontButton.setAttribute('font-data', `${font}`); fontButton.classList.add('subviewbutton'); - fontButton.style.fontFamily = fonts[i]; + fontButton.style.fontFamily = `'${font}'`; fontButton.innerHTML = 'Aa'; fontButton.addEventListener('click', this.onFontButtonClick.bind(this)); - + fontButtonGroup.appendChild(fontButton); } + // Add default value + const defaultOption = this.doc.createElement('option'); + defaultOption.value = ''; // Use default font of site + defaultOption.label = "Default"; + fontList.appendChild(defaultOption); + for (let j = 0; j < fonts.length; j++) { const font = fonts[j]; - const select = this.doc.createElement('option'); - select.value = font; - select.innerHTML = font; - fontSelect.appendChild(select); + const option = this.doc.createElement('option'); + option.style.fontFamily = `'${font}'`; + option.value = font; + option.label = font; + fontList.appendChild(option); } - fontSelect.addEventListener('change', this.onFontDropdownSelect.bind(this)); + fontList.addEventListener('change', this.onFontDropdownSelect.bind(this)); + } + + /** + * Fetches a list of all available system fonts. + * @returns {Array} An array with names of available fonts. + */ + fetchFontList() { + const enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].createInstance( + Ci.nsIFontEnumerator + ); + + return enumerator.EnumerateFonts(null, null); } /** diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 29ecf97d2d..b9f1e472f2 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -49,9 +49,9 @@ class nsZenBoostsManager { dotPos: { x: null, y: null }, dotDistance: 0, - brightness: 128, - contrast: 128, - saturation: 128, + brightness: 0.5, + saturation: 0.5, + contrast: 0.5, fontFamily: '', diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 5d1349cfb1..25b5da72fc 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -128,13 +128,12 @@ export class ZenBoostsChild extends JSWindowActorChild { if (boost.enableColorBoost) { const rgbColor = this.#hslToRgb( boost.dotAngleDeg / 360, - boost.dotDistance * (boost.saturation / 255).toFixed(4) /* already is [0, 1] */, - 0.2 + - boost.dotDistance * - 0.8 * - (boost.brightness / 255).toFixed(4) /* lightness range from [0.2, 0.8] */ + /* already is [0, 1] */ + boost.dotDistance * (1 - boost.saturation), + /* lightness range from [0.2, 0.6] */ + 0.2 + boost.dotDistance * 0.4 * boost.brightness ); - const nsColor = this.#rgbToNSColor(rgbColor, boost.contrast); + const nsColor = this.#rgbToNSColor(rgbColor, (1 - boost.contrast) * 255); browsingContext.zenBoostsData = nsColor; } else browsingContext.zenBoostsData = 0; } else { diff --git a/src/zen/boosts/zen-boost-editor.xhtml b/src/zen/boosts/zen-boost-editor.xhtml index 41d24d970d..795a964ec9 100644 --- a/src/zen/boosts/zen-boost-editor.xhtml +++ b/src/zen/boosts/zen-boost-editor.xhtml @@ -93,13 +93,13 @@

Contrast

- +

Brightness

- +

Original Saturation

- +
diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index db8e658516..b839c32265 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -9,7 +9,6 @@ max-width: 185px; min-width: 185px; min-height: 528px; - max-height: 528px; } body { @@ -37,8 +36,8 @@ body { appearance: none; &:active { - scale: 1; - transform: none; + scale: 1 !important; + transform: none !important; } } @@ -153,6 +152,7 @@ body { & button { padding: auto !important; margin: auto !important; + background-color: transparent; } } @@ -190,19 +190,6 @@ body { } } -/* -#zen-boost-code { - opacity: 0.5 !important; - cursor: not-allowed !important; - pointer-events: none !important; -} - -#zen-boost-zap { - opacity: 0.5 !important; - cursor: not-allowed !important; - pointer-events: none !important; -} */ - .zen-boost-panel-disabled { filter: grayscale(1); } From f859aa1d7b367a02c4b1ab40a1a9102c3dee4a6f Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Sun, 9 Nov 2025 15:42:05 +0100 Subject: [PATCH 31/33] feat: Implemented stylesheet managers, b=no-bug, c=no-component --- src/zen/boosts/ZenBoostStyles.sys.mjs | 76 +++++++++++++++++++ src/zen/boosts/ZenBoostsManager.sys.mjs | 6 +- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 73 ++++++++++++------ src/zen/boosts/actors/ZenBoostsParent.sys.mjs | 11 ++- 4 files changed, 136 insertions(+), 30 deletions(-) diff --git a/src/zen/boosts/ZenBoostStyles.sys.mjs b/src/zen/boosts/ZenBoostStyles.sys.mjs index 05d5f84d84..84f9879d10 100644 --- a/src/zen/boosts/ZenBoostStyles.sys.mjs +++ b/src/zen/boosts/ZenBoostStyles.sys.mjs @@ -1,7 +1,83 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { XPCOMUtils } from 'resource://gre/modules/XPCOMUtils.sys.mjs'; + +const lazy = XPCOMUtils.declareLazy({ + styleSheetService: { + service: '@mozilla.org/content/style-sheet-service;1', + iid: Ci.nsIStyleSheetService, + }, +}); + +const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET; + export class nsZenBoostStyles { #stylesCache = new Map(); + + /** + * Retrieves the CSS style string for a given boost configuration. + * Caches styles to optimize performance. + * @param {Object} boostData - The boost configuration data. + * @returns {string} The generated CSS style string. + */ + getStyleForBoost(boostData) { + const { domain } = boostData; + if (this.#stylesCache.has(domain)) { + return this.#stylesCache.get(domain); + } + + const rawStyle = this.#generateStyleString(boostData); + if (!rawStyle) return null; + + const styleUri = this.#convertStyleToDataUri(rawStyle); + this.#cacheStyle(styleUri, domain); + return this.getStyleForBoost(boostData); + } + + invalidateStyleForDomain(domain) { + if (this.#stylesCache.has(domain)) { + const { uri } = this.#stylesCache.get(domain); + lazy.styleSheetService.unregisterSheet(uri, AGENT_SHEET); + this.#stylesCache.delete(domain); + } + } + + /** + * Generates a CSS style string based on the boost configuration. + * @param {Object} boostData - The boost configuration data. + * @returns {string} The generated CSS style string. + * @private + */ + #generateStyleString(boostData) { + return '* { color: red !important; }'; // Placeholder implementation + } + + /** + * Converts a raw CSS style string into a data URI. + * @param {string} rawStyle - The raw CSS style string. + * @returns {string} The data URI representing the CSS style. + * @private + */ + #convertStyleToDataUri(rawStyle) { + const encodedStyle = encodeURIComponent(rawStyle); + return Services.io.newURI(`data:text/css;charset=utf-8,${encodedStyle}`); + } + + /** + * Prefetches the style from the data URI and caches it. + * @param {string} styleUri - The data URI of the CSS style. + * @param {string} domain - The domain associated with the boost. + * @returns {string} The cached style sheet URI. + * @private + */ + #cacheStyle(styleUri, domain) { + this.#stylesCache.set(domain, { + uuid: Services.uuid.generateUUID().toString(), + uri: styleUri.spec, + }); + } } diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index 29ecf97d2d..37d8dadf9a 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -90,8 +90,8 @@ class nsZenBoostsManager { * Notifies all observers that boost data has been updated. * This triggers a 'zen-boosts-update' notification event. */ - notify() { - Services.obs.notifyObservers(null, 'zen-boosts-update'); + notify(unloadStyles = false) { + Services.obs.notifyObservers(null, 'zen-boosts-update', { unloadStyles }); } /** @@ -175,7 +175,7 @@ class nsZenBoostsManager { * @returns {nsIStyleSheet} The style sheet corresponding to the boost data. */ getStyleSheetForBoost(boostData) { - //return + return this.#stylesManager.getStyleForBoost(boostData); } } diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 5d1349cfb1..87bf180d20 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -2,7 +2,11 @@ // 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/. +const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET; + export class ZenBoostsChild extends JSWindowActorChild { + #currentSheet = null; + /** * Creates a new ZenBoostsChild actor instance. */ @@ -77,13 +81,13 @@ export class ZenBoostsChild extends JSWindowActorChild { /** * Handles messages received from the parent actor. * @param {Object} message - The message object containing name and data. - * @returns {Promise} A promise that resolves when the message is handled. */ async receiveMessage(message) { switch (message.name) { - case 'ZenBoost:BoostDataUpdated': - this.#applyBoostForPageIfAvailable(); - return Promise.resolve(null); + case 'ZenBoost:BoostDataUpdated': { + const { unloadStyles = false } = message.data || {}; + this.#applyBoostForPageIfAvailable(unloadStyles); + } } } @@ -102,9 +106,9 @@ export class ZenBoostsChild extends JSWindowActorChild { /** * Applies the boost settings for the current page if available. - * @returns {Promise} + * @param {boolean} unloadStyles - Indicates whether to unload styles. */ - async #applyBoostForPageIfAvailable() { + async #applyBoostForPageIfAvailable(unloadStyles = false) { const browsingContext = this.browsingContext; if (!browsingContext) { return null; @@ -115,8 +119,14 @@ export class ZenBoostsChild extends JSWindowActorChild { return null; } - this.sendQuery('ZenBoost:GetBoostForDomain', domain).then((boost) => { - if (boost) { + const boost = await this.sendQuery('ZenBoost:GetBoostForDomain', domain); + + if (unloadStyles || !boost?.enableColorBoost) { + this.#unloadCurrentStyleSheet(); + } + + if (boost) { + if (boost.enableColorBoost) { let prefersColorSchemeOverride = 'none'; if (boost.smartInvert) { prefersColorSchemeOverride = boost.topWindowIsDarkMode ? 'light' : 'dark'; @@ -125,22 +135,37 @@ export class ZenBoostsChild extends JSWindowActorChild { // Has to be a finite value for zoom to work correctly // TODO: Figure out something better for site size override // browsingContext.fullZoom = boost.siteSizeOverride; - if (boost.enableColorBoost) { - const rgbColor = this.#hslToRgb( - boost.dotAngleDeg / 360, - boost.dotDistance * (boost.saturation / 255).toFixed(4) /* already is [0, 1] */, - 0.2 + - boost.dotDistance * - 0.8 * - (boost.brightness / 255).toFixed(4) /* lightness range from [0.2, 0.8] */ - ); - const nsColor = this.#rgbToNSColor(rgbColor, boost.contrast); - browsingContext.zenBoostsData = nsColor; - } else browsingContext.zenBoostsData = 0; - } else { - browsingContext.prefersColorSchemeOverride = 'none'; - browsingContext.zenBoostsData = 0; + if (boost.styleSheet) { + const { styleSheet } = boost; + styleSheet.uri = Services.io.newURI(styleSheet.uri); + if (this.#currentSheet?.uuid !== styleSheet.uuid) { + browsingContext.window.windowUtils.loadSheet(styleSheet.uri, AGENT_SHEET); + this.#currentSheet = styleSheet; + } + } + const rgbColor = this.#hslToRgb( + boost.dotAngleDeg / 360, + boost.dotDistance * (boost.saturation / 255).toFixed(4) /* already is [0, 1] */, + 0.2 + + boost.dotDistance * + 0.8 * + (boost.brightness / 255).toFixed(4) /* lightness range from [0.2, 0.8] */ + ); + const nsColor = this.#rgbToNSColor(rgbColor, boost.contrast); + browsingContext.zenBoostsData = nsColor; + return; } - }); + } + + browsingContext.prefersColorSchemeOverride = 'none'; + browsingContext.zenBoostsData = 0; + } + + #unloadCurrentStyleSheet() { + const browsingContext = this.browsingContext; + if (this.#currentSheet && browsingContext) { + browsingContext.window.windowUtils.removeSheet(this.#currentSheet.uri, AGENT_SHEET); + this.#currentSheet = null; + } } } diff --git a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs index da821b1ba9..53c80e2521 100644 --- a/src/zen/boosts/actors/ZenBoostsParent.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsParent.sys.mjs @@ -35,7 +35,7 @@ export class ZenBoostsParent extends JSWindowActorParent { */ observe(subject, topic) { if (topic === 'zen-boosts-update') { - this.sendQuery('ZenBoost:BoostDataUpdated'); + this.sendQuery('ZenBoost:BoostDataUpdated', { unloadStyles: true }); } } @@ -51,10 +51,15 @@ export class ZenBoostsParent extends JSWindowActorParent { const domain = message.data; const embedder = this.browsingContext.top.embedderElement; if (!embedder || !domain) return null; - if (!lazy.gZenBoostsManager.registeredBoostForDomain(domain)) return null; + const exists = lazy.gZenBoostsManager.registeredBoostForDomain(domain); + if (!exists) return null; const topWindowIsDarkMode = embedder.ownerGlobal.getComputedStyle(embedder).colorScheme === 'dark'; - return { ...lazy.gZenBoostsManager.loadBoostFromStore(domain), topWindowIsDarkMode }; + return { + ...lazy.gZenBoostsManager.loadBoostFromStore(domain), + topWindowIsDarkMode, + styleSheet: await lazy.gZenBoostsManager.getStyleSheetForBoost(domain), + }; } default: console.warn(`[ZenBoostsParent]: Unknown message: ${message.name}`); From 254d83c8e37721f59128cfdd54f28bdcaa8649ab Mon Sep 17 00:00:00 2001 From: FlorianButz Date: Sun, 9 Nov 2025 18:05:22 +0100 Subject: [PATCH 32/33] fix: correct color conversion, add stylesheet implementation --- src/browser/themes/shared/zen-icons/icons.css | 4 ++-- src/zen/boosts/ZenBoostStyles.sys.mjs | 17 +++++++++++++++-- src/zen/boosts/ZenBoostsEditor.sys.mjs | 17 +++++++---------- src/zen/boosts/ZenBoostsManager.sys.mjs | 1 + src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 5 +++-- src/zen/boosts/zen-boosts.css | 4 ++++ 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css index a3a486649d..b18f0df69e 100644 --- a/src/browser/themes/shared/zen-icons/icons.css +++ b/src/browser/themes/shared/zen-icons/icons.css @@ -1015,11 +1015,11 @@ list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-title-case.svg'); } -#zen-boost-text-case-toggle[mode='upper'] { +#zen-boost-text-case-toggle[mode='uppercase'] { list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-uppercase.svg'); } -#zen-boost-text-case-toggle[mode='lower'] { +#zen-boost-text-case-toggle[mode='lowercase'] { list-style-image: url('chrome://browser/skin/zen-icons/selectable/text-lowercase.svg'); } diff --git a/src/zen/boosts/ZenBoostStyles.sys.mjs b/src/zen/boosts/ZenBoostStyles.sys.mjs index 84f9879d10..c6ce76685b 100644 --- a/src/zen/boosts/ZenBoostStyles.sys.mjs +++ b/src/zen/boosts/ZenBoostStyles.sys.mjs @@ -10,7 +10,11 @@ const lazy = XPCOMUtils.declareLazy({ styleSheetService: { service: '@mozilla.org/content/style-sheet-service;1', iid: Ci.nsIStyleSheetService, - }, + } +}); + +ChromeUtils.defineESModuleGetters(lazy, { + gZenBoostsManager: 'resource:///modules/ZenBoostsManager.sys.mjs', }); const AGENT_SHEET = Ci.nsIStyleSheetService.AGENT_SHEET; @@ -53,7 +57,16 @@ export class nsZenBoostStyles { * @private */ #generateStyleString(boostData) { - return '* { color: red !important; }'; // Placeholder implementation + const boost = lazy.gZenBoostsManager.loadBoostFromStore(boostData); + console.log(boost); + + let style = 'body, p, h1, h2, h3, h4, h5, a, span, textarea, input, span {'; + + if(boost.fontFamily != '') + style += `font-family: ${boost.fontFamily} !important;`; + style += `text-transform: ${boost.textCaseOverride} !important;`; + + return style; } /** diff --git a/src/zen/boosts/ZenBoostsEditor.sys.mjs b/src/zen/boosts/ZenBoostsEditor.sys.mjs index 18a8ec9896..d418ad1372 100644 --- a/src/zen/boosts/ZenBoostsEditor.sys.mjs +++ b/src/zen/boosts/ZenBoostsEditor.sys.mjs @@ -291,11 +291,13 @@ export class nsZenBoostEditor { * (none, lower, upper) and updating the UI accordingly. */ onBoostCasePressed() { - if (this.currentBoostData.textCaseOverride == 'lower') - this.currentBoostData.textCaseOverride = 'upper'; - else if (this.currentBoostData.textCaseOverride == 'upper') + if (this.currentBoostData.textCaseOverride == 'lowercase') + this.currentBoostData.textCaseOverride = 'uppercase'; + else if (this.currentBoostData.textCaseOverride == 'uppercase') + this.currentBoostData.textCaseOverride = 'capitalize'; + else if (this.currentBoostData.textCaseOverride == 'capitalize') this.currentBoostData.textCaseOverride = 'none'; - else this.currentBoostData.textCaseOverride = 'lower'; + else this.currentBoostData.textCaseOverride = 'lowercase'; this.updateCaseButtonVisuals(); this.updateCurrentBoost(); @@ -512,12 +514,7 @@ export class nsZenBoostEditor { */ updateCaseButtonVisuals() { const sizeValue = this.doc.getElementById('zen-boost-text-case-toggle'); - - if (this.currentBoostData.textCaseOverride == 'none') sizeValue.setAttribute('mode', 'none'); - else if (this.currentBoostData.textCaseOverride == 'upper') - sizeValue.setAttribute('mode', 'upper'); - else if (this.currentBoostData.textCaseOverride == 'lower') - sizeValue.setAttribute('mode', 'lower'); + sizeValue.setAttribute('mode', this.currentBoostData.textCaseOverride); } /** diff --git a/src/zen/boosts/ZenBoostsManager.sys.mjs b/src/zen/boosts/ZenBoostsManager.sys.mjs index e4ee4ba7dd..655e0dc27f 100644 --- a/src/zen/boosts/ZenBoostsManager.sys.mjs +++ b/src/zen/boosts/ZenBoostsManager.sys.mjs @@ -83,6 +83,7 @@ class nsZenBoostsManager { */ updateBoost(boostData) { this.registeredBoosts.set(boostData.domain, boostData); + this.#stylesManager.invalidateStyleForDomain(boostData.domain); this.notify(); } diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 58091d9292..4a70b74192 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -144,12 +144,13 @@ export class ZenBoostsChild extends JSWindowActorChild { } } const rgbColor = this.#hslToRgb( + boost.dotAngleDeg / 360, /* already is [0, 1] */ boost.dotDistance * (1 - boost.saturation), /* lightness range from [0.2, 0.6] */ - 0.2 + boost.dotDistance * 0.4 * boost.brightness + 0.1 + boost.dotDistance * 0.6 * boost.brightness ); - const nsColor = this.#rgbToNSColor(rgbColor, boost.contrast); + const nsColor = this.#rgbToNSColor(rgbColor, (1 - boost.contrast) * 255); browsingContext.zenBoostsData = nsColor; return; } diff --git a/src/zen/boosts/zen-boosts.css b/src/zen/boosts/zen-boosts.css index b839c32265..1a3273d416 100644 --- a/src/zen/boosts/zen-boosts.css +++ b/src/zen/boosts/zen-boosts.css @@ -397,6 +397,10 @@ body { background: linear-gradient(180deg, #5d45ff 0%, #4125ff 100%) border-box; } +#zen-boost-text-case-toggle[mode='none'] { + opacity: 0.5; +} + .zen-boost-color-picker-gradient::after { content: ''; position: absolute; From 54c04f471542c55bde2f78ffc4ebfabfb8a72782 Mon Sep 17 00:00:00 2001 From: "Mr. M" Date: Wed, 12 Nov 2025 16:48:55 +0100 Subject: [PATCH 33/33] fix: Fixed crashes on sub-document contexts, b=no-bug, c=no-component --- src/zen/boosts/ZenBoostStyles.sys.mjs | 7 +++---- src/zen/boosts/actors/ZenBoostsChild.sys.mjs | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/zen/boosts/ZenBoostStyles.sys.mjs b/src/zen/boosts/ZenBoostStyles.sys.mjs index c6ce76685b..6dddf69912 100644 --- a/src/zen/boosts/ZenBoostStyles.sys.mjs +++ b/src/zen/boosts/ZenBoostStyles.sys.mjs @@ -10,7 +10,7 @@ const lazy = XPCOMUtils.declareLazy({ styleSheetService: { service: '@mozilla.org/content/style-sheet-service;1', iid: Ci.nsIStyleSheetService, - } + }, }); ChromeUtils.defineESModuleGetters(lazy, { @@ -61,9 +61,8 @@ export class nsZenBoostStyles { console.log(boost); let style = 'body, p, h1, h2, h3, h4, h5, a, span, textarea, input, span {'; - - if(boost.fontFamily != '') - style += `font-family: ${boost.fontFamily} !important;`; + + if (boost.fontFamily != '') style += `font-family: ${boost.fontFamily} !important;`; style += `text-transform: ${boost.textCaseOverride} !important;`; return style; diff --git a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs index 4a70b74192..1c38a516d6 100644 --- a/src/zen/boosts/actors/ZenBoostsChild.sys.mjs +++ b/src/zen/boosts/actors/ZenBoostsChild.sys.mjs @@ -110,7 +110,9 @@ export class ZenBoostsChild extends JSWindowActorChild { */ async #applyBoostForPageIfAvailable(unloadStyles = false) { const browsingContext = this.browsingContext; - if (!browsingContext) { + // Prevent applying boosts to iframes or non-top-level browsing contexts. + // It makes the tab crash if we try to load stylesheets into an iframe's + if (!browsingContext || browsingContext.parent !== null) { return null; }