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 bool aDifferentTheme
, bool aFromParentTheme
) {
168 using IntID
= LookAndFeel::IntID
;
169 using FontID
= LookAndFeel::FontID
;
170 using FloatID
= LookAndFeel::FloatID
;
171 using ColorID
= LookAndFeel::ColorID
;
172 using ColorScheme
= LookAndFeel::ColorScheme
;
174 bool anyFromOtherTheme
= false;
175 for (auto id
: MakeEnumeratedRange(IntID::End
)) {
176 if (!IsNeededInChildProcess(id
)) {
179 if (aDifferentTheme
&& aImpl
->FromParentTheme(id
) != aFromParentTheme
) {
180 anyFromOtherTheme
= true;
184 nsresult rv
= aImpl
->NativeGetInt(id
, theInt
);
185 AddToMap(aLf
->tables().ints(), aLf
->tables().intMap(), id
,
186 NS_SUCCEEDED(rv
) ? Some(theInt
) : Nothing
{});
189 for (auto id
: MakeEnumeratedRange(ColorID::End
)) {
190 if (aDifferentTheme
&& aImpl
->FromParentTheme(id
) != aFromParentTheme
) {
191 anyFromOtherTheme
= true;
195 nsresult rv
= aImpl
->NativeGetColor(id
, ColorScheme::Light
, theColor
);
196 AddToMap(aLf
->tables().lightColors(), aLf
->tables().lightColorMap(), id
,
197 NS_SUCCEEDED(rv
) ? Some(theColor
) : Nothing
{});
198 rv
= aImpl
->NativeGetColor(id
, ColorScheme::Dark
, theColor
);
199 AddToMap(aLf
->tables().darkColors(), aLf
->tables().darkColorMap(), id
,
200 NS_SUCCEEDED(rv
) ? Some(theColor
) : Nothing
{});
203 // The rest of IDs only come from the child content theme.
204 if (aFromParentTheme
) {
205 return anyFromOtherTheme
;
208 for (auto id
: MakeEnumeratedRange(FloatID::End
)) {
210 nsresult rv
= aImpl
->NativeGetFloat(id
, theFloat
);
211 AddToMap(aLf
->tables().floats(), aLf
->tables().floatMap(), id
,
212 NS_SUCCEEDED(rv
) ? Some(theFloat
) : Nothing
{});
215 for (auto id
: MakeEnumeratedRange(FontID::End
)) {
216 gfxFontStyle fontStyle
{};
219 bool rv
= aImpl
->NativeGetFont(id
, name
, fontStyle
);
220 Maybe
<LookAndFeelFont
> maybeFont
;
223 nsXPLookAndFeel::StyleToLookAndFeelFont(name
, fontStyle
));
225 AddToMap(aLf
->tables().fonts(), aLf
->tables().fontMap(), id
,
226 std::move(maybeFont
));
229 return anyFromOtherTheme
;
233 const FullLookAndFeel
* RemoteLookAndFeel::ExtractData() {
234 MOZ_ASSERT(XRE_IsParentProcess(),
235 "Only parent processes should be extracting LookAndFeel data");
237 if (sCachedLookAndFeelData
) {
238 return sCachedLookAndFeelData
;
241 static bool sInitialized
= false;
244 ClearOnShutdown(&sCachedLookAndFeelData
);
247 FullLookAndFeel
* lf
= new FullLookAndFeel
{};
248 nsXPLookAndFeel
* impl
= nsXPLookAndFeel::GetInstance();
250 bool anyFromParent
= false;
251 impl
->WithThemeConfiguredForContent([&](const LookAndFeelTheme
& aTheme
,
252 bool aDifferentTheme
) {
254 AddIDsToMap(impl
, lf
, aDifferentTheme
, /* aFromParentTheme = */ false);
255 MOZ_ASSERT_IF(anyFromParent
, aDifferentTheme
);
256 lf
->tables().passwordChar() = impl
->GetPasswordCharacterImpl();
257 lf
->tables().passwordEcho() = impl
->GetEchoPasswordImpl();
258 #ifdef MOZ_WIDGET_GTK
259 lf
->theme() = aTheme
;
264 AddIDsToMap(impl
, lf
, /* aDifferentTheme = */ true,
265 /* aFromParentTheme = */ true);
268 // This assignment to sCachedLookAndFeelData must be done after the
269 // WithThemeConfiguredForContent call, since it can end up calling RefreshImpl
270 // on the LookAndFeel, which will clear out sCachedTables.
271 sCachedLookAndFeelData
= lf
;
272 return sCachedLookAndFeelData
;
275 void RemoteLookAndFeel::ClearCachedData() {
276 MOZ_ASSERT(XRE_IsParentProcess());
277 sCachedLookAndFeelData
= nullptr;
280 } // namespace mozilla::widget