diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc5ec32 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# Firefox Native Controls + +Simple source code patches that re-enable native controls on Firefox. Requires modifying Firefox installation files, but you don't need to use a custom build of Firefox altogether. + +It is still recommended that you use ESR and disable automatic updates. Unfortunately, updating is just a sacrifice you have to make to theming. However, I may write a custom updater with this mod in consideration, eventually. + +The only potential caveat: Widevine support. I don't know if this is controlled by `xul.dll` or not, but there is a file beside it called `xul.dll.sig` in official Firefox builds that has information regarding a Widevine certificate (and is otherwise seemingly completely unused). If you are based, this will not matter to you. + +At the moment, it is required that you still manually disable non-native controls in order to use this. + +## Change documentation + +- [Scrollbars](docs/scrollbars.md) + +## Installing prebuilt versions + +In the releases section on the right, there are pre-compiled versions of `xul.dll` for Firefox ESR. + +Check your Firefox version and download the right version for the version that you have, and then replace `xul.dll` in the installation path with the file that you downloaded. + +If you don't trust the versions I built myself, then you can mix the provided patches with the Firefox source code (see the following section). The prebuilt versions are just provided for convenience, because the Firefox source code is pretty big and takes a while to compile. + +Step-by-step: + +1. Download the release on the right side of the page. If your version is unsupported, then you will have to build the changes from source. +2. Replace `C:\Program Files\Mozilla Firefox\xul.dll` with the downloaded `xul.dll` file. +3. Make sure that `widget.non-native-theme.enabled` is false in `about:config`. +4. Close all Firefox processes in Task Manager and restart the browser. + +## Building from source + +[Clone Firefox for yourself](https://firefox-source-docs.mozilla.org/setup/index.html), and then mix in the patches as needed. Note that when you clone, it will put in the `mozilla-central` branch. You probably don't want this as these correspond to the latest Nightly builds, which will mean that the produced `xul.dll` binary will likely be incompatible with the current build environment. + +After cloning, navigate to `.hg/hgrc` in the source directory and change the default path to the branch of your preferred build. For example, I changed it to `https://hg.mozilla.org/releases/mozilla-esr115` in order to access specific tags for the ESR 115 release. You can usually then find specific tags relating to subversions here: https://hg.mozilla.org/releases/mozilla-esr115/tags ("tags" tab in the Mercurial web viewer). + +Since Firefox uses Mercurial for version control, it may be a little unfamiliar to you. You "update", rather than "checkout", different branches. So after you make those changes, I think you just do something like `hg pull -u -r FIREFOX_115_3_1esr_RELEASE` to switch to the specific branch. `pull` makes it get the specific version from remote, and `-u` means to update. + +Run `./mach build` to build Firefox from source and then `./mach run` to test it. Here is a [Windhawk](//windhawk.net) mod that I use to quickly test classic theme for Firefox only (where I make a copy of `firefox.exe` named `firefoxa.exe`): + +```cpp +// ==WindhawkMod== +// @id firefoxa-classic-theme-test +// @name [Testing] firefox classic theme +// @description The best mod ever that does great things +// @version 0.1 +// @author ephemeralViolette +// @include firefoxa.exe +// @compilerOptions -luxtheme +// ==/WindhawkMod== + +#include + +BOOL Wh_ModInit() +{ + SetThemeAppProperties(0); + return TRUE; +} +``` + +Also, currently there is a bug you need to be aware of. After disabling `widget.non-native-theme.enabled`, you must restart Firefox completely, or else the scrollbars will simply not render. I do not know why this happens exactly (maybe the native theme isn't loaded upon disabling the property? I haven't looked at other controls). + +After building, the `xul.dll` file can be found in somewhere like `obj-x86_64-pc-windows-msvc/dist/bin` in the source code root, which looks just like `C:\Program Files\Mozilla Firefox`. + +### Distribution method + +Also because Firefox uses Mercurial rather than Git, I found it would be more trouble than it's worth to attempt to post a modified codebase onto GitHub. I initially thought to fork [mozilla/gecko-dev](//github.com/mozilla/gecko-dev), but the commit identifiers for this repository do not at all align with their Mercurial revision identifiers, so it is less than worthless. Also, I found I couldn't even find certain tags which were useful to access in the Mercurial version. + +As a result, I just `hg export` the patches I make, which makes them pretty easy to bring back into the codebase later. + +## Will you do other controls? + +Probably. My main focus with the initial release was restoring native scrollbars. I didn't see other controls as important, since their native appearances could be better replicated with CSS. \ No newline at end of file diff --git a/docs/scrollbars.md b/docs/scrollbars.md new file mode 100644 index 0000000..cdbbd5e --- /dev/null +++ b/docs/scrollbars.md @@ -0,0 +1,92 @@ +# How I ported native scrollbars to modern Firefox + +"Native" scrollbars (really, they were never native, just themed to look native) were disabled in Firefox 97 and residual code was removed in Firefox 98. This means that the last version of Firefox that they were accessible in was Firefox 96, which was released a pretty long time ago by now. + +While it would be ideal to theme the scrollbars natively (i.e. with CSS), there are a few issues with this plan that make it somewhat unviable: + +- Installation would require a userChrome.js script (i.e. an autoconfig browser chrome script loader) in order to modify the scrollbar CSS at all. This is because the scrollbars exist in no-mans-land, inaccessible by even the browser chrome inspector and userChrome.css. +- CSS properties on the scrollbar elements, being CSS, are very limited. I could not figure out a way to even adequately replicate the drawing of the native Windows scrollbars from XP to 7, so I gave up rather fast. Since the scrollbars are XUL custom elements, you can't even use pseudoelements on them for glyphs. +- Sites would randomly override the custom CSS if they used any custom scrollbar styles, which I also found very undesirable. I could not figure out how to fix that. + +It took me about 4 days straight to figure my way around the Firefox codebase and solve some pretty weird, pretty obvious bugs with this. I am currently maining my `xul.dll` build with native scrollbars and I think it works better than I expected. + +My original plan was to do this using Windhawk, which would have been a nicer solution, if only I weren't completely misguided in the beginning, and if Windhawk could actually hook Firefox's symbols. Even though Mozilla has a symbol server for all release builds of Firefox, the symbols for `xul.dll` are so large (~1.19 GB) that Windhawk seemingly fails to load it. I assume it infinitely looped or whatever. + +Now, I think a source code modification is the cleanest and best approach to patching this, in any case. Even if it's only a few functions that needed to be patched realistically, it would have still gotten pretty unmanagable to be hooking everything from foreign code needlessly. + +## Don't get lost + +ScrollbarDrawing (`widget/`) and its related classes are completely pointless to the implementation of native-styled scrollbars. They are +only responsible for drawing routines of the non-native ones seen in regular use. + +Scrollbar drawing proper is done by `layout/generic/nsGfxScrollFrame.cpp`, with backing from `layout/xul/nsScrollbarFrame.cpp`. Themes +are reported by the application theme, which is `nsNativeThemeWin` when non-native controls are disabled (presumedly). + +The primary file you will be editing is `widget/win/nsNativeThemeWin.cpp`. + +## Important change history: + +1. Sometime before the scrollbar changes, rendering code was restructured, such that +nsBasicNativeTheme, previously platform-specific, became `Theme` (`widgets/Theme.cpp`). +This doesn't really matter except in tracking change history, which may be quite useful +to do. (commit: https://github.com/mozilla/gecko-dev/commit/0468798e53b23f65d6538d985c0d3fef08cd5816) + +2. As of ESR 115, scrollbar sizing code was greatly simplified. Previously, there was a `ScrollbarSizes` +struct which was used internally. This allowed for scrollbars to have independent horizontal and +vertical sizes, albeit this was never used. To account for this change, anything extending from +`nsITheme` (like `nsNativeThemeWin`) must change: + + ``` + virtual ScrollbarSizes GetScrollbarSizes(nsPresContext *, StyleScrollbarWidth, Overlay); + ``` + + to: + + ``` + virtual LayoutDeviceIntCoord GetScrollbarSize(const nsPresContext *, StyleScrollbarWidth, Overlay); + ``` + + Do note that the `const` before the `nsPresContext` argument is also very important. + + (commit: https://github.com/mozilla/gecko-dev/commit/604d8268b2e83340b8fce766bed3a0302c65cac4) + + **This was important to me in an early iteration, however, I figured out you don't need this function in the first place.** I simply use `GetMinimumWidgetSize` and `ClassicGetMinimumWidgetSize` since the initial public release of this. + +3. Similarly, XUL layout was deprecated for the scrollbars by ESR 115. You will need to modify how +scrollbar size calculation works in order for it to appear at all (in debug builds, there will be an assertion +fail). I originally did this by modifying `ScrollbarMinSize` in `layout/xul/nsScrollbarFrame.cpp`, but later changed it to a simpler approach by modifying +`GetMinimumWidgetSize` in `widget/win/nsNativeThemeWin.cpp`, which meant modifying one fewer file. (commit: https://github.com/mozilla/gecko-dev/commit/38b10eafda0c7d5959deab3b7f212eddaba5cc57) + +4. At some point, `EventState` was merged into `ElementState`. You can just replace all "`EventState`" with +"`ElementState`" and "`NS_EVENT_STATE_`" with "`ElementState::`", if this is applicable. + +5. At some point (I don't know when), `nsNativeThemeWin::GetWidgetBorder` broke with scrollbar thumbs. This may need to be fixed, which can be done by redirecting the call to `nsNativeThemeWin::ClassicGetWidgetBorder`. + +In the provided diff file, I basically already made all of these necessary changes as of ESR 115. + +## General instructions + +Here are the general steps for restoring native scrollbars. This is the approach I used to restore them to ESR 115, +and these instructions may be very useful for applying the patch to previous or later versions of Firefox. + + +1. Generally undo these commits: + - ["Always draw scrollbars using the non-native theme on Windows."](https://github.com/mozilla/gecko-dev/commit/36215bf43067b04058cc397a12ef78b4f864d2a6) + - ["Remove dead windows scrollbar drawing code."](https://github.com/mozilla/gecko-dev/commit/98c3acf4c210d194f0e48f9d6e87813a002c390b) + + Note that there have been a few changes in types and code organisation since then, so you will need to make + some modifications to the code. One such example includes changing `GetScrollbarSizes` to the simpler `GetScrollbarSize` + (described above). + +2. Fix up layout code if necessary (important change history #2 and #3). + +3. In nsNativeThemeWin, make `GetWidgetBorder` call `ClassicGetWidgetBorder` for thumbs: + - `StyleAppearance::ScrollbarthumbVertical` + - `StyleAppearance::ScrollbarthumbHorizontal` + + This is necessary in order for the horizontal thumbs to appear at all with themes for some versions of Firefox. + +## Files I changed + +- `widget/windows/nsNativeThemeWin.cpp` (most things) +- `widget/windows/nsUXThemeData.cpp` & `.h` (old UxTheme theme classes) \ No newline at end of file diff --git a/native-controls-esr-115.3.1.diff b/native-controls-esr-115.3.1.diff new file mode 100644 index 0000000..1a2ef87 --- /dev/null +++ b/native-controls-esr-115.3.1.diff @@ -0,0 +1,428 @@ +# HG changeset patch +# User daylin +# Date 1697093022 25200 +# Wed Oct 11 23:43:42 2023 -0700 +# Branch FIREFOX_ESR_115_3_X_RELBRANCH +# Node ID 232d6e70ef3246869f0c005227cec4f74eabcfb7 +# Parent 749617c4473c65f8aebb2e3254777ae99dc17cce +Native scrollbars ESR 115.3.1 + +diff --git a/widget/windows/nsNativeThemeWin.cpp b/widget/windows/nsNativeThemeWin.cpp +--- a/widget/windows/nsNativeThemeWin.cpp ++++ b/widget/windows/nsNativeThemeWin.cpp +@@ -40,6 +40,7 @@ + #include "nsWindow.h" + #include "prinrval.h" + #include "WinUtils.h" ++#include "ScrollbarDrawingWin.h" + + using namespace mozilla; + using namespace mozilla::gfx; +@@ -68,8 +69,11 @@ nsNativeThemeWin::~nsNativeThemeWin() { + auto nsNativeThemeWin::IsWidgetNonNative(nsIFrame* aFrame, + StyleAppearance aAppearance) + -> NonNative { +- if (IsWidgetScrollbarPart(aAppearance) || +- aAppearance == StyleAppearance::FocusOutline) { ++ if (IsWidgetScrollbarPart(aAppearance)) { ++ return NonNative::No; ++ } ++ ++ if (aAppearance == StyleAppearance::FocusOutline) { + return NonNative::Always; + } + +@@ -718,6 +722,16 @@ mozilla::Maybe nsNativeT + case StyleAppearance::Tabpanel: + case StyleAppearance::Tabpanels: + return Some(eUXTab); ++ case StyleAppearance::ScrollbarVertical: ++ case StyleAppearance::ScrollbarHorizontal: ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: ++ case StyleAppearance::ScrollbarthumbVertical: ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ case StyleAppearance::Scrollcorner: ++ return Some(eUXScrollbar); + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + return Some(eUXTrackbar); +@@ -967,6 +981,66 @@ nsresult nsNativeThemeWin::GetThemePartA + aState = TS_NORMAL; + return NS_OK; + } ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: { ++ aPart = SP_BUTTON; ++ aState = (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) * 4; ++ ElementState eventState = GetContentState(aFrame, aAppearance); ++ if (!aFrame) ++ aState += TS_NORMAL; ++ else if (eventState.HasState(ElementState::DISABLED)) ++ aState += TS_DISABLED; ++ else { ++ nsIFrame* parent = aFrame->GetParent(); ++ ElementState parentState = GetContentState( ++ parent, parent->StyleDisplay()->EffectiveAppearance()); ++ if (eventState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE)) ++ aState += TS_ACTIVE; ++ else if (eventState.HasState(ElementState::HOVER)) ++ aState += TS_HOVER; ++ else if (parentState.HasState(ElementState::HOVER)) ++ aState = ++ (int(aAppearance) - int(StyleAppearance::ScrollbarbuttonUp)) + ++ SP_BUTTON_IMPLICIT_HOVER_BASE; ++ else ++ aState += TS_NORMAL; ++ } ++ return NS_OK; ++ } ++ case StyleAppearance::ScrollbarHorizontal: ++ case StyleAppearance::ScrollbarVertical: { ++ aPart = (aAppearance == StyleAppearance::ScrollbarHorizontal) ++ ? SP_TRACKSTARTHOR ++ : SP_TRACKSTARTVERT; ++ aState = TS_NORMAL; ++ return NS_OK; ++ } ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ case StyleAppearance::ScrollbarthumbVertical: { ++ aPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal) ++ ? SP_THUMBHOR ++ : SP_THUMBVERT; ++ ElementState eventState = GetContentState(aFrame, aAppearance); ++ if (!aFrame) ++ aState = TS_NORMAL; ++ else if (eventState.HasState(ElementState::DISABLED)) ++ aState = TS_DISABLED; ++ else { ++ if (eventState.HasState( ++ ElementState::ACTIVE)) // Hover is not also a requirement for ++ // the thumb, since the drag is not ++ // canceled when you move outside the ++ // thumb. ++ aState = TS_ACTIVE; ++ else if (eventState.HasState(ElementState::HOVER)) ++ aState = TS_HOVER; ++ else ++ aState = TS_NORMAL; ++ } ++ return NS_OK; ++ } + case StyleAppearance::Range: { + if (IsRangeHorizontal(aFrame)) { + aPart = TKP_TRACK; +@@ -1004,6 +1078,11 @@ nsresult nsNativeThemeWin::GetThemePartA + } + return NS_OK; + } ++ case StyleAppearance::Scrollcorner: { ++ aState = 0; ++ aPart = RP_BACKGROUND; ++ return NS_OK; ++ } + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: { + aPart = (aAppearance == StyleAppearance::SpinnerUpbutton) ? SPNP_UP +@@ -1696,6 +1775,26 @@ RENDER_AGAIN: + widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT; + DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP, + nullptr); ++ } else if (aAppearance == StyleAppearance::ScrollbarthumbHorizontal || ++ aAppearance == StyleAppearance::ScrollbarthumbVertical) { ++ // Draw the decorative gripper for the scrollbar thumb button, if it fits ++ ++ SIZE gripSize; ++ MARGINS thumbMgns; ++ int gripPart = (aAppearance == StyleAppearance::ScrollbarthumbHorizontal) ++ ? SP_GRIPPERHOR ++ : SP_GRIPPERVERT; ++ ++ if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE, ++ &gripSize) == S_OK && ++ GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr, ++ &thumbMgns) == S_OK && ++ gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <= ++ widgetRect.right - widgetRect.left && ++ gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <= ++ widgetRect.bottom - widgetRect.top) { ++ DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect); ++ } + } + + nativeDrawing.EndNativeDrawing(); +@@ -1745,7 +1844,11 @@ LayoutDeviceIntMargin nsNativeThemeWin:: + if (!themeClass.isNothing()) { + theme = nsUXThemeData::GetTheme(themeClass.value()); + } +- if (!theme) { ++ ++ // Classic scrollbar thumbs require classic borders. The theme procedure will ++ // break horizontal scrollbar thumbs otherwise. ++ if (aAppearance == StyleAppearance::ScrollbarthumbVertical || ++ aAppearance == StyleAppearance::ScrollbarthumbHorizontal || !theme) { + result = ClassicGetWidgetBorder(aContext, aFrame, aAppearance); + ScaleForFrameDPI(&result, aFrame); + return result; +@@ -1757,6 +1860,9 @@ LayoutDeviceIntMargin nsNativeThemeWin:: + aAppearance == StyleAppearance::MozWinCommunicationsToolbox || + aAppearance == StyleAppearance::MozWinBrowsertabbarToolbox || + aAppearance == StyleAppearance::Tabpanel || ++ aAppearance == StyleAppearance::ScrollbarHorizontal || ++ aAppearance == StyleAppearance::ScrollbarVertical || ++ aAppearance == StyleAppearance::Scrollcorner || + aAppearance == StyleAppearance::Menuitem || + aAppearance == StyleAppearance::Checkmenuitem || + aAppearance == StyleAppearance::Radiomenuitem || +@@ -2051,6 +2157,14 @@ LayoutDeviceIntSize nsNativeThemeWin::Ge + // Windows appears to always use metrics when drawing standard scrollbars) + THEMESIZE sizeReq = TS_TRUE; // Best-fit size + switch (aAppearance) { ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ case StyleAppearance::ScrollbarthumbVertical: ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: ++ case StyleAppearance::ScrollbarHorizontal: ++ case StyleAppearance::ScrollbarVertical: + case StyleAppearance::MozMenulistArrowButton: { + auto result = ClassicGetMinimumWidgetSize(aFrame, aAppearance); + ScaleForFrameDPI(&result, aFrame); +@@ -2095,6 +2209,17 @@ LayoutDeviceIntSize nsNativeThemeWin::Ge + return result; + } + ++ case StyleAppearance::Scrollcorner: { ++ if (nsLookAndFeel::GetInt(nsLookAndFeel::IntID::UseOverlayScrollbars) != ++ 0) { ++ LayoutDeviceIntSize result(::GetSystemMetrics(SM_CXHSCROLL), ++ ::GetSystemMetrics(SM_CYVSCROLL)); ++ ScaleForFrameDPI(&result, aFrame); ++ return result; ++ } ++ break; ++ } ++ + case StyleAppearance::Separator: { + // that's 2px left margin, 2px right margin and 2px separator + // (the margin is drawn as part of the separator, though) +@@ -2400,6 +2525,15 @@ bool nsNativeThemeWin::ClassicThemeSuppo + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: + case StyleAppearance::Groupbox: ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: ++ case StyleAppearance::ScrollbarthumbVertical: ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ case StyleAppearance::ScrollbarVertical: ++ case StyleAppearance::ScrollbarHorizontal: ++ case StyleAppearance::Scrollcorner: + case StyleAppearance::Menulist: + case StyleAppearance::MenulistButton: + case StyleAppearance::MozMenulistArrowButton: +@@ -2526,6 +2660,25 @@ LayoutDeviceIntSize nsNativeThemeWin::Cl + result.width = ::GetSystemMetrics(SM_CXVSCROLL); + result.height = 8; // No good metrics available for this + break; ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ result.width = ::GetSystemMetrics(SM_CXVSCROLL); ++ result.height = ::GetSystemMetrics(SM_CYVSCROLL); ++ break; ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: ++ // For scrollbar-width:thin, we don't display the buttons. ++ if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) { ++ result.width = ::GetSystemMetrics(SM_CXHSCROLL); ++ result.height = ::GetSystemMetrics(SM_CYHSCROLL); ++ } ++ break; ++ case StyleAppearance::ScrollbarVertical: ++ case StyleAppearance::ScrollbarHorizontal: ++ // Sizing code needed after removal of XUL layout (around ESR 115) ++ result.width = ::GetSystemMetrics(SM_CYHSCROLL); ++ result.height = ::GetSystemMetrics(SM_CYHSCROLL); ++ break; + case StyleAppearance::RangeThumb: { + if (IsRangeHorizontal(aFrame)) { + result.width = 12; +@@ -2536,6 +2689,35 @@ LayoutDeviceIntSize nsNativeThemeWin::Cl + } + break; + } ++ case StyleAppearance::ScrollbarthumbVertical: ++ result.width = ::GetSystemMetrics(SM_CXVSCROLL); ++ result.height = ::GetSystemMetrics(SM_CYVTHUMB); ++ // Without theming, divide the thumb size by two in order to look more ++ // native ++ if (!GetTheme(aAppearance)) { ++ result.height >>= 1; ++ } ++ // If scrollbar-width is thin, divide the thickness by two to make ++ // it look more compact. ++ if (ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) { ++ result.width >>= 1; ++ } ++ break; ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ result.width = ::GetSystemMetrics(SM_CXHTHUMB); ++ result.height = ::GetSystemMetrics(SM_CYHSCROLL); ++ // Without theming, divide the thumb size by two in order to look more ++ // native ++ if (TRUE || !GetTheme(aAppearance)) { ++ result.width >>= 1; ++ } ++ // If scrollbar-width is thin, divide the thickness by two to make ++ // it look more compact. ++ if (ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) { ++ result.height >>= 1; ++ } ++ ++ break; + case StyleAppearance::MozMenulistArrowButton: + result.width = ::GetSystemMetrics(SM_CXVSCROLL); + break; +@@ -2732,6 +2914,11 @@ nsresult nsNativeThemeWin::ClassicGetThe + case StyleAppearance::MenulistButton: + case StyleAppearance::Range: + case StyleAppearance::RangeThumb: ++ case StyleAppearance::ScrollbarthumbVertical: ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ case StyleAppearance::ScrollbarVertical: ++ case StyleAppearance::ScrollbarHorizontal: ++ case StyleAppearance::Scrollcorner: + case StyleAppearance::Progresschunk: + case StyleAppearance::ProgressBar: + case StyleAppearance::Tab: +@@ -2777,6 +2964,39 @@ nsresult nsNativeThemeWin::ClassicGetThe + + return NS_OK; + } ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: { ++ ElementState contentState = GetContentState(aFrame, aAppearance); ++ ++ aPart = DFC_SCROLL; ++ switch (aAppearance) { ++ case StyleAppearance::ScrollbarbuttonUp: ++ aState = DFCS_SCROLLUP; ++ break; ++ case StyleAppearance::ScrollbarbuttonDown: ++ aState = DFCS_SCROLLDOWN; ++ break; ++ case StyleAppearance::ScrollbarbuttonLeft: ++ aState = DFCS_SCROLLLEFT; ++ break; ++ case StyleAppearance::ScrollbarbuttonRight: ++ aState = DFCS_SCROLLRIGHT; ++ break; ++ default: ++ break; ++ } ++ ++ if (contentState.HasState(ElementState::DISABLED)) { ++ aState |= DFCS_INACTIVE; ++ } else if (contentState.HasAllStates(ElementState::HOVER | ++ ElementState::ACTIVE)) { ++ aState |= DFCS_PUSHED | DFCS_FLAT; ++ } ++ ++ return NS_OK; ++ } + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: { + ElementState contentState = GetContentState(aFrame, aAppearance); +@@ -3055,6 +3275,10 @@ RENDER_AGAIN: + // Draw controls supported by DrawFrameControl + case StyleAppearance::Checkbox: + case StyleAppearance::Radio: ++ case StyleAppearance::ScrollbarbuttonUp: ++ case StyleAppearance::ScrollbarbuttonDown: ++ case StyleAppearance::ScrollbarbuttonLeft: ++ case StyleAppearance::ScrollbarbuttonRight: + case StyleAppearance::SpinnerUpbutton: + case StyleAppearance::SpinnerDownbutton: + case StyleAppearance::MozMenulistArrowButton: { +@@ -3110,6 +3334,12 @@ RENDER_AGAIN: + ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1)); + break; + } ++ // Draw scrollbar thumb ++ case StyleAppearance::ScrollbarthumbVertical: ++ case StyleAppearance::ScrollbarthumbHorizontal: ++ ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE); ++ ++ break; + case StyleAppearance::RangeThumb: { + ElementState elementState = GetContentState(aFrame, aAppearance); + +@@ -3147,6 +3377,37 @@ RENDER_AGAIN: + + break; + } ++ // Draw scrollbar track background ++ case StyleAppearance::ScrollbarVertical: ++ case StyleAppearance::ScrollbarHorizontal: { ++ // Windows fills in the scrollbar track differently ++ // depending on whether these are equal ++ DWORD color3D, colorScrollbar, colorWindow; ++ ++ color3D = ::GetSysColor(COLOR_3DFACE); ++ colorWindow = ::GetSysColor(COLOR_WINDOW); ++ colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR); ++ ++ if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar)) ++ // Use solid brush ++ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1)); ++ else { ++ DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE, ++ (HBRUSH)COLOR_SCROLLBAR + 1); ++ } ++ // XXX should invert the part of the track being clicked here ++ // but the track is never :active ++ ++ break; ++ } ++ case StyleAppearance::Scrollcorner: { ++ ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1)); ++ ++ // Are Mozilla fucking retarded? They added this in 2018 ++ // (https://github.com/mozilla/gecko-dev/blob/7038d5f94456dcb558f7c7f6fe66d913070001c5/widget/windows/nsNativeThemeWin.cpp#L3793-L3795) ++ // and never fixed this fallthrough. ++ break; ++ } + case StyleAppearance::Progresschunk: { + nsIFrame* stateFrame = aFrame->GetParent(); + ElementState elementState = GetContentState(stateFrame, aAppearance); +diff --git a/widget/windows/nsUXThemeData.cpp b/widget/windows/nsUXThemeData.cpp +--- a/widget/windows/nsUXThemeData.cpp ++++ b/widget/windows/nsUXThemeData.cpp +@@ -94,6 +94,8 @@ const wchar_t* nsUXThemeData::GetClassNa + return L"Communications::Rebar"; + case eUXBrowserTabBarRebar: + return L"BrowserTabBar::Rebar"; ++ case eUXScrollbar: ++ return L"Scrollbar"; + case eUXToolbar: + return L"Toolbar"; + case eUXMediaToolbar: +diff --git a/widget/windows/nsUXThemeData.h b/widget/windows/nsUXThemeData.h +--- a/widget/windows/nsUXThemeData.h ++++ b/widget/windows/nsUXThemeData.h +@@ -28,6 +28,7 @@ enum nsUXThemeClass { + eUXCommunicationsToolbar, + eUXProgress, + eUXTab, ++ eUXScrollbar, + eUXTrackbar, + eUXSpin, + eUXCombobox,