diff options
Diffstat (limited to 'widget/RemoteLookAndFeel.cpp')
-rw-r--r-- | widget/RemoteLookAndFeel.cpp | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/widget/RemoteLookAndFeel.cpp b/widget/RemoteLookAndFeel.cpp new file mode 100644 index 0000000000..1f83b1a4d5 --- /dev/null +++ b/widget/RemoteLookAndFeel.cpp @@ -0,0 +1,236 @@ +/* -*- 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/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_widget.h" +#include "nsXULAppAPI.h" + +#include <limits> +#include <type_traits> +#include <utility> + +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<FullLookAndFeel> 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 <typename Item, typename UInt, typename ID> +Result<const Item*, nsresult> MapLookup(const nsTArray<Item>& aItems, + const nsTArray<UInt>& aMap, ID aID) { + UInt mapped = aMap[static_cast<size_t>(aID)]; + + if (mapped == std::numeric_limits<UInt>::max()) { + return Err(NS_ERROR_NOT_IMPLEMENTED); + } + + return &aItems[static_cast<size_t>(mapped)]; +} + +template <typename Item, typename UInt, typename Id> +void AddToMap(nsTArray<Item>& aItems, nsTArray<UInt>& aMap, Id aId, + Maybe<Item>&& aNewItem) { + auto mapIndex = size_t(aId); + aMap.EnsureLengthAtLeast(mapIndex + 1); + if (aNewItem.isNothing()) { + aMap[mapIndex] = std::numeric_limits<UInt>::max(); + return; + } + + size_t newIndex = aItems.Length(); + MOZ_ASSERT(newIndex < std::numeric_limits<UInt>::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<UInt>(i); + return; + } + } + + aItems.AppendElement(aNewItem.extract()); + aMap[mapIndex] = static_cast<UInt>(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<char16_t>(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<LookAndFeelFont> 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 |