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
This commit is contained in:
Emilio Cobos Álvarez
2025-03-22 03:24:00 +00:00
parent 87cd77f506
commit 3618e5ed50
12 changed files with 210 additions and 325 deletions

View File

@@ -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<UserInteractionObserver> uio = new UserInteractionObserver();

View File

@@ -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<nsIRunnable> 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

View File

@@ -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;

View File

@@ -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<LookAndFeel::IntID>(aId);
AutoWriteLock guard(*sServoFFILock);
return LookAndFeel::GetInt(intId);
}
float Gecko_GetLookAndFeelFloat(int32_t aId) {
auto id = static_cast<LookAndFeel::FloatID>(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);
}

View File

@@ -338,8 +338,6 @@ void ServoStyleSet::PreTraverseSync() {
mDocument->ResolveScheduledPresAttrs();
LookAndFeel::NativeInit();
mDocument->CacheAllKnownLangPrefs();
if (gfxUserFontSet* userFontSet = mDocument->GetUserFontSet()) {

View File

@@ -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);

View File

@@ -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 <typename Item, typename UInt, typename ID>
Result<const Item*, nsresult> MapLookup(const nsTArray<Item>& aItems,
const nsTArray<UInt>& 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<LookAndFeelFont> 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

View File

@@ -168,7 +168,6 @@ void Theme::Init() {
for (const auto& pref : kPrefs) {
Preferences::RegisterCallback(PrefChangedCallback, pref);
}
LookAndFeelChanged();
}
void Theme::Shutdown() {

View File

@@ -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;
}

View File

@@ -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 <typename Index, typename Value, Index kEnd>
class EnumeratedCache {
mozilla::EnumeratedArray<Index, Value, size_t(kEnd)> mEntries;
std::bitset<size_t(kEnd)> mValidity;
// Fully transparent red seems unlikely enough.
constexpr nscolor kNoColor = NS_RGBA(0xff, 0, 0, 0);
using ColorStore =
EnumeratedArray<ColorID, RelaxedAtomicUint32, size_t(ColorID::End)>;
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, Maybe<nscolor>, 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, Maybe<float>, FloatID::End> sFloatCache;
static EnumeratedCache<IntID, Maybe<int32_t>, IntID::End> sIntCache;
MOZ_RUNINIT static EnumeratedCache<FontID, widget::LookAndFeelFont, FontID::End>
sFontCache;
static ColorStores sColorStores;
constexpr uint32_t kNoFloat = 0xffffff;
static EnumeratedArray<FloatID, RelaxedAtomicUint32, size_t(FloatID::End)>
sFloatStore;
constexpr int32_t kNoInt = INT32_MIN;
static EnumeratedArray<IntID, RelaxedAtomicInt32, size_t(IntID::End)> sIntStore;
StaticRWLock sFontStoreLock;
MOZ_RUNINIT static EnumeratedArray<FontID, widget::LookAndFeelFont,
size_t(FontID::End)>
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<uint32_t>(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<nscolor> 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<nscolor> 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<nscolor> 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<float>(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) {

View File

@@ -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.

View File

@@ -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: