From 3618e5ed501243bf7f480192b862f4dfe116ca7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 22 Mar 2025 03:24:00 +0000 Subject: [PATCH] Bug 1955591 - Make LookAndFeel APIs safe to call off the main thread. r=jfkthame Make IntID::AlertNotificationOrigin not terribly slow on windows, since that's the only thing we had a carveout for avoid querying on startup: * Modern windows doesn't let you move the taskbar to a separate side. * Even with that, on a multi-monitor setup there are multiple taskbars. * More importantly, even with that, now we have native notifications by default, so this is less important (this is only useful for the non-native alerts). Avoid GTK CRITICALs and crashes when calling into LookAndFeel from XPCShell, which now happens. Differential Revision: https://phabricator.services.mozilla.com/D242566 --- dom/base/nsContentUtils.cpp | 3 + dom/ipc/ContentChild.cpp | 19 +-- gfx/thebes/gfxPlatformGtk.cpp | 10 +- layout/style/GeckoBindings.cpp | 6 - layout/style/ServoStyleSet.cpp | 2 - widget/LookAndFeel.h | 12 +- widget/RemoteLookAndFeel.cpp | 53 ++---- widget/Theme.cpp | 1 - widget/gtk/nsLookAndFeel.cpp | 76 +++++---- widget/nsXPLookAndFeel.cpp | 284 +++++++++++++------------------ widget/nsXPLookAndFeel.h | 32 ++-- widget/windows/nsLookAndFeel.cpp | 37 +--- 12 files changed, 210 insertions(+), 325 deletions(-) diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index a30b9a639727..940ed6a51b20 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -1044,6 +1044,9 @@ nsresult nsContentUtils::Init() { RunOnShutdown( [&] { glean_pings::UseCounters.Submit("app_shutdown_confirmed"_ns); }, ShutdownPhase::AppShutdownConfirmed); + + // On child process, this is initialized in ContentChild. + LookAndFeel::EnsureInit(); } RefPtr uio = new UserInteractionObserver(); diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index f6f2ff577349..a06429259401 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -666,6 +666,7 @@ mozilla::ipc::IPCResult ContentChild::RecvSetXPCOMProcessAttributes( gfx::gfxVars::SetValuesForInitialize(aXPCOMInit.gfxNonDefaultVarUpdates()); PerfStats::SetCollectionMask(aXPCOMInit.perfStatsMask()); + LookAndFeel::EnsureInit(); InitSharedUASheets(std::move(aSharedUASheetHandle), aSharedUASheetAddress); InitXPCOM(std::move(aXPCOMInit), aInitialData, aIsReadyForBackgroundProcessing); @@ -680,16 +681,6 @@ mozilla::ipc::IPCResult ContentChild::RecvSetXPCOMProcessAttributes( return IPC_OK(); } -class nsGtkNativeInitRunnable : public Runnable { - public: - nsGtkNativeInitRunnable() : Runnable("nsGtkNativeInitRunnable") {} - - NS_IMETHOD Run() override { - LookAndFeel::NativeInit(); - return NS_OK; - } -}; - void ContentChild::Init(mozilla::ipc::UntypedEndpoint&& aEndpoint, const char* aParentBuildID, bool aIsForBrowser) { #ifdef MOZ_WIDGET_GTK @@ -1351,14 +1342,6 @@ void ContentChild::InitXPCOM( XPCOMInitData&& aXPCOMInit, const mozilla::dom::ipc::StructuredCloneData& aInitialData, bool aIsReadyForBackgroundProcessing) { -#ifdef MOZ_WIDGET_GTK - // LookAndFeel::NativeInit takes a long time to run on Linux, here we schedule - // it as soon as possible after BackgroundChild::Startup to give - // it chance to run ahead of ConstructBrowser - nsCOMPtr event = new nsGtkNativeInitRunnable(); - NS_DispatchToMainThreadQueue(event.forget(), EventQueuePriority::Idle); -#endif - #if defined(XP_WIN) // DLL services untrusted modules processing depends on // BackgroundChild::Startup having been called diff --git a/gfx/thebes/gfxPlatformGtk.cpp b/gfx/thebes/gfxPlatformGtk.cpp index 256a140aac42..07ac24530dfa 100644 --- a/gfx/thebes/gfxPlatformGtk.cpp +++ b/gfx/thebes/gfxPlatformGtk.cpp @@ -460,10 +460,12 @@ int32_t gfxPlatformGtk::GetFontScaleDPI() { if (MOZ_LIKELY(sDPI != 0)) { return sDPI; } - GdkScreen* screen = gdk_screen_get_default(); - // Ensure settings in config files are processed. - gtk_settings_get_for_screen(screen); - int32_t dpi = int32_t(round(gdk_screen_get_resolution(screen))); + int32_t dpi = 0; + if (GdkScreen* screen = gdk_screen_get_default()) { + // Ensure settings in config files are processed. + gtk_settings_get_for_screen(screen); + dpi = int32_t(round(gdk_screen_get_resolution(screen))); + } if (dpi <= 0) { // Fall back to something reasonable dpi = 96; diff --git a/layout/style/GeckoBindings.cpp b/layout/style/GeckoBindings.cpp index 4549d81ecf36..f23bb2725265 100644 --- a/layout/style/GeckoBindings.cpp +++ b/layout/style/GeckoBindings.cpp @@ -310,7 +310,6 @@ bool Gecko_AnimationNameMayBeReferencedFromStyle( float Gecko_GetScrollbarInlineSize(const nsPresContext* aPc) { MOZ_ASSERT(aPc); - AutoWriteLock guard(*sServoFFILock); // We read some look&feel values. auto overlay = aPc->UseOverlayScrollbars() ? nsITheme::Overlay::Yes : nsITheme::Overlay::No; LayoutDeviceIntCoord size = @@ -749,20 +748,16 @@ nscolor Gecko_ComputeSystemColor(StyleSystemColor aColor, const Document* aDoc, } auto useStandins = LookAndFeel::ShouldUseStandins(*aDoc, aColor); - - AutoWriteLock guard(*sServoFFILock); return LookAndFeel::Color(aColor, colorScheme, useStandins); } int32_t Gecko_GetLookAndFeelInt(int32_t aId) { auto intId = static_cast(aId); - AutoWriteLock guard(*sServoFFILock); return LookAndFeel::GetInt(intId); } float Gecko_GetLookAndFeelFloat(int32_t aId) { auto id = static_cast(aId); - AutoWriteLock guard(*sServoFFILock); return LookAndFeel::GetFloat(id); } @@ -940,7 +935,6 @@ void Gecko_nsFont_InitSystem(nsFont* aDest, StyleSystemFont aFontId, // itself, so this will do. new (aDest) nsFont(defaultVariableFont); - AutoWriteLock guard(*sServoFFILock); nsLayoutUtils::ComputeSystemFont(aDest, aFontId, defaultVariableFont, aDocument); } diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp index c1c3c5cecbee..6087f02d96e4 100644 --- a/layout/style/ServoStyleSet.cpp +++ b/layout/style/ServoStyleSet.cpp @@ -338,8 +338,6 @@ void ServoStyleSet::PreTraverseSync() { mDocument->ResolveScheduledPresAttrs(); - LookAndFeel::NativeInit(); - mDocument->CacheAllKnownLangPrefs(); if (gfxUserFontSet* userFontSet = mDocument->GetUserFontSet()) { diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h index f57a4517c55c..f88440218ea3 100644 --- a/widget/LookAndFeel.h +++ b/widget/LookAndFeel.h @@ -33,6 +33,7 @@ class Document; namespace widget { class FullLookAndFeel; +class LookAndFeelFont; } // namespace widget enum class StyleSystemColor : uint8_t; @@ -506,6 +507,7 @@ class LookAndFeel { * @param aStyle Styling to apply to the font. */ static bool GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle); + static void GetFont(FontID, widget::LookAndFeelFont&); /** * GetPasswordCharacter() returns a unicode character which should be used @@ -567,14 +569,10 @@ class LookAndFeel { static void Refresh(); /** - * GTK's initialization code can't be run off main thread, call this - * if you plan on using LookAndFeel off main thread later. - * - * This initialized state may get reset due to theme changes, so it - * must be called prior to each potential off-main-thread LookAndFeel - * call, not just once. + * LookAndFeel initialization must be done on the main thread. If you need + * LookAndFeel to be initialized OMT then you need to call this first. */ - static void NativeInit(); + static void EnsureInit(); static void SetData(widget::FullLookAndFeel&& aTables); static void NotifyChangedAllWindows(widget::ThemeChangeKind); diff --git a/widget/RemoteLookAndFeel.cpp b/widget/RemoteLookAndFeel.cpp index 5eea54496b92..8b2350c02e7b 100644 --- a/widget/RemoteLookAndFeel.cpp +++ b/widget/RemoteLookAndFeel.cpp @@ -47,17 +47,6 @@ void RemoteLookAndFeel::SetDataImpl(FullLookAndFeel&& aData) { namespace { -// Some lnf values are somewhat expensive to get, and are not needed in child -// processes, so we can avoid querying them. -bool IsNeededInChildProcess(LookAndFeel::IntID aId) { - switch (aId) { - case LookAndFeel::IntID::AlertNotificationOrigin: - return false; // see bug 1703205 - default: - return true; - } -} - template Result MapLookup(const nsTArray& aItems, const nsTArray& aMap, ID aID) { @@ -113,10 +102,6 @@ nsresult RemoteLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme, } nsresult RemoteLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { - MOZ_DIAGNOSTIC_ASSERT( - IsNeededInChildProcess(aID), - "Querying value that we didn't bother getting from the parent process!"); - const int32_t* result; MOZ_TRY_VAR(result, MapLookup(mTables.ints(), mTables.intMap(), aID)); aResult = *result; @@ -146,53 +131,42 @@ char16_t RemoteLookAndFeel::GetPasswordCharacterImpl() { bool RemoteLookAndFeel::GetEchoPasswordImpl() { return mTables.passwordEcho(); } -static bool AddIDsToMap(nsXPLookAndFeel* aImpl, FullLookAndFeel* aLf) { +// TODO(emilio): Maybe reuse more of nsXPLookAndFeel's stores... +static bool AddIDsToMap(FullLookAndFeel* aLf) { using IntID = LookAndFeel::IntID; using FontID = LookAndFeel::FontID; using FloatID = LookAndFeel::FloatID; using ColorID = LookAndFeel::ColorID; using ColorScheme = LookAndFeel::ColorScheme; + using UseStandins = LookAndFeel::UseStandins; bool anyFromOtherTheme = false; for (auto id : MakeEnumeratedRange(IntID::End)) { - if (!IsNeededInChildProcess(id)) { - continue; - } int32_t theInt; - nsresult rv = aImpl->NativeGetInt(id, theInt); + nsresult rv = LookAndFeel::GetInt(id, &theInt); AddToMap(aLf->tables().ints(), aLf->tables().intMap(), id, NS_SUCCEEDED(rv) ? Some(theInt) : Nothing{}); } for (auto id : MakeEnumeratedRange(ColorID::End)) { - nscolor theColor; - nsresult rv = aImpl->NativeGetColor(id, ColorScheme::Light, theColor); AddToMap(aLf->tables().lightColors(), aLf->tables().lightColorMap(), id, - NS_SUCCEEDED(rv) ? Some(theColor) : Nothing{}); - rv = aImpl->NativeGetColor(id, ColorScheme::Dark, theColor); + LookAndFeel::GetColor(id, ColorScheme::Light, UseStandins::No)); AddToMap(aLf->tables().darkColors(), aLf->tables().darkColorMap(), id, - NS_SUCCEEDED(rv) ? Some(theColor) : Nothing{}); + LookAndFeel::GetColor(id, ColorScheme::Dark, UseStandins::No)); } for (auto id : MakeEnumeratedRange(FloatID::End)) { float theFloat; - nsresult rv = aImpl->NativeGetFloat(id, theFloat); + nsresult rv = LookAndFeel::GetFloat(id, &theFloat); AddToMap(aLf->tables().floats(), aLf->tables().floatMap(), id, NS_SUCCEEDED(rv) ? Some(theFloat) : Nothing{}); } for (auto id : MakeEnumeratedRange(FontID::End)) { - gfxFontStyle fontStyle{}; - - nsString name; - bool rv = aImpl->NativeGetFont(id, name, fontStyle); - Maybe maybeFont; - if (rv) { - maybeFont.emplace( - nsXPLookAndFeel::StyleToLookAndFeelFont(name, fontStyle)); - } + LookAndFeelFont font; + LookAndFeel::GetFont(id, font); AddToMap(aLf->tables().fonts(), aLf->tables().fontMap(), id, - std::move(maybeFont)); + font.haveFont() ? Some(std::move(font)) : Nothing{}); } return anyFromOtherTheme; @@ -214,12 +188,11 @@ const FullLookAndFeel* RemoteLookAndFeel::ExtractData() { } FullLookAndFeel* lf = new FullLookAndFeel{}; - nsXPLookAndFeel* impl = nsXPLookAndFeel::GetInstance(); - lf->tables().passwordChar() = impl->GetPasswordCharacterImpl(); - lf->tables().passwordEcho() = impl->GetEchoPasswordImpl(); + lf->tables().passwordChar() = LookAndFeel::GetPasswordCharacter(); + lf->tables().passwordEcho() = LookAndFeel::GetEchoPassword(); - AddIDsToMap(impl, lf); + AddIDsToMap(lf); // This assignment to sCachedLookAndFeelData must be done after the // WithThemeConfiguredForContent call, since it can end up calling RefreshImpl diff --git a/widget/Theme.cpp b/widget/Theme.cpp index 311d1ffa87d7..f3f2731786e4 100644 --- a/widget/Theme.cpp +++ b/widget/Theme.cpp @@ -168,7 +168,6 @@ void Theme::Init() { for (const auto& pref : kPrefs) { Preferences::RegisterCallback(PrefChangedCallback, pref); } - LookAndFeelChanged(); } void Theme::Shutdown() { diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp index abc16c60fa10..76e41f576255 100644 --- a/widget/gtk/nsLookAndFeel.cpp +++ b/widget/gtk/nsLookAndFeel.cpp @@ -321,6 +321,10 @@ nsLookAndFeel::nsLookAndFeel() { }; GtkSettings* settings = gtk_settings_get_default(); + if (MOZ_UNLIKELY(!settings)) { + return; + } + for (const auto& setting : kObservedSettings) { g_signal_connect_after(settings, setting.get(), G_CALLBACK(settings_changed_cb), nullptr); @@ -364,8 +368,10 @@ nsLookAndFeel::~nsLookAndFeel() { mDBusID = 0; } UnwatchDBus(); - g_signal_handlers_disconnect_by_func( - gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr); + if (GtkSettings* settings = gtk_settings_get_default()) { + g_signal_handlers_disconnect_by_func( + settings, FuncToGpointer(settings_changed_cb), nullptr); + } } #if 0 @@ -952,8 +958,9 @@ static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle, static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle( GtkWidget* aWidget) { - if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single; - + if (!aWidget) { + return mozilla::LookAndFeel::eScrollArrowStyle_Single; + } return CheckWidgetStyle(aWidget, "has-backward-stepper", mozilla::LookAndFeel::eScrollArrow_StartBackward) | CheckWidgetStyle(aWidget, "has-forward-stepper", @@ -994,42 +1001,32 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { aResult = 1; break; case IntID::SelectTextfieldsOnKeyFocus: { - GtkSettings* settings; - gboolean select_on_focus; - - settings = gtk_settings_get_default(); - g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus, - nullptr); - - if (select_on_focus) - aResult = 1; - else - aResult = 0; - + GtkSettings* settings = gtk_settings_get_default(); + gboolean select_on_focus = FALSE; + if (MOZ_LIKELY(settings)) { + g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus, + nullptr); + } + aResult = select_on_focus; } break; case IntID::ScrollToClick: { - GtkSettings* settings; + GtkSettings* settings = gtk_settings_get_default(); gboolean warps_slider = FALSE; - - settings = gtk_settings_get_default(); - if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings), + if (MOZ_LIKELY(settings) && + g_object_class_find_property(G_OBJECT_GET_CLASS(settings), "gtk-primary-button-warps-slider")) { g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider, nullptr); } - - if (warps_slider) - aResult = 1; - else - aResult = 0; + aResult = warps_slider; } break; case IntID::SubmenuDelay: { - GtkSettings* settings; - gint delay; - - settings = gtk_settings_get_default(); - g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr); - aResult = (int32_t)delay; + GtkSettings* settings = gtk_settings_get_default(); + gint delay = 0; + if (MOZ_LIKELY(settings)) { + g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr); + } + aResult = int32_t(delay); break; } case IntID::MenusCanOverlapOSBar: @@ -1041,14 +1038,19 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { case IntID::DragThresholdX: case IntID::DragThresholdY: { gint threshold = 0; - g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold", - &threshold, nullptr); - + GtkSettings* settings = gtk_settings_get_default(); + if (MOZ_LIKELY(settings)) { + g_object_get(settings, "gtk-dnd-drag-threshold", &threshold, nullptr); + } aResult = threshold; } break; case IntID::ScrollArrowStyle: { - GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL); - aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar); + aResult = eScrollArrowStyle_Single; + GtkSettings* settings = gtk_settings_get_default(); + if (MOZ_LIKELY(settings)) { + GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL); + aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar); + } break; } case IntID::TreeOpenDelay: @@ -1099,7 +1101,7 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { break; case IntID::GTKCSDTransparencyAvailable: { auto* screen = gdk_screen_get_default(); - aResult = gdk_screen_get_rgba_visual(screen) && + aResult = MOZ_LIKELY(screen) && gdk_screen_get_rgba_visual(screen) && gdk_screen_is_composited(screen); break; } diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp index 3b92fdf4bba3..7a2a9c605860 100644 --- a/widget/nsXPLookAndFeel.cpp +++ b/widget/nsXPLookAndFeel.cpp @@ -6,6 +6,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/LookAndFeel.h" +#include "mozilla/RWLock.h" #include "nscore.h" #include "nsXPLookAndFeel.h" @@ -56,67 +57,34 @@ using FloatID = mozilla::LookAndFeel::FloatID; using ColorID = mozilla::LookAndFeel::ColorID; using FontID = mozilla::LookAndFeel::FontID; -template -class EnumeratedCache { - mozilla::EnumeratedArray mEntries; - std::bitset mValidity; +// Fully transparent red seems unlikely enough. +constexpr nscolor kNoColor = NS_RGBA(0xff, 0, 0, 0); +using ColorStore = + EnumeratedArray; - public: - constexpr EnumeratedCache() = default; - - bool IsValid(Index aIndex) const { return mValidity[size_t(aIndex)]; } - - const Value* Get(Index aIndex) const { - return IsValid(aIndex) ? &mEntries[aIndex] : nullptr; - } - - void Insert(Index aIndex, Value aValue) { - mValidity[size_t(aIndex)] = true; - mEntries[aIndex] = aValue; - } - - void Remove(Index aIndex) { - mValidity[size_t(aIndex)] = false; - mEntries[aIndex] = Value(); - } - - void Clear() { - mValidity.reset(); - for (auto& entry : mEntries) { - entry = Value(); - } - } -}; - -using ColorCache = EnumeratedCache, ColorID::End>; - -struct ColorCaches { +struct ColorStores { using UseStandins = LookAndFeel::UseStandins; - ColorCache mCaches[2][2]; + ColorStore mStores[2][2]; - constexpr ColorCaches() = default; + constexpr ColorStores() = default; - ColorCache& Get(ColorScheme aScheme, UseStandins aUseStandins) { - return mCaches[aScheme == ColorScheme::Dark] + ColorStore& Get(ColorScheme aScheme, UseStandins aUseStandins) { + return mStores[aScheme == ColorScheme::Dark] [aUseStandins == UseStandins::Yes]; } - - void Clear() { - for (auto& c : mCaches) { - for (auto& cache : c) { - cache.Clear(); - } - } - } }; -static ColorCaches sColorCaches; - -static EnumeratedCache, FloatID::End> sFloatCache; -static EnumeratedCache, IntID::End> sIntCache; -MOZ_RUNINIT static EnumeratedCache - sFontCache; +static ColorStores sColorStores; +constexpr uint32_t kNoFloat = 0xffffff; +static EnumeratedArray + sFloatStore; +constexpr int32_t kNoInt = INT32_MIN; +static EnumeratedArray sIntStore; +StaticRWLock sFontStoreLock; +MOZ_RUNINIT static EnumeratedArray + sFontStore MOZ_GUARDED_BY(sFontStoreLock); // To make one of these prefs toggleable from a reftest add a user // pref in testing/profiles/reftest/user.js. For example, to make @@ -348,8 +316,6 @@ const char* nsXPLookAndFeel::GetColorPrefName(ColorID aId) { return sColorPrefs[size_t(aId)]; } -bool nsXPLookAndFeel::sInitialized = false; - nsXPLookAndFeel* nsXPLookAndFeel::sInstance = nullptr; bool nsXPLookAndFeel::sShutdown = false; @@ -402,10 +368,49 @@ nsXPLookAndFeel* nsXPLookAndFeel::GetInstance() { *lnf = {}; } + sInstance->Init(); + sInstance->NativeInit(); + FillStores(sInstance); widget::Theme::Init(); + if (XRE_IsParentProcess()) { + nsLayoutUtils::RecomputeSmoothScrollDefault(); + } return sInstance; } +void nsXPLookAndFeel::FillStores(nsXPLookAndFeel* aInst) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + for (auto scheme : {ColorScheme::Light, ColorScheme::Dark}) { + for (auto standins : {UseStandins::Yes, UseStandins::No}) { + auto& store = sColorStores.Get(scheme, standins); + for (ColorID id : MakeEnumeratedRange(ColorID(0), ColorID::End)) { + auto uncached = aInst->GetUncachedColor(id, scheme, standins); + MOZ_ASSERT_IF(uncached, uncached.value() != kNoColor); + store[id] = uncached.valueOr(kNoColor); + } + } + } + for (IntID id : MakeEnumeratedRange(IntID(0), IntID::End)) { + int32_t value = 0; + nsresult rv = aInst->GetIntValue(id, value); + MOZ_ASSERT_IF(NS_SUCCEEDED(rv), value != kNoInt); + sIntStore[id] = NS_SUCCEEDED(rv) ? value : kNoInt; + } + + for (FloatID id : MakeEnumeratedRange(FloatID(0), FloatID::End)) { + float value = 0; + nsresult rv = aInst->GetFloatValue(id, value); + auto repr = BitwiseCast(value); + MOZ_ASSERT_IF(NS_SUCCEEDED(rv), repr != kNoFloat); + sFloatStore[id] = NS_SUCCEEDED(rv) ? repr : kNoFloat; + } + + StaticAutoWriteLock guard(sFontStoreLock); + for (FontID id : MakeEnumeratedRange(FontID(0), FontID::End)) { + sFontStore[id] = aInst->GetFontValue(id); + } +} + // static void nsXPLookAndFeel::Shutdown() { if (sShutdown) { @@ -417,7 +422,12 @@ void nsXPLookAndFeel::Shutdown() { sInstance = nullptr; // This keeps strings alive, so need to clear to make leak checking happy. - sFontCache.Clear(); + { + StaticAutoWriteLock guard(sFontStoreLock); + for (auto& f : sFontStore) { + f = {}; + } + } widget::Theme::Shutdown(); } @@ -521,14 +531,6 @@ static constexpr struct { void nsXPLookAndFeel::Init() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); - // Say we're already initialized, and take the chance that it might fail; - // protects against some other process writing to our static variables. - sInitialized = true; - - if (XRE_IsParentProcess()) { - nsLayoutUtils::RecomputeSmoothScrollDefault(); - } - // XXX If we could reorganize the pref names, we should separate the branch // for each types. Then, we could reduce the unnecessary loop from // nsXPLookAndFeel::OnPrefChanged(). @@ -546,8 +548,9 @@ void nsXPLookAndFeel::Init() { } nsXPLookAndFeel::~nsXPLookAndFeel() { - NS_ASSERTION(sInstance == this, - "This destroying instance isn't the singleton instance"); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(sInstance == this, + "This destroying instance isn't the singleton instance"); sInstance = nullptr; } @@ -954,29 +957,13 @@ static nsresult GetColorFromPref(LookAndFeel::ColorID aID, ColorScheme aScheme, nsresult nsXPLookAndFeel::GetColorValue(ColorID aID, ColorScheme aScheme, UseStandins aUseStandins, nscolor& aResult) { - if (!sInitialized) { - Init(); - } - #ifdef DEBUG_SYSTEM_COLOR_USE if (NS_SUCCEEDED(SystemColorUseDebuggingColor(aID, aResult))) { return NS_OK; } #endif - auto& cache = sColorCaches.Get(aScheme, aUseStandins); - if (const auto* cached = cache.Get(aID)) { - if (cached->isNothing()) { - return NS_ERROR_FAILURE; - } - aResult = cached->value(); - return NS_OK; - } - - // NOTE: Servo holds a lock and the main thread is paused, so writing to the - // global cache here is fine. auto result = GetUncachedColor(aID, aScheme, aUseStandins); - cache.Insert(aID, result); if (!result) { return NS_ERROR_FAILURE; } @@ -1001,59 +988,23 @@ Maybe nsXPLookAndFeel::GetUncachedColor(ColorID aID, } nsresult nsXPLookAndFeel::GetIntValue(IntID aID, int32_t& aResult) { - if (!sInitialized) { - Init(); - } - - if (const auto* cached = sIntCache.Get(aID)) { - if (cached->isNothing()) { - return NS_ERROR_FAILURE; - } - aResult = cached->value(); - return NS_OK; - } - if (NS_SUCCEEDED(Preferences::GetInt(sIntPrefs[size_t(aID)], &aResult))) { - sIntCache.Insert(aID, Some(aResult)); return NS_OK; } if (NS_FAILED(NativeGetInt(aID, aResult))) { - sIntCache.Insert(aID, Nothing()); return NS_ERROR_FAILURE; } - - sIntCache.Insert(aID, Some(aResult)); return NS_OK; } nsresult nsXPLookAndFeel::GetFloatValue(FloatID aID, float& aResult) { - if (!sInitialized) { - Init(); - } - - if (const auto* cached = sFloatCache.Get(aID)) { - if (cached->isNothing()) { - return NS_ERROR_FAILURE; - } - aResult = cached->value(); - return NS_OK; - } - int32_t pref = 0; if (NS_SUCCEEDED(Preferences::GetInt(sFloatPrefs[size_t(aID)], &pref))) { aResult = float(pref) / 100.0f; - sFloatCache.Insert(aID, Some(aResult)); return NS_OK; } - - if (NS_FAILED(NativeGetFloat(aID, aResult))) { - sFloatCache.Insert(aID, Nothing()); - return NS_ERROR_FAILURE; - } - - sFloatCache.Insert(aID, Some(aResult)); - return NS_OK; + return NativeGetFloat(aID, aResult); } bool nsXPLookAndFeel::LookAndFeelFontToStyle(const LookAndFeelFont& aFont, @@ -1098,20 +1049,14 @@ widget::LookAndFeelFont nsXPLookAndFeel::StyleToLookAndFeelFont( return font; } -bool nsXPLookAndFeel::GetFontValue(FontID aID, nsString& aName, - gfxFontStyle& aStyle) { - if (const LookAndFeelFont* cached = sFontCache.Get(aID)) { - return LookAndFeelFontToStyle(*cached, aName, aStyle); - } - +widget::LookAndFeelFont nsXPLookAndFeel::GetFontValue(FontID aID) { LookAndFeelFont font; auto GetFontsFromPrefs = [&]() -> bool { nsDependentCString pref(sFontPrefs[size_t(aID)]); - if (NS_FAILED(Preferences::GetString(pref.get(), aName))) { + if (NS_FAILED(Preferences::GetString(pref.get(), font.name()))) { return false; } font.haveFont() = true; - font.name() = aName; font.size() = Preferences::GetFloat(nsAutoCString(pref + ".size"_ns).get()); // This is written this way rather than using the fallback so that an empty // pref (such like the one about:config creates) doesn't cause system fonts @@ -1125,32 +1070,19 @@ bool nsXPLookAndFeel::GetFontValue(FontID aID, nsString& aName, Preferences::GetBool(nsAutoCString(pref + ".italic"_ns).get()); return true; }; - - if (GetFontsFromPrefs()) { - LookAndFeelFontToStyle(font, aName, aStyle); - } else if (NativeGetFont(aID, aName, aStyle)) { - font = StyleToLookAndFeelFont(aName, aStyle); - } else { - MOZ_ASSERT(!font.haveFont()); + if (!GetFontsFromPrefs()) { + nsAutoString name; + gfxFontStyle style; + if (NativeGetFont(aID, name, style)) { + font = StyleToLookAndFeelFont(name, style); + } else { + MOZ_ASSERT(!font.haveFont()); + } } - bool success = font.haveFont(); - sFontCache.Insert(aID, std::move(font)); - return success; + return font; } -void nsXPLookAndFeel::RefreshImpl() { - // Wipe out our caches. - sColorCaches.Clear(); - sFontCache.Clear(); - sFloatCache.Clear(); - sIntCache.Clear(); - - if (XRE_IsParentProcess()) { - nsLayoutUtils::RecomputeSmoothScrollDefault(); - // Clear any cached FullLookAndFeel data, which is now invalid. - widget::RemoteLookAndFeel::ClearCachedData(); - } -} +void nsXPLookAndFeel::RefreshImpl() {} static bool sRecordedLookAndFeelTelemetry = false; @@ -1366,13 +1298,12 @@ LookAndFeel::ColorScheme LookAndFeel::ColorSchemeForFrame( // static Maybe LookAndFeel::GetColor(ColorID aId, ColorScheme aScheme, UseStandins aUseStandins) { - nscolor result; - nsresult rv = nsLookAndFeel::GetInstance()->GetColorValue( - aId, aScheme, aUseStandins, result); - if (NS_FAILED(rv)) { + MOZ_ASSERT(nsXPLookAndFeel::sInstance, "Not initialized"); + nscolor color = sColorStores.Get(aScheme, aUseStandins)[aId]; + if (color == kNoColor) { return Nothing(); } - return Some(result); + return Some(color); } // Returns whether there is a CSS color name for this color. @@ -1432,17 +1363,37 @@ Maybe LookAndFeel::GetColor(ColorID aId, const nsIFrame* aFrame) { // static nsresult LookAndFeel::GetInt(IntID aID, int32_t* aResult) { - return nsLookAndFeel::GetInstance()->GetIntValue(aID, *aResult); + MOZ_ASSERT(nsXPLookAndFeel::sInstance, "Not initialized?"); + int32_t result = sIntStore[aID]; + if (result == kNoInt) { + return NS_ERROR_FAILURE; + } + *aResult = result; + return NS_OK; } // static nsresult LookAndFeel::GetFloat(FloatID aID, float* aResult) { - return nsLookAndFeel::GetInstance()->GetFloatValue(aID, *aResult); + uint32_t result = sFloatStore[aID]; + if (result == kNoFloat) { + return NS_ERROR_FAILURE; + } + *aResult = BitwiseCast(result); + return NS_OK; } // static +void LookAndFeel::GetFont(FontID aID, widget::LookAndFeelFont& aFont) { + MOZ_ASSERT(nsXPLookAndFeel::sInstance, "Not initialized?"); + StaticAutoReadLock guard(sFontStoreLock); + aFont = sFontStore[aID]; +} + bool LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle) { - return nsLookAndFeel::GetInstance()->GetFontValue(aID, aName, aStyle); + MOZ_ASSERT(nsXPLookAndFeel::sInstance, "Not initialized?"); + StaticAutoReadLock guard(sFontStoreLock); + return nsXPLookAndFeel::LookAndFeelFontToStyle(sFontStore[aID], aName, + aStyle); } // static @@ -1508,14 +1459,21 @@ Modifiers LookAndFeel::GetMenuAccessKeyModifiers() { } } -// static -void LookAndFeel::Refresh() { - nsLookAndFeel::GetInstance()->RefreshImpl(); - widget::Theme::LookAndFeelChanged(); -} +void LookAndFeel::EnsureInit() { Unused << nsXPLookAndFeel::GetInstance(); } // static -void LookAndFeel::NativeInit() { nsLookAndFeel::GetInstance()->NativeInit(); } +void LookAndFeel::Refresh() { + auto* inst = nsLookAndFeel::GetInstance(); + inst->RefreshImpl(); + inst->NativeInit(); + nsXPLookAndFeel::FillStores(inst); + if (XRE_IsParentProcess()) { + nsLayoutUtils::RecomputeSmoothScrollDefault(); + // Clear any cached FullLookAndFeel data, which is now invalid. + widget::RemoteLookAndFeel::ClearCachedData(); + } + widget::Theme::LookAndFeelChanged(); +} // static void LookAndFeel::SetData(widget::FullLookAndFeel&& aTables) { diff --git a/widget/nsXPLookAndFeel.h b/widget/nsXPLookAndFeel.h index c8e7893200e6..52dcf424cf92 100644 --- a/widget/nsXPLookAndFeel.h +++ b/widget/nsXPLookAndFeel.h @@ -14,6 +14,7 @@ class nsLookAndFeel; class nsXPLookAndFeel : public mozilla::LookAndFeel { + friend class mozilla::LookAndFeel; public: using FullLookAndFeel = mozilla::widget::FullLookAndFeel; using LookAndFeelFont = mozilla::widget::LookAndFeelFont; @@ -23,24 +24,9 @@ class nsXPLookAndFeel : public mozilla::LookAndFeel { static nsXPLookAndFeel* GetInstance(); static void Shutdown(); - void Init(); - // Gets the pref name for a given color, just for debugging purposes. static const char* GetColorPrefName(ColorID); - // These functions will return a value specified by an override pref, if it - // exists, and otherwise will call into the NativeGetXxx function to get the - // platform-specific value. - // - // NS_ERROR_NOT_AVAILABLE is returned if there is neither an override pref or - // a platform-specific value. - nsresult GetColorValue(ColorID, ColorScheme, UseStandins, nscolor& aResult); - nsresult GetIntValue(IntID aID, int32_t& aResult); - nsresult GetFloatValue(FloatID aID, float& aResult); - // Same, but returns false if there is no platform-specific value. - // (There are no override prefs for font values.) - bool GetFontValue(FontID aID, nsString& aName, gfxFontStyle& aStyle); - virtual nsresult NativeGetInt(IntID aID, int32_t& aResult) = 0; virtual nsresult NativeGetFloat(FloatID aID, float& aResult) = 0; virtual nsresult NativeGetColor(ColorID, ColorScheme, nscolor& aResult) = 0; @@ -79,7 +65,23 @@ class nsXPLookAndFeel : public mozilla::LookAndFeel { protected: nsXPLookAndFeel() = default; + void Init(); + static nscolor GetStandinForNativeColor(ColorID, ColorScheme); + static void FillStores(nsXPLookAndFeel* aInstance); + + // These functions will return a value specified by an override pref, if it + // exists, and otherwise will call into the NativeGetXxx function to get the + // platform-specific value. + // + // NS_ERROR_NOT_AVAILABLE is returned if there is neither an override pref or + // a platform-specific value. + nsresult GetColorValue(ColorID, ColorScheme, UseStandins, nscolor& aResult); + nsresult GetIntValue(IntID aID, int32_t& aResult); + nsresult GetFloatValue(FloatID aID, float& aResult); + // Same, but returns false if there is no platform-specific value. + bool GetFontValue(FontID aID, nsString& aName, gfxFontStyle& aStyle); + LookAndFeelFont GetFontValue(FontID aID); // A platform-agnostic dark-color scheme, for platforms where we don't have // "native" dark colors, like Windows and Android. diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp index 8533e9880d93..063dee4dcab3 100644 --- a/widget/windows/nsLookAndFeel.cpp +++ b/widget/windows/nsLookAndFeel.cpp @@ -15,6 +15,7 @@ #include "WindowsUIUtils.h" #include "mozilla/FontPropertyTypes.h" #include "mozilla/Telemetry.h" +#include "mozilla/intl/LocaleService.h" #include "mozilla/widget/WinRegistry.h" #define AVG2(a, b) (((a) + (b) + 1) >> 1) @@ -518,38 +519,10 @@ nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) { aResult = WinUtils::MicaPopupsEnabled(); break; case IntID::AlertNotificationOrigin: - aResult = 0; - { - // Get task bar window handle - HWND shellWindow = FindWindowW(L"Shell_TrayWnd", nullptr); - - if (shellWindow != nullptr) { - // Determine position - APPBARDATA appBarData; - appBarData.hWnd = shellWindow; - appBarData.cbSize = sizeof(appBarData); - if (SHAppBarMessage(ABM_GETTASKBARPOS, &appBarData)) { - // Set alert origin as a bit field - see LookAndFeel.h - // 0 represents bottom right, sliding vertically. - switch (appBarData.uEdge) { - case ABE_LEFT: - aResult = NS_ALERT_HORIZONTAL | NS_ALERT_LEFT; - break; - case ABE_RIGHT: - aResult = NS_ALERT_HORIZONTAL; - break; - case ABE_TOP: - aResult = NS_ALERT_TOP; - [[fallthrough]]; - case ABE_BOTTOM: - // If the task bar is right-to-left, - // move the origin to the left - if (::GetWindowLong(shellWindow, GWL_EXSTYLE) & WS_EX_LAYOUTRTL) - aResult |= NS_ALERT_LEFT; - break; - } - } - } + aResult = NS_ALERT_TOP; + if (intl::LocaleService::GetInstance()->IsAppLocaleRTL()) { + // If the task bar is right-to-left, move the origin to the left + aResult |= NS_ALERT_LEFT; } break; case IntID::IMERawInputUnderlineStyle: