diff --git a/package-lock.json b/package-lock.json
index 4be119e91..db79a9ecd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MPL-2.0",
"dependencies": {
- "@zen-browser/surfer": "^1.11.26"
+ "@zen-browser/surfer": "^1.12.0"
},
"devDependencies": {
"@babel/preset-typescript": "^7.27.0",
@@ -35,6 +35,7 @@
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
@@ -64,6 +65,7 @@
"integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -106,6 +108,7 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
+ "peer": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -146,6 +149,7 @@
"integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/compat-data": "^7.26.8",
"@babel/helper-validator-option": "^7.25.9",
@@ -163,6 +167,7 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
+ "peer": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -336,6 +341,7 @@
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
@@ -1109,9 +1115,9 @@
"license": "MIT"
},
"node_modules/@zen-browser/surfer": {
- "version": "1.11.26",
- "resolved": "https://registry.npmjs.org/@zen-browser/surfer/-/surfer-1.11.26.tgz",
- "integrity": "sha512-NZcFZ4a/HWvEJlEr5IlQto/xHLOr6tZjkZALue2qHg+rjRKR5v2BEV4hV5mfAo85gKhyM2Ism0sD+0+/VQIESg==",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/@zen-browser/surfer/-/surfer-1.12.0.tgz",
+ "integrity": "sha512-I5nxDgGpFGtdOAC9DZkoQp9GJ4cAqCW+0p0DoQRjW/jdnQJUH20ygvPyPr+sgjXISPFclYX+KrVoT2kJqTdlTw==",
"license": "MPL-2.0",
"dependencies": {
"@resvg/resvg-js": "^1.4.0",
@@ -1147,7 +1153,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1819,7 +1824,8 @@
"url": "https://github.com/sponsors/ai"
}
],
- "license": "CC-BY-4.0"
+ "license": "CC-BY-4.0",
+ "peer": true
},
"node_modules/chalk": {
"version": "4.1.2",
@@ -2006,7 +2012,8 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/core-js-pure": {
"version": "3.41.0",
@@ -2326,7 +2333,8 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.139.tgz",
"integrity": "sha512-GGnRYOTdN5LYpwbIr0rwP/ZHOQSvAF6TG0LSzp28uCBb9JiXHJGmaaKw29qjNJc5bGnnp6kXJqRnGMQoELwi5w==",
"dev": true,
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@@ -2562,6 +2570,7 @@
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -2585,7 +2594,6 @@
"integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3433,6 +3441,7 @@
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -4430,6 +4439,7 @@
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"json5": "lib/cli.js"
},
@@ -4864,6 +4874,7 @@
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"license": "ISC",
+ "peer": true,
"dependencies": {
"yallist": "^3.0.2"
}
@@ -5078,7 +5089,8 @@
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/normalize-path": {
"version": "3.0.0",
@@ -5534,7 +5546,6 @@
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -6949,6 +6960,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
@@ -7252,7 +7264,8 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true,
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/yaml": {
"version": "2.7.0",
diff --git a/package.json b/package.json
index 17ae8137e..355f9be11 100644
--- a/package.json
+++ b/package.json
@@ -49,7 +49,7 @@
},
"homepage": "https://github.com/zen-browser/desktop#readme",
"dependencies": {
- "@zen-browser/surfer": "^1.11.26"
+ "@zen-browser/surfer": "^1.12.0"
},
"devDependencies": {
"@babel/preset-typescript": "^7.27.0",
diff --git a/src/browser/base/content/zen-panels/gradient-generator.inc b/src/browser/base/content/zen-panels/gradient-generator.inc
index b5f87468b..6bc5830d2 100644
--- a/src/browser/base/content/zen-panels/gradient-generator.inc
+++ b/src/browser/base/content/zen-panels/gradient-generator.inc
@@ -12,10 +12,9 @@
-
-
+
+
+
@@ -78,6 +77,27 @@
+
+# Start from black to white in a span on 8 steps and.
+# They must all go from the middle to the right side. They must always stay verically centered.
+# And reach to 180 on the right side, meaning we must divide the width in 16 segments.
+
+
+
+
+
+
+
+
+
diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css
index 258f007f3..80db0d71d 100644
--- a/src/browser/themes/shared/zen-icons/icons.css
+++ b/src/browser/themes/shared/zen-icons/icons.css
@@ -692,6 +692,10 @@
list-style-image: url('container-tab.svg') !important;
}
+#PanelUI-zen-gradient-generator-color-toggle-algo {
+ list-style-image: url('algorithm.svg');
+}
+
#appMenuClearRecentHistory {
list-style-image: url('edit-delete.svg') !important;
}
diff --git a/src/browser/themes/shared/zen-icons/jar.inc.mn b/src/browser/themes/shared/zen-icons/jar.inc.mn
index 485933c80..2efa99098 100644
--- a/src/browser/themes/shared/zen-icons/jar.inc.mn
+++ b/src/browser/themes/shared/zen-icons/jar.inc.mn
@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifdef XP_WIN
+* skin/classic/browser/zen-icons/algorithm.svg (../shared/zen-icons/lin/algorithm.svg)
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg)
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg)
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg)
@@ -131,6 +132,7 @@
* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg)
#endif
#ifdef XP_MACOSX
+* skin/classic/browser/zen-icons/algorithm.svg (../shared/zen-icons/lin/algorithm.svg)
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg)
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg)
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg)
@@ -259,6 +261,7 @@
* skin/classic/browser/zen-icons/zoom-out.svg (../shared/zen-icons/lin/zoom-out.svg)
#endif
#ifdef XP_LINUX
+* skin/classic/browser/zen-icons/algorithm.svg (../shared/zen-icons/lin/algorithm.svg)
* skin/classic/browser/zen-icons/arrow-down.svg (../shared/zen-icons/lin/arrow-down.svg)
* skin/classic/browser/zen-icons/arrow-left.svg (../shared/zen-icons/lin/arrow-left.svg)
* skin/classic/browser/zen-icons/arrow-right.svg (../shared/zen-icons/lin/arrow-right.svg)
diff --git a/src/browser/themes/shared/zen-icons/lin/algorithm.svg b/src/browser/themes/shared/zen-icons/lin/algorithm.svg
new file mode 100644
index 000000000..f35991ccc
--- /dev/null
+++ b/src/browser/themes/shared/zen-icons/lin/algorithm.svg
@@ -0,0 +1,5 @@
+#filter dumbComments emptyLines substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
\ No newline at end of file
diff --git a/src/browser/themes/shared/zen-icons/lin/face-sun.svg b/src/browser/themes/shared/zen-icons/lin/face-sun.svg
index 432970d42..543e131a0 100644
--- a/src/browser/themes/shared/zen-icons/lin/face-sun.svg
+++ b/src/browser/themes/shared/zen-icons/lin/face-sun.svg
@@ -2,4 +2,4 @@
# 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
+
\ No newline at end of file
diff --git a/src/browser/themes/shared/zen-icons/lin/moon-stars.svg b/src/browser/themes/shared/zen-icons/lin/moon-stars.svg
index 031c60f8e..f4739fbae 100644
--- a/src/browser/themes/shared/zen-icons/lin/moon-stars.svg
+++ b/src/browser/themes/shared/zen-icons/lin/moon-stars.svg
@@ -2,4 +2,4 @@
# 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
+
\ No newline at end of file
diff --git a/src/browser/themes/shared/zen-icons/lin/sparkles.svg b/src/browser/themes/shared/zen-icons/lin/sparkles.svg
index 77cc7d3ff..63f0bf063 100644
--- a/src/browser/themes/shared/zen-icons/lin/sparkles.svg
+++ b/src/browser/themes/shared/zen-icons/lin/sparkles.svg
@@ -2,4 +2,4 @@
# 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
+
\ No newline at end of file
diff --git a/src/firefox-patches/147_windows_border_fix_1.patch b/src/firefox-patches/147_windows_border_fix_1.patch
new file mode 100644
index 000000000..3a4dfb7d2
--- /dev/null
+++ b/src/firefox-patches/147_windows_border_fix_1.patch
@@ -0,0 +1,91 @@
+From 433cc8224790300fdabe76bd225b644c1812da34 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?=
+Date: Thu, 27 Nov 2025 15:28:12 +0000
+Subject: [PATCH] Bug 1993474 - Ensure our WNDPROC has precedence over
+ WinAppSDK's. r=gstoll,win-reviewers
+
+See the comment for reasoning. WM_NCCALCSIZE wasn't getting called, and
+we rely on that to get the right client area on things like maximized
+windows.
+
+Differential Revision: https://phabricator.services.mozilla.com/D274281
+---
+ widget/windows/nsWindow.cpp | 32 ++++++++++++++++++++++----------
+ widget/windows/nsWindow.h | 2 ++
+ 2 files changed, 24 insertions(+), 10 deletions(-)
+
+diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
+index 0b98d157097da..b357df236cfcd 100644
+--- a/widget/windows/nsWindow.cpp
++++ b/widget/windows/nsWindow.cpp
+@@ -1520,12 +1520,31 @@ DWORD nsWindow::WindowExStyle() {
+ *
+ **************************************************************/
+
++bool nsWindow::ShouldAssociateWithWinAppSDK() const {
++ // We currently don't need any SDK functionality for for PiP windows,
++ // and using the SDK on these windows causes them to go under the
++ // taskbar (bug 1995838).
++ //
++ // TODO(emilio): That might not be true anymore after bug 1993474,
++ // consider re-testing and removing that special-case.
++ return IsTopLevelWidget() && !mIsPIPWindow;
++}
++
+ bool nsWindow::AssociateWithNativeWindow() {
+ if (!mWnd || !IsWindow(mWnd)) {
+ NS_ERROR("Invalid window handle");
+ return false;
+ }
+
++ if (ShouldAssociateWithWinAppSDK()) {
++ // Make sure to call this here to associate our window with the
++ // Windows App SDK _before_ setting our WNDPROC, if needed.
++ // This is important because the SDKs WNDPROC might handle messages like
++ // WM_NCCALCSIZE without calling into us, and that can cause sizing issues,
++ // see bug 1993474.
++ WindowsUIUtils::SetIsTitlebarCollapsed(mWnd, mCustomNonClient);
++ }
++
+ // Connect the this pointer to the native window handle.
+ // This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
+ // uses WinUtils::GetNSWindowPtr internally.
+@@ -1552,12 +1571,7 @@ void nsWindow::DissociateFromNativeWindow() {
+ DebugOnly wndProcBeforeDissociate =
+ reinterpret_cast(::SetWindowLongPtrW(
+ mWnd, GWLP_WNDPROC, reinterpret_cast(*mPrevWndProc)));
+- // If we've used the Windows App SDK to remove the minimize/maximize/close
+- // entries from the titlebar, then the Windows App SDK sets its own WNDPROC
+- // own the window, so this assertion would fail. But we only do this if
+- // Mica is available.
+- NS_ASSERTION(WinUtils::MicaAvailable() ||
+- wndProcBeforeDissociate == nsWindow::WindowProc,
++ NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
+ "Unstacked an unexpected native window procedure");
+
+ WinUtils::SetNSWindowPtr(mWnd, nullptr);
+@@ -2835,9 +2849,7 @@ void nsWindow::SetCustomTitlebar(bool aCustomTitlebar) {
+ mCustomNonClientMetrics = {};
+ ResetLayout();
+ }
+- // Not needed for PiP windows, and using the Windows App SDK on
+- // these windows causes them to go under the taskbar (bug 1995838)
+- if (!mPIPWindow) {
++ if (ShouldAssociateWithWinAppSDK()) {
+ WindowsUIUtils::SetIsTitlebarCollapsed(mWnd, mCustomNonClient);
+ }
+ }
+diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
+index 20f016757dfee..2756b248368a3 100644
+--- a/widget/windows/nsWindow.h
++++ b/widget/windows/nsWindow.h
+@@ -335,6 +335,8 @@ class nsWindow final : public nsIWidget {
+
+ bool IsRTL() const { return mIsRTL; }
+
++ bool ShouldAssociateWithWinAppSDK() const;
++
+ /**
+ * AssociateDefaultIMC() associates or disassociates the default IMC for
+ * the window.
+
\ No newline at end of file
diff --git a/src/firefox-patches/147_windows_border_fix_2.patch b/src/firefox-patches/147_windows_border_fix_2.patch
new file mode 100644
index 000000000..de070e124
--- /dev/null
+++ b/src/firefox-patches/147_windows_border_fix_2.patch
@@ -0,0 +1,58 @@
+diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h
+--- a/widget/windows/nsWindow.h
++++ b/widget/windows/nsWindow.h
+@@ -850,13 +850,10 @@
+ bool mHasTaskbarIconBeenCreated = false;
+
+ // Whether we're in the process of sending a WM_SETTEXT ourselves
+ bool mSendingSetText = false;
+
+- // Whether we're a PIP window.
+- bool mPIPWindow : 1;
+-
+ // Whether we are asked to render a mica backdrop.
+ bool mMicaBackdrop : 1;
+
+ int32_t mCachedHitTestResult = 0;
+
+diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp
+--- a/widget/windows/nsWindow.cpp
++++ b/widget/windows/nsWindow.cpp
+@@ -815,11 +815,10 @@
+ **************************************************************/
+
+ nsWindow::nsWindow()
+ : nsIWidget(BorderStyle::Default),
+ mFrameState(std::in_place, this),
+- mPIPWindow(false),
+ mMicaBackdrop(false),
+ mLastPaintEndTime(TimeStamp::Now()),
+ mCachedHitTestTime(TimeStamp::Now()),
+ mSizeConstraintsScale(GetDefaultScale().scale) {
+ if (!gInitializedVirtualDesktopManager) {
+@@ -1026,11 +1025,10 @@
+
+ HWND parent =
+ aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
+
+ mIsRTL = aInitData.mRTL;
+- mPIPWindow = aInitData.mPIPWindow;
+ mOpeningAnimationSuppressed = aInitData.mIsAnimationSuppressed;
+ mAlwaysOnTop = aInitData.mAlwaysOnTop;
+ mIsAlert = aInitData.mIsAlert;
+ mResizable = aInitData.mResizable;
+
+@@ -2805,11 +2803,11 @@
+ } else if (sizeMode == nsSizeMode_Maximized) {
+ // We make the entire frame part of the client area. We leave the default
+ // frame sizes for left, right and bottom since Windows will automagically
+ // position the edges "offscreen" for maximized windows.
+ metrics.mOffset.top = metrics.mCaptionHeight;
+- } else if (mPIPWindow &&
++ } else if (mIsPIPWindow &&
+ !StaticPrefs::widget_windows_pip_decorations_enabled()) {
+ metrics.mOffset = metrics.DefaultMargins();
+ } else {
+ metrics.mOffset = NormalWindowNonClientOffset();
+ }
+
diff --git a/src/firefox-patches/147_windows_border_fix_3.patch b/src/firefox-patches/147_windows_border_fix_3.patch
new file mode 100644
index 000000000..8c93f7304
--- /dev/null
+++ b/src/firefox-patches/147_windows_border_fix_3.patch
@@ -0,0 +1,34 @@
+From dd4460727998a53e9fa7372afba2a93a9546cec3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?=
+Date: Fri, 28 Nov 2025 15:06:26 +0000
+Subject: [PATCH] Bug 2002986 - Use IAppWindowTitlebar::ResetToDefault() for
+ non-collapsed titlebar. r=win-reviewers,gstoll
+
+This seems to actually go to the default DWM stuff and is the documented
+way of doing so:
+
+https://learn.microsoft.com/en-us/windows/apps/develop/title-bar#reset-the-title-bar
+
+Differential Revision: https://phabricator.services.mozilla.com/D274413
+---
+ widget/windows/WindowsUIUtils.cpp | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp
+index a5a6c893e7056..abaabfba69dfa 100644
+--- a/widget/windows/WindowsUIUtils.cpp
++++ b/widget/windows/WindowsUIUtils.cpp
+@@ -1394,7 +1394,11 @@ void WindowsUIUtils::SetIsTitlebarCollapsed(HWND aWnd, bool aIsCollapsed) {
+ MOZ_ASSERT_UNREACHABLE("IAppWindowTitleBar could not be acquired");
+ return;
+ }
+- hr = titleBar->put_ExtendsContentIntoTitleBar(aIsCollapsed);
++ if (aIsCollapsed) {
++ hr = titleBar->put_ExtendsContentIntoTitleBar(aIsCollapsed);
++ } else {
++ hr = titleBar->ResetToDefault();
++ }
+ if (FAILED(hr)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ ("Skipping SetIsTitlebarCollapsed() because "
+
\ No newline at end of file
diff --git a/src/firefox-patches/147_windows_border_fix_4.patch b/src/firefox-patches/147_windows_border_fix_4.patch
new file mode 100644
index 000000000..8c93f7304
--- /dev/null
+++ b/src/firefox-patches/147_windows_border_fix_4.patch
@@ -0,0 +1,34 @@
+From dd4460727998a53e9fa7372afba2a93a9546cec3 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?=
+Date: Fri, 28 Nov 2025 15:06:26 +0000
+Subject: [PATCH] Bug 2002986 - Use IAppWindowTitlebar::ResetToDefault() for
+ non-collapsed titlebar. r=win-reviewers,gstoll
+
+This seems to actually go to the default DWM stuff and is the documented
+way of doing so:
+
+https://learn.microsoft.com/en-us/windows/apps/develop/title-bar#reset-the-title-bar
+
+Differential Revision: https://phabricator.services.mozilla.com/D274413
+---
+ widget/windows/WindowsUIUtils.cpp | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/widget/windows/WindowsUIUtils.cpp b/widget/windows/WindowsUIUtils.cpp
+index a5a6c893e7056..abaabfba69dfa 100644
+--- a/widget/windows/WindowsUIUtils.cpp
++++ b/widget/windows/WindowsUIUtils.cpp
+@@ -1394,7 +1394,11 @@ void WindowsUIUtils::SetIsTitlebarCollapsed(HWND aWnd, bool aIsCollapsed) {
+ MOZ_ASSERT_UNREACHABLE("IAppWindowTitleBar could not be acquired");
+ return;
+ }
+- hr = titleBar->put_ExtendsContentIntoTitleBar(aIsCollapsed);
++ if (aIsCollapsed) {
++ hr = titleBar->put_ExtendsContentIntoTitleBar(aIsCollapsed);
++ } else {
++ hr = titleBar->ResetToDefault();
++ }
+ if (FAILED(hr)) {
+ MOZ_LOG(gWindowsLog, LogLevel::Error,
+ ("Skipping SetIsTitlebarCollapsed() because "
+
\ No newline at end of file
diff --git a/src/zen/common/styles/zen-animations.css b/src/zen/common/styles/zen-animations.css
index ae77e9502..1c2bd3c69 100644
--- a/src/zen/common/styles/zen-animations.css
+++ b/src/zen/common/styles/zen-animations.css
@@ -18,13 +18,13 @@
@keyframes zen-theme-picker-dot-animation {
from {
- transform: scale(0.8) translate(-50%, -50%);
+ transform: scale(0.8) translate(-25%, -25%);
}
50% {
- transform: scale(1.2) translate(-50%, -50%);
+ transform: scale(1.2) translate(-25%, -25%);
}
to {
- transform: scale(1) translate(-50%, -50%);
+ transform: scale(1) translate(-25%, -25%);
}
}
diff --git a/src/zen/workspaces/ZenGradientGenerator.mjs b/src/zen/workspaces/ZenGradientGenerator.mjs
index ba3f1edbe..377765efb 100644
--- a/src/zen/workspaces/ZenGradientGenerator.mjs
+++ b/src/zen/workspaces/ZenGradientGenerator.mjs
@@ -45,6 +45,7 @@ const MAX_OPACITY = 0.9;
const MIN_OPACITY = AppConstants.platform === 'macosx' ? 0.25 : 0.35;
const EXPLICIT_LIGHTNESS_TYPE = 'explicit-lightness';
+const EXPLICIT_BLACKWHITE_TYPE = 'explicit-black-white';
export class nsZenThemePicker extends nsZenMultiWindowFeature {
static MAX_DOTS = 3;
@@ -146,6 +147,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
get colorHarmonies() {
return [
{ type: 'complementary', angles: [180] },
+ { type: 'singleAnalogous', angles: [310] },
{ type: 'splitComplementary', angles: [150, 210] },
{ type: 'analogous', angles: [50, 310] },
{ type: 'triadic', angles: [120, 240] },
@@ -198,6 +200,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
}
this.dots = this.dots.slice(0, numDots);
}
+ const type = target.getAttribute('data-type') || EXPLICIT_LIGHTNESS_TYPE;
// Generate new gradient from the single color given
const [x, y] = rawPosition.split(',').map((pos) => parseInt(pos));
let dots = [
@@ -205,18 +208,18 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
ID: 0,
position: { x, y },
isPrimary: true,
- type: EXPLICIT_LIGHTNESS_TYPE,
+ type,
},
];
for (let i = 1; i < numDots; i++) {
dots.push({
ID: i,
position: { x: 0, y: 0 },
- type: EXPLICIT_LIGHTNESS_TYPE,
+ type,
});
}
this.useAlgo = algo;
- this.#currentLightness = lightness;
+ if (lightness !== null) this.#currentLightness = lightness;
dots = this.calculateCompliments(dots, 'update', this.useAlgo);
this.handleColorPositions(dots, true);
this.updateCurrentWorkspace();
@@ -416,10 +419,10 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
calculateInitialPosition([r, g, b]) {
// This function is called before the picker is even rendered, so we hard code the dimensions
// important: If any sort of sizing is changed, make sure changes are reflected here
- const padding = 30;
+ const padding = 0;
const rect = {
- width: 338,
- height: 338,
+ width: 380 + padding * 2,
+ height: 380 + padding * 2,
};
const centerX = rect.width / 2;
const centerY = rect.height / 2;
@@ -437,7 +440,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
const gradient = this.panel.querySelector('.zen-theme-picker-gradient');
const rect = gradient.getBoundingClientRect();
const padding = 30; // each side
- const dotHalfSize = 36 / 2; // half the size of the dot
+ const dotHalfSize = 38 / 2; // half the size of the dot
x += dotHalfSize;
y += dotHalfSize;
rect.width += padding * 2; // Adjust width and height for padding
@@ -452,7 +455,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
angle += 360; // Normalize to [0, 360)
}
const normalizedDistance = 1 - Math.min(distance / radius, 1); // Normalize distance to [0, 1]
- const hue = (angle / 360) * 360; // Normalize angle to [0, 360)
+ let hue = (angle / 360) * 360; // Normalize angle to [0, 360)
let saturation = normalizedDistance * 100; // stays high even in center
if (type !== EXPLICIT_LIGHTNESS_TYPE) {
saturation = 80 + (1 - normalizedDistance) * 20;
@@ -460,7 +463,12 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
// For example, moving the dot outside will have higher lightness, while moving it inside will have lower lightness
this.#currentLightness = Math.round((1 - normalizedDistance) * 100);
}
- const lightness = this.#currentLightness; // Fixed lightness for simplicity
+ let lightness = this.#currentLightness; // Fixed lightness for simplicity
+ if (type === EXPLICIT_BLACKWHITE_TYPE) {
+ // We can only get grayscales from white to black
+ saturation = 0;
+ lightness = Math.round((1 - normalizedDistance) * 100);
+ }
const [r, g, b] = this.hslToRgb(hue / 360, saturation / 100, lightness / 100);
return [
Math.min(255, Math.max(0, r)),
@@ -675,7 +683,13 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
}
if (action === 'remove') {
- return colorHarmonies.find((harmony) => harmony.angles.length === numDots);
+ let harmony = colorHarmonies.find((harmony) => harmony.angles.length === numDots);
+ // If we are coming from 3 analogous dots, we should now go to singleAnalogous if
+ // there are 2 dots left
+ if (harmony.type === 'analogous' && numDots === 1) {
+ harmony = colorHarmonies.find((harmony) => harmony.type === 'singleAnalogous');
+ }
+ return harmony;
}
if (action === 'add') {
return colorHarmonies.find((harmony) => harmony.angles.length + 1 === numDots);
@@ -700,7 +714,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
const dotPad = this.panel.querySelector('.zen-theme-picker-gradient');
const rect = dotPad.getBoundingClientRect();
- const padding = 30;
+ const padding = 0;
let updatedDots = [...dots];
const centerPosition = { x: rect.width / 2, y: rect.height / 2 };
@@ -843,16 +857,30 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
this.handleColorPositions(colorPositions);
this.updateCurrentWorkspace();
return;
+ } else if (target.id === 'PanelUI-zen-gradient-generator-color-toggle-algo') {
+ const applicableHarmonies = this.colorHarmonies.filter(
+ (harmony) => harmony.angles.length + 1 === this.dots.length
+ );
+
+ let currentIndex = applicableHarmonies.findIndex((harmony) => harmony.type === this.useAlgo);
+
+ const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % applicableHarmonies.length;
+ this.useAlgo = applicableHarmonies[nextIndex].type;
+
+ let colorPositions = this.calculateCompliments(this.dots, 'update', this.useAlgo);
+ this.handleColorPositions(colorPositions);
+ this.updateCurrentWorkspace();
+ return;
}
if (event.button !== 0 || this.dragging || this.recentlyDragged) return;
const gradient = this.panel.querySelector('.zen-theme-picker-gradient');
const rect = gradient.getBoundingClientRect();
- const padding = 30;
+ const padding = 0;
- const centerX = rect.left + rect.width / 2;
- const centerY = rect.top + rect.height / 2;
+ const centerX = rect.left + rect.width / 2 - padding;
+ const centerY = rect.top + rect.height / 2 - padding;
const radius = (rect.width - padding) / 2;
let pixelX = event.clientX;
let pixelY = event.clientY;
@@ -982,7 +1010,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
if (this.dragging) {
event.preventDefault();
const rect = this.panel.querySelector('.zen-theme-picker-gradient').getBoundingClientRect();
- const padding = 30; // each side
+ const padding = 0; // each side
// do NOT let the ball be draged outside of an imaginary circle. You can drag it anywhere inside the circle
// if the distance between the center of the circle and the dragged ball is bigger than the radius, then the ball
// should be placed on the edge of the circle. If it's inside the circle, then the ball just follows the mouse
@@ -1164,7 +1192,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
if (!forToolbar) {
return [
`linear-gradient(${rotation}deg, ${this.getSingleRGBColor(themedColors[1], forToolbar)} 0%, transparent 100%)`,
- `linear-gradient(${rotation + 180}deg, ${this.getSingleRGBColor(themedColors[0], forToolbar)} 0%, transparent 80%)`,
+ `linear-gradient(${rotation + 180}deg, ${this.getSingleRGBColor(themedColors[0], forToolbar)} 0%, transparent 100%)`,
]
.reverse()
.join(', ');
@@ -1175,8 +1203,8 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
let color2 = this.getSingleRGBColor(themedColors[0], forToolbar);
let color3 = this.getSingleRGBColor(themedColors[1], forToolbar);
return [
+ `radial-gradient(circle at 0% 0%, ${color2} -10%, transparent 100%)`,
`linear-gradient(to top, ${color1} -50%, transparent 125%)`,
- `radial-gradient(circle at 0% 0%, ${color2} 10%, transparent 80%)`,
`radial-gradient(circle at 100% -100%, ${color3} -100%, transparent 400%)`,
].join(', ');
}
@@ -1389,6 +1417,9 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
workspaceTheme.gradientColors.length === 0 ||
(button.id === 'PanelUI-zen-gradient-generator-color-add'
? workspaceTheme.gradientColors.length >= nsZenThemePicker.MAX_DOTS
+ : false) ||
+ (button.id === 'PanelUI-zen-gradient-generator-color-toggle-algo'
+ ? workspaceTheme.gradientColors.length < 2
: false);
}
const clickToAdd = browser.document.getElementById(
diff --git a/src/zen/workspaces/zen-gradient-generator.css b/src/zen/workspaces/zen-gradient-generator.css
index 24004a1d5..3e4ca8911 100644
--- a/src/zen/workspaces/zen-gradient-generator.css
+++ b/src/zen/workspaces/zen-gradient-generator.css
@@ -5,7 +5,7 @@
*/
#PanelUI-zen-gradient-generator {
- --panel-width: 360px;
+ --panel-width: 380px;
--panel-padding: 10px;
min-width: var(--panel-width);
}
@@ -102,15 +102,16 @@
overflow: auto;
scrollbar-width: none;
scroll-behavior: smooth;
- mask-image: linear-gradient(to right, transparent 0%, black 2.5%, black 97.5%, transparent 100%);
& > hbox {
- justify-content: space-around;
+ justify-content: space-between;
min-width: 100%;
+ padding: 0 1px;
& > box {
- width: 22px;
- height: 22px;
+ width: 26px;
+ height: 26px;
+ box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
border-radius: 50%;
cursor: pointer;
position: relative;
@@ -251,17 +252,17 @@
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,
+ light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.1)) 0.5px,
transparent 0
);
background-position: -23px -23px;
background-size: 6px 6px;
& .zen-theme-picker-dot {
- position: absolute;
+ position: fixed;
z-index: 2;
- width: 18px;
- height: 18px;
+ width: 20px;
+ height: 20px;
border-radius: 50%;
background: var(--zen-theme-picker-dot-color);
@media (-prefers-color-scheme: dark) {
@@ -270,25 +271,25 @@
cursor: pointer;
border: 3px solid #ffffff;
animation: zen-theme-picker-dot-animation 0.5s;
- transform: translate(-50%, -50%);
+ transform: translate(-25%, -25%);
pointer-events: none;
transform-origin: center center;
&:first-of-type {
- width: 36px;
- height: 36px;
+ width: 38px;
+ height: 38px;
border-width: 4px;
z-index: 2;
pointer-events: all;
transition: transform 0.2s;
z-index: 999;
&:hover {
- transform: scale(1.05) translate(-50%, -50%);
+ transform: scale(1.05) translate(-25%, -25%);
}
}
&[dragging='true'] {
- transform: scale(1.2) translate(-50%, -50%) !important;
+ transform: scale(1.2) translate(-25%, -25%) !important;
}
}
}
@@ -329,10 +330,10 @@
min-width: fit-content !important;
transition: background 0.2s;
appearance: none;
- max-height: 26px;
- max-width: 26px;
- min-height: 26px;
- min-width: 26px !important;
+ max-height: 30px;
+ max-width: 30px;
+ min-height: 30px;
+ min-width: 30px !important;
color: light-dark(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.9));
& .button-box {