1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sts=2 sw=2 et cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "WinRegistry.h"
8 #include "nsThreadUtils.h"
10 namespace mozilla::widget::WinRegistry
{
12 Key::Key(HKEY aParent
, const nsString
& aPath
, KeyMode aMode
, CreateFlag
) {
15 ::RegCreateKeyExW(aParent
, aPath
.get(), 0, nullptr, REG_OPTION_NON_VOLATILE
,
16 (REGSAM
)aMode
, nullptr, &mKey
, &disposition
);
19 Key::Key(HKEY aParent
, const nsString
& aPath
, KeyMode aMode
) {
21 ::RegOpenKeyExW(aParent
, aPath
.get(), 0, (REGSAM
)aMode
, &mKey
);
24 uint32_t Key::GetChildCount() const {
27 ::RegQueryInfoKeyW(mKey
, nullptr, nullptr, nullptr, &result
, nullptr, nullptr,
28 nullptr, nullptr, nullptr, nullptr, nullptr);
32 bool Key::GetChildName(uint32_t aIndex
, nsAString
& aResult
) const {
36 wchar_t nameBuf
[kMaxKeyNameLen
+ 1];
37 DWORD nameLen
= std::size(nameBuf
);
39 LONG rv
= RegEnumKeyExW(mKey
, aIndex
, nameBuf
, &nameLen
, nullptr, nullptr,
40 nullptr, &lastWritten
);
41 if (rv
!= ERROR_SUCCESS
) {
44 aResult
.Assign(nameBuf
, nameLen
);
48 bool Key::RemoveChildKey(const nsString
& aName
) const {
50 return SUCCEEDED(RegDeleteKeyW(mKey
, aName
.get()));
53 uint32_t Key::GetValueCount() const {
56 ::RegQueryInfoKeyW(mKey
, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
57 &result
, nullptr, nullptr, nullptr, nullptr);
61 bool Key::GetValueName(uint32_t aIndex
, nsAString
& aResult
) const {
63 wchar_t nameBuf
[kMaxValueNameLen
+ 1];
64 DWORD nameLen
= std::size(nameBuf
);
66 LONG rv
= RegEnumValueW(mKey
, aIndex
, nameBuf
, &nameLen
, nullptr, nullptr,
68 if (rv
!= ERROR_SUCCESS
) {
71 aResult
.Assign(nameBuf
, nameLen
);
75 ValueType
Key::GetValueType(const nsString
& aName
) const {
79 RegQueryValueExW(mKey
, aName
.get(), nullptr, &result
, nullptr, nullptr);
80 return SUCCEEDED(rv
) ? ValueType(result
) : ValueType::None
;
83 bool Key::RemoveValue(const nsString
& aName
) const {
85 return SUCCEEDED(RegDeleteValueW(mKey
, aName
.get()));
88 Maybe
<uint32_t> Key::GetValueAsDword(const nsString
& aName
) const {
92 DWORD size
= sizeof(DWORD
);
93 HRESULT rv
= RegQueryValueExW(mKey
, aName
.get(), nullptr, &type
,
94 (LPBYTE
)&value
, &size
);
95 if (FAILED(rv
) || type
!= REG_DWORD
) {
101 bool Key::WriteValueAsDword(const nsString
& aName
, uint32_t aValue
) {
103 return SUCCEEDED(RegSetValueExW(mKey
, aName
.get(), 0, REG_DWORD
,
104 (const BYTE
*)&aValue
, sizeof(aValue
)));
107 Maybe
<uint64_t> Key::GetValueAsQword(const nsString
& aName
) const {
111 DWORD size
= sizeof(uint64_t);
112 HRESULT rv
= RegQueryValueExW(mKey
, aName
.get(), nullptr, &type
,
113 (LPBYTE
)&value
, &size
);
114 if (FAILED(rv
) || type
!= REG_QWORD
) {
120 bool Key::WriteValueAsQword(const nsString
& aName
, uint64_t aValue
) {
122 return SUCCEEDED(RegSetValueExW(mKey
, aName
.get(), 0, REG_QWORD
,
123 (const BYTE
*)&aValue
, sizeof(aValue
)));
126 bool Key::GetValueAsBinary(const nsString
& aName
,
127 nsTArray
<uint8_t>& aResult
) const {
131 LONG rv
= RegQueryValueExW(mKey
, aName
.get(), nullptr, &type
, nullptr, &size
);
132 if (FAILED(rv
) || type
!= REG_BINARY
) {
135 if (!aResult
.SetLength(size
, fallible
)) {
138 rv
= RegQueryValueExW(mKey
, aName
.get(), nullptr, nullptr, aResult
.Elements(),
140 return SUCCEEDED(rv
);
143 Maybe
<nsTArray
<uint8_t>> Key::GetValueAsBinary(const nsString
& aName
) const {
144 nsTArray
<uint8_t> value
;
145 Maybe
<nsTArray
<uint8_t>> result
;
146 if (GetValueAsBinary(aName
, value
)) {
147 result
.emplace(std::move(value
));
152 bool Key::WriteValueAsBinary(const nsString
& aName
,
153 Span
<const uint8_t> aValue
) {
155 return SUCCEEDED(RegSetValueExW(mKey
, aName
.get(), 0, REG_BINARY
,
156 (const BYTE
*)aValue
.data(), aValue
.size()));
159 static bool IsStringType(DWORD aType
, StringFlags aFlags
) {
162 return bool(aFlags
& StringFlags::Sz
);
164 return bool(aFlags
& StringFlags::ExpandSz
);
166 return bool(aFlags
& StringFlags::LegacyMultiSz
);
172 bool Key::GetValueAsString(const nsString
& aName
, nsString
& aResult
,
173 StringFlags aFlags
) const {
177 LONG rv
= RegQueryValueExW(mKey
, aName
.get(), nullptr, &type
, nullptr, &size
);
178 if (FAILED(rv
) || !IsStringType(type
, aFlags
)) {
185 // The buffer size must be a multiple of 2.
186 if (NS_WARN_IF(size
% 2 != 0)) {
189 size_t resultLen
= size
/ 2;
192 aResult
.BulkWrite(resultLen
, 0, /* aAllowShrinking = */ false);
193 if (NS_WARN_IF(handleOrError
.isErr())) {
196 auto handle
= handleOrError
.unwrap();
197 auto len
= GetValueAsString(aName
, {handle
.Elements(), handle
.Length() + 1},
198 aFlags
& ~StringFlags::ExpandEnvironment
);
199 if (NS_WARN_IF(!len
)) {
202 handle
.Finish(*len
, /* aAllowShrinking = */ false);
203 if (*len
&& !aResult
.CharAt(*len
- 1)) {
204 // The string passed to us had a null terminator in the final
206 aResult
.Truncate(*len
- 1);
209 if (type
== REG_EXPAND_SZ
&& (aFlags
& StringFlags::ExpandEnvironment
)) {
210 resultLen
= ExpandEnvironmentStringsW(aResult
.get(), nullptr, 0);
212 nsString expandedResult
;
213 // |resultLen| includes the terminating null character
215 if (!expandedResult
.SetLength(resultLen
, fallible
)) {
218 resultLen
= ExpandEnvironmentStringsW(aResult
.get(), expandedResult
.get(),
220 if (resultLen
<= 0) {
223 aResult
= std::move(expandedResult
);
224 } else if (resultLen
== 1) {
225 // It apparently expands to nothing (just a null terminator).
233 Maybe
<nsString
> Key::GetValueAsString(const nsString
& aName
,
234 StringFlags aFlags
) const {
236 Maybe
<nsString
> result
;
237 if (GetValueAsString(aName
, value
, aFlags
)) {
238 result
.emplace(std::move(value
));
243 Maybe
<uint32_t> Key::GetValueAsString(const nsString
& aName
,
244 Span
<char16_t
> aBuffer
,
245 StringFlags aFlags
) const {
247 MOZ_ASSERT(aBuffer
.Length(), "Empty buffer?");
248 MOZ_ASSERT(!(aFlags
& StringFlags::ExpandEnvironment
),
249 "Environment expansion not performed on a single buffer");
251 DWORD size
= aBuffer
.LengthBytes();
253 HRESULT rv
= RegQueryValueExW(mKey
, aName
.get(), nullptr, &type
,
254 (LPBYTE
)aBuffer
.data(), &size
);
258 if (!IsStringType(type
, aFlags
)) {
261 uint32_t len
= size
? size
/ sizeof(char16_t
) - 1 : 0;
266 KeyWatcher::KeyWatcher(Key
&& aKey
,
267 nsISerialEventTarget
* aTargetSerialEventTarget
,
268 Callback
&& aCallback
)
269 : mKey(std::move(aKey
)),
270 mEventTarget(aTargetSerialEventTarget
),
271 mCallback(std::move(aCallback
)) {
273 MOZ_ASSERT(mEventTarget
);
274 MOZ_ASSERT(mCallback
);
275 mEvent
= CreateEvent(nullptr, /* bManualReset = */ FALSE
,
276 /* bInitialState = */ FALSE
, nullptr);
277 if (NS_WARN_IF(!mEvent
)) {
281 if (NS_WARN_IF(!Register())) {
285 // The callback only dispatches to the relevant event target, so we can use
286 // WT_EXECUTEINWAITTHREAD.
287 RegisterWaitForSingleObject(&mWaitObject
, mEvent
, WatchCallback
, this,
288 INFINITE
, WT_EXECUTEINWAITTHREAD
);
291 void KeyWatcher::WatchCallback(void* aContext
, BOOLEAN
) {
292 auto* watcher
= static_cast<KeyWatcher
*>(aContext
);
294 watcher
->mEventTarget
->Dispatch(
295 NS_NewRunnableFunction("KeyWatcher callback", watcher
->mCallback
));
298 // As per the documentation in:
299 // https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regnotifychangekeyvalue
301 // This function detects a single change. After the caller receives a
302 // notification event, it should call the function again to receive the next
304 bool KeyWatcher::Register() {
306 DWORD flags
= REG_NOTIFY_CHANGE_NAME
| REG_NOTIFY_CHANGE_ATTRIBUTES
|
307 REG_NOTIFY_CHANGE_LAST_SET
| REG_NOTIFY_CHANGE_SECURITY
|
308 REG_NOTIFY_THREAD_AGNOSTIC
;
310 RegNotifyChangeKeyValue(mKey
.RawKey(), /* bWatchSubtree = */ TRUE
, flags
,
311 mEvent
, /* fAsynchronous = */ TRUE
);
312 return !NS_WARN_IF(FAILED(rv
));
315 KeyWatcher::~KeyWatcher() {
317 UnregisterWait(mWaitObject
);
318 CloseHandle(mWaitObject
);
325 } // namespace mozilla::widget::WinRegistry