summaryrefslogtreecommitdiffstats
path: root/widget/RemoteLookAndFeel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/RemoteLookAndFeel.cpp')
-rw-r--r--widget/RemoteLookAndFeel.cpp236
1 files changed, 236 insertions, 0 deletions
diff --git a/widget/RemoteLookAndFeel.cpp b/widget/RemoteLookAndFeel.cpp
new file mode 100644
index 0000000000..5eea54496b
--- /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/ResultExtensions.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/Try.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