1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "RemoteLookAndFeel.h"
11 #include "MainThreadUtils.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/Result.h"
15 #include "mozilla/ResultExtensions.h"
16 #include "mozilla/StaticPrefs_widget.h"
17 #include "nsLookAndFeel.h"
18 #include "nsXULAppAPI.h"
21 #include <type_traits>
24 namespace mozilla::widget
{
26 // A cached copy of the data extracted by ExtractData.
28 // Storing this lets us avoid doing most of the work of ExtractData each
29 // time we create a new content process.
31 // Only used in the parent process.
32 static StaticAutoPtr
<FullLookAndFeel
> sCachedLookAndFeelData
;
34 RemoteLookAndFeel::RemoteLookAndFeel(FullLookAndFeel
&& aData
)
35 : mTables(std::move(aData
.tables())) {
36 MOZ_ASSERT(XRE_IsContentProcess(),
37 "Only content processes should be using a RemoteLookAndFeel");
40 if (!StaticPrefs::widget_non_native_theme_enabled()) {
41 // Configure the theme in this content process with the Gtk theme that was
42 // chosen by WithThemeConfiguredForContent in the parent process.
43 nsLookAndFeel::ConfigureTheme(aData
.theme());
48 RemoteLookAndFeel::~RemoteLookAndFeel() = default;
50 void RemoteLookAndFeel::SetDataImpl(FullLookAndFeel
&& aData
) {
51 MOZ_ASSERT(XRE_IsContentProcess(),
52 "Only content processes should be using a RemoteLookAndFeel");
53 MOZ_ASSERT(NS_IsMainThread());
54 mTables
= std::move(aData
.tables());
57 if (!StaticPrefs::widget_non_native_theme_enabled()) {
58 // Configure the theme in this content process with the Gtk theme that was
59 // chosen by WithThemeConfiguredForContent in the parent process.
60 nsLookAndFeel::ConfigureTheme(aData
.theme());
67 // Some lnf values are somewhat expensive to get, and are not needed in child
68 // processes, so we can avoid querying them.
69 bool IsNeededInChildProcess(LookAndFeel::IntID aId
) {
71 case LookAndFeel::IntID::AlertNotificationOrigin
:
72 return false; // see bug 1703205
78 template <typename Item
, typename UInt
, typename ID
>
79 Result
<const Item
*, nsresult
> MapLookup(const nsTArray
<Item
>& aItems
,
80 const nsTArray
<UInt
>& aMap
, ID aID
) {
81 UInt mapped
= aMap
[static_cast<size_t>(aID
)];
83 if (mapped
== std::numeric_limits
<UInt
>::max()) {
84 return Err(NS_ERROR_NOT_IMPLEMENTED
);
87 return &aItems
[static_cast<size_t>(mapped
)];
90 template <typename Item
, typename UInt
, typename Id
>
91 void AddToMap(nsTArray
<Item
>& aItems
, nsTArray
<UInt
>& aMap
, Id aId
,
92 Maybe
<Item
>&& aNewItem
) {
93 auto mapIndex
= size_t(aId
);
94 aMap
.EnsureLengthAtLeast(mapIndex
+ 1);
95 if (aNewItem
.isNothing()) {
96 aMap
[mapIndex
] = std::numeric_limits
<UInt
>::max();
100 size_t newIndex
= aItems
.Length();
101 MOZ_ASSERT(newIndex
< std::numeric_limits
<UInt
>::max());
103 // Check if there is an existing value in aItems that we can point to.
105 // The arrays should be small enough and contain few enough unique
106 // values that sequential search here is reasonable.
107 for (size_t i
= 0; i
< newIndex
; ++i
) {
108 if (aItems
[i
] == aNewItem
.ref()) {
109 aMap
[mapIndex
] = static_cast<UInt
>(i
);
114 aItems
.AppendElement(aNewItem
.extract());
115 aMap
[mapIndex
] = static_cast<UInt
>(newIndex
);
120 nsresult
RemoteLookAndFeel::NativeGetColor(ColorID aID
, ColorScheme aScheme
,
122 const nscolor
* result
;
123 const bool dark
= aScheme
== ColorScheme::Dark
;
126 MapLookup(dark
? mTables
.darkColors() : mTables
.lightColors(),
127 dark
? mTables
.darkColorMap() : mTables
.lightColorMap(), aID
));
132 nsresult
RemoteLookAndFeel::NativeGetInt(IntID aID
, int32_t& aResult
) {
133 MOZ_DIAGNOSTIC_ASSERT(
134 IsNeededInChildProcess(aID
),
135 "Querying value that we didn't bother getting from the parent process!");
137 const int32_t* result
;
138 MOZ_TRY_VAR(result
, MapLookup(mTables
.ints(), mTables
.intMap(), aID
));
143 nsresult
RemoteLookAndFeel::NativeGetFloat(FloatID aID
, float& aResult
) {
145 MOZ_TRY_VAR(result
, MapLookup(mTables
.floats(), mTables
.floatMap(), aID
));
150 bool RemoteLookAndFeel::NativeGetFont(FontID aID
, nsString
& aFontName
,
151 gfxFontStyle
& aFontStyle
) {
152 auto result
= MapLookup(mTables
.fonts(), mTables
.fontMap(), aID
);
153 if (result
.isErr()) {
156 const LookAndFeelFont
& font
= *result
.unwrap();
157 return LookAndFeelFontToStyle(font
, aFontName
, aFontStyle
);
160 char16_t
RemoteLookAndFeel::GetPasswordCharacterImpl() {
161 return static_cast<char16_t
>(mTables
.passwordChar());
164 bool RemoteLookAndFeel::GetEchoPasswordImpl() { return mTables
.passwordEcho(); }
166 static bool AddIDsToMap(nsXPLookAndFeel
* aImpl
, FullLookAndFeel
* aLf
) {
167 using IntID
= LookAndFeel::IntID
;
168 using FontID
= LookAndFeel::FontID
;
169 using FloatID
= LookAndFeel::FloatID
;
170 using ColorID
= LookAndFeel::ColorID
;
171 using ColorScheme
= LookAndFeel::ColorScheme
;
173 bool anyFromOtherTheme
= false;
174 for (auto id
: MakeEnumeratedRange(IntID::End
)) {
175 if (!IsNeededInChildProcess(id
)) {
179 nsresult rv
= aImpl
->NativeGetInt(id
, theInt
);
180 AddToMap(aLf
->tables().ints(), aLf
->tables().intMap(), id
,
181 NS_SUCCEEDED(rv
) ? Some(theInt
) : Nothing
{});
184 for (auto id
: MakeEnumeratedRange(ColorID::End
)) {
186 nsresult rv
= aImpl
->NativeGetColor(id
, ColorScheme::Light
, theColor
);
187 AddToMap(aLf
->tables().lightColors(), aLf
->tables().lightColorMap(), id
,
188 NS_SUCCEEDED(rv
) ? Some(theColor
) : Nothing
{});
189 rv
= aImpl
->NativeGetColor(id
, ColorScheme::Dark
, theColor
);
190 AddToMap(aLf
->tables().darkColors(), aLf
->tables().darkColorMap(), id
,
191 NS_SUCCEEDED(rv
) ? Some(theColor
) : Nothing
{});
194 for (auto id
: MakeEnumeratedRange(FloatID::End
)) {
196 nsresult rv
= aImpl
->NativeGetFloat(id
, theFloat
);
197 AddToMap(aLf
->tables().floats(), aLf
->tables().floatMap(), id
,
198 NS_SUCCEEDED(rv
) ? Some(theFloat
) : Nothing
{});
201 for (auto id
: MakeEnumeratedRange(FontID::End
)) {
202 gfxFontStyle fontStyle
{};
205 bool rv
= aImpl
->NativeGetFont(id
, name
, fontStyle
);
206 Maybe
<LookAndFeelFont
> maybeFont
;
209 nsXPLookAndFeel::StyleToLookAndFeelFont(name
, fontStyle
));
211 AddToMap(aLf
->tables().fonts(), aLf
->tables().fontMap(), id
,
212 std::move(maybeFont
));
215 return anyFromOtherTheme
;
219 const FullLookAndFeel
* RemoteLookAndFeel::ExtractData() {
220 MOZ_ASSERT(XRE_IsParentProcess(),
221 "Only parent processes should be extracting LookAndFeel data");
223 if (sCachedLookAndFeelData
) {
224 return sCachedLookAndFeelData
;
227 static bool sInitialized
= false;
230 ClearOnShutdown(&sCachedLookAndFeelData
);
233 FullLookAndFeel
* lf
= new FullLookAndFeel
{};
234 nsXPLookAndFeel
* impl
= nsXPLookAndFeel::GetInstance();
236 #ifdef MOZ_WIDGET_GTK
237 impl
->GetGtkContentTheme(lf
->theme());
240 lf
->tables().passwordChar() = impl
->GetPasswordCharacterImpl();
241 lf
->tables().passwordEcho() = impl
->GetEchoPasswordImpl();
243 AddIDsToMap(impl
, lf
);
245 // This assignment to sCachedLookAndFeelData must be done after the
246 // WithThemeConfiguredForContent call, since it can end up calling RefreshImpl
247 // on the LookAndFeel, which will clear out sCachedTables.
248 sCachedLookAndFeelData
= lf
;
249 return sCachedLookAndFeelData
;
252 void RemoteLookAndFeel::ClearCachedData() {
253 MOZ_ASSERT(XRE_IsParentProcess());
254 sCachedLookAndFeelData
= nullptr;
257 } // namespace mozilla::widget