/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: sw=2 ts=8 et : */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RemoteLookAndFeel.h" #include "gfxFont.h" #include "MainThreadUtils.h" #include "mozilla/Assertions.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/ResultExtensions.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/Try.h" #include "nsXULAppAPI.h" #include #include #include namespace mozilla::widget { // A cached copy of the data extracted by ExtractData. // // Storing this lets us avoid doing most of the work of ExtractData each // time we create a new content process. // // Only used in the parent process. static StaticAutoPtr sCachedLookAndFeelData; RemoteLookAndFeel::RemoteLookAndFeel(FullLookAndFeel&& aData) : mTables(std::move(aData.tables())) { MOZ_ASSERT(XRE_IsContentProcess(), "Only content processes should be using a RemoteLookAndFeel"); } RemoteLookAndFeel::~RemoteLookAndFeel() = default; void RemoteLookAndFeel::SetDataImpl(FullLookAndFeel&& aData) { MOZ_ASSERT(XRE_IsContentProcess(), "Only content processes should be using a RemoteLookAndFeel"); MOZ_ASSERT(NS_IsMainThread()); mTables = std::move(aData.tables()); } 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) { UInt mapped = aMap[static_cast(aID)]; if (mapped == std::numeric_limits::max()) { return Err(NS_ERROR_NOT_IMPLEMENTED); } return &aItems[static_cast(mapped)]; } template void AddToMap(nsTArray& aItems, nsTArray& aMap, Id aId, Maybe&& aNewItem) { auto mapIndex = size_t(aId); aMap.EnsureLengthAtLeast(mapIndex + 1); if (aNewItem.isNothing()) { aMap[mapIndex] = std::numeric_limits::max(); return; } size_t newIndex = aItems.Length(); MOZ_ASSERT(newIndex < std::numeric_limits::max()); // Check if there is an existing value in aItems that we can point to. // // The arrays should be small enough and contain few enough unique // values that sequential search here is reasonable. for (size_t i = 0; i < newIndex; ++i) { if (aItems[i] == aNewItem.ref()) { aMap[mapIndex] = static_cast(i); return; } } aItems.AppendElement(aNewItem.extract()); aMap[mapIndex] = static_cast(newIndex); } } // namespace nsresult RemoteLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme, nscolor& aResult) { const nscolor* result; const bool dark = aScheme == ColorScheme::Dark; MOZ_TRY_VAR( result, MapLookup(dark ? mTables.darkColors() : mTables.lightColors(), dark ? mTables.darkColorMap() : mTables.lightColorMap(), aID)); aResult = *result; return NS_OK; } 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; return NS_OK; } nsresult RemoteLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) { const float* result; MOZ_TRY_VAR(result, MapLookup(mTables.floats(), mTables.floatMap(), aID)); aResult = *result; return NS_OK; } bool RemoteLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName, gfxFontStyle& aFontStyle) { auto result = MapLookup(mTables.fonts(), mTables.fontMap(), aID); if (result.isErr()) { return false; } const LookAndFeelFont& font = *result.unwrap(); return LookAndFeelFontToStyle(font, aFontName, aFontStyle); } char16_t RemoteLookAndFeel::GetPasswordCharacterImpl() { return static_cast(mTables.passwordChar()); } bool RemoteLookAndFeel::GetEchoPasswordImpl() { return mTables.passwordEcho(); } static bool AddIDsToMap(nsXPLookAndFeel* aImpl, FullLookAndFeel* aLf) { using IntID = LookAndFeel::IntID; using FontID = LookAndFeel::FontID; using FloatID = LookAndFeel::FloatID; using ColorID = LookAndFeel::ColorID; using ColorScheme = LookAndFeel::ColorScheme; bool anyFromOtherTheme = false; for (auto id : MakeEnumeratedRange(IntID::End)) { if (!IsNeededInChildProcess(id)) { continue; } int32_t theInt; nsresult rv = aImpl->NativeGetInt(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); AddToMap(aLf->tables().darkColors(), aLf->tables().darkColorMap(), id, NS_SUCCEEDED(rv) ? Some(theColor) : Nothing{}); } for (auto id : MakeEnumeratedRange(FloatID::End)) { float theFloat; nsresult rv = aImpl->NativeGetFloat(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)); } AddToMap(aLf->tables().fonts(), aLf->tables().fontMap(), id, std::move(maybeFont)); } return anyFromOtherTheme; } // static const FullLookAndFeel* RemoteLookAndFeel::ExtractData() { MOZ_ASSERT(XRE_IsParentProcess(), "Only parent processes should be extracting LookAndFeel data"); if (sCachedLookAndFeelData) { return sCachedLookAndFeelData; } static bool sInitialized = false; if (!sInitialized) { sInitialized = true; ClearOnShutdown(&sCachedLookAndFeelData); } FullLookAndFeel* lf = new FullLookAndFeel{}; nsXPLookAndFeel* impl = nsXPLookAndFeel::GetInstance(); lf->tables().passwordChar() = impl->GetPasswordCharacterImpl(); lf->tables().passwordEcho() = impl->GetEchoPasswordImpl(); AddIDsToMap(impl, lf); // This assignment to sCachedLookAndFeelData must be done after the // WithThemeConfiguredForContent call, since it can end up calling RefreshImpl // on the LookAndFeel, which will clear out sCachedTables. sCachedLookAndFeelData = lf; return sCachedLookAndFeelData; } void RemoteLookAndFeel::ClearCachedData() { MOZ_ASSERT(XRE_IsParentProcess()); sCachedLookAndFeelData = nullptr; } } // namespace mozilla::widget