Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / widget / windows / WinRegistry.cpp
blobb04ae1df45750cdca4f11b6f91c713be040680e3
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) {
13 MOZ_ASSERT(aParent);
14 DWORD disposition;
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) {
20 MOZ_ASSERT(aParent);
21 ::RegOpenKeyExW(aParent, aPath.get(), 0, (REGSAM)aMode, &mKey);
24 uint32_t Key::GetChildCount() const {
25 MOZ_ASSERT(mKey);
26 DWORD result = 0;
27 ::RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, &result, nullptr, nullptr,
28 nullptr, nullptr, nullptr, nullptr, nullptr);
29 return result;
32 bool Key::GetChildName(uint32_t aIndex, nsAString& aResult) const {
33 MOZ_ASSERT(mKey);
34 FILETIME lastWritten;
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) {
42 return false;
44 aResult.Assign(nameBuf, nameLen);
45 return true;
48 bool Key::RemoveChildKey(const nsString& aName) const {
49 MOZ_ASSERT(mKey);
50 return SUCCEEDED(RegDeleteKeyW(mKey, aName.get()));
53 uint32_t Key::GetValueCount() const {
54 MOZ_ASSERT(mKey);
55 DWORD result = 0;
56 ::RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
57 &result, nullptr, nullptr, nullptr, nullptr);
58 return result;
61 bool Key::GetValueName(uint32_t aIndex, nsAString& aResult) const {
62 MOZ_ASSERT(mKey);
63 wchar_t nameBuf[kMaxValueNameLen + 1];
64 DWORD nameLen = std::size(nameBuf);
66 LONG rv = RegEnumValueW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr,
67 nullptr, nullptr);
68 if (rv != ERROR_SUCCESS) {
69 return false;
71 aResult.Assign(nameBuf, nameLen);
72 return true;
75 ValueType Key::GetValueType(const nsString& aName) const {
76 MOZ_ASSERT(mKey);
77 DWORD result;
78 LONG rv =
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 {
84 MOZ_ASSERT(mKey);
85 return SUCCEEDED(RegDeleteValueW(mKey, aName.get()));
88 Maybe<uint32_t> Key::GetValueAsDword(const nsString& aName) const {
89 MOZ_ASSERT(mKey);
90 DWORD type;
91 DWORD value = 0;
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) {
96 return Nothing();
98 return Some(value);
101 bool Key::WriteValueAsDword(const nsString& aName, uint32_t aValue) {
102 MOZ_ASSERT(mKey);
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 {
108 MOZ_ASSERT(mKey);
109 DWORD type;
110 uint64_t value = 0;
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) {
115 return Nothing();
117 return Some(value);
120 bool Key::WriteValueAsQword(const nsString& aName, uint64_t aValue) {
121 MOZ_ASSERT(mKey);
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 {
128 MOZ_ASSERT(mKey);
129 DWORD type;
130 DWORD size;
131 LONG rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type, nullptr, &size);
132 if (FAILED(rv) || type != REG_BINARY) {
133 return false;
135 if (!aResult.SetLength(size, fallible)) {
136 return false;
138 rv = RegQueryValueExW(mKey, aName.get(), nullptr, nullptr, aResult.Elements(),
139 &size);
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));
149 return result;
152 bool Key::WriteValueAsBinary(const nsString& aName,
153 Span<const uint8_t> aValue) {
154 MOZ_ASSERT(mKey);
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) {
160 switch (aType) {
161 case REG_SZ:
162 return bool(aFlags & StringFlags::Sz);
163 case REG_EXPAND_SZ:
164 return bool(aFlags & StringFlags::ExpandSz);
165 case REG_MULTI_SZ:
166 return bool(aFlags & StringFlags::LegacyMultiSz);
167 default:
168 return false;
172 bool Key::GetValueAsString(const nsString& aName, nsString& aResult,
173 StringFlags aFlags) const {
174 MOZ_ASSERT(mKey);
175 DWORD type;
176 DWORD size;
177 LONG rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type, nullptr, &size);
178 if (FAILED(rv) || !IsStringType(type, aFlags)) {
179 return false;
181 if (!size) {
182 aResult.Truncate();
183 return true;
185 // The buffer size must be a multiple of 2.
186 if (NS_WARN_IF(size % 2 != 0)) {
187 return false;
189 size_t resultLen = size / 2;
191 auto handleOrError =
192 aResult.BulkWrite(resultLen, 0, /* aAllowShrinking = */ false);
193 if (NS_WARN_IF(handleOrError.isErr())) {
194 return false;
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)) {
200 return false;
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
205 // position.
206 aResult.Truncate(*len - 1);
209 if (type == REG_EXPAND_SZ && (aFlags & StringFlags::ExpandEnvironment)) {
210 resultLen = ExpandEnvironmentStringsW(aResult.get(), nullptr, 0);
211 if (resultLen > 1) {
212 nsString expandedResult;
213 // |resultLen| includes the terminating null character
214 resultLen--;
215 if (!expandedResult.SetLength(resultLen, fallible)) {
216 return false;
218 resultLen = ExpandEnvironmentStringsW(aResult.get(), expandedResult.get(),
219 resultLen + 1);
220 if (resultLen <= 0) {
221 return false;
223 aResult = std::move(expandedResult);
224 } else if (resultLen == 1) {
225 // It apparently expands to nothing (just a null terminator).
226 resultLen = 0;
227 aResult.Truncate();
230 return true;
233 Maybe<nsString> Key::GetValueAsString(const nsString& aName,
234 StringFlags aFlags) const {
235 nsString value;
236 Maybe<nsString> result;
237 if (GetValueAsString(aName, value, aFlags)) {
238 result.emplace(std::move(value));
240 return result;
243 Maybe<uint32_t> Key::GetValueAsString(const nsString& aName,
244 Span<char16_t> aBuffer,
245 StringFlags aFlags) const {
246 MOZ_ASSERT(mKey);
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();
252 DWORD type;
253 HRESULT rv = RegQueryValueExW(mKey, aName.get(), nullptr, &type,
254 (LPBYTE)aBuffer.data(), &size);
255 if (FAILED(rv)) {
256 return Nothing();
258 if (!IsStringType(type, aFlags)) {
259 return Nothing();
261 uint32_t len = size ? size / sizeof(char16_t) - 1 : 0;
262 aBuffer[len] = 0;
263 return Some(len);
266 KeyWatcher::KeyWatcher(Key&& aKey,
267 nsISerialEventTarget* aTargetSerialEventTarget,
268 Callback&& aCallback)
269 : mKey(std::move(aKey)),
270 mEventTarget(aTargetSerialEventTarget),
271 mCallback(std::move(aCallback)) {
272 MOZ_ASSERT(mKey);
273 MOZ_ASSERT(mEventTarget);
274 MOZ_ASSERT(mCallback);
275 mEvent = CreateEvent(nullptr, /* bManualReset = */ FALSE,
276 /* bInitialState = */ FALSE, nullptr);
277 if (NS_WARN_IF(!mEvent)) {
278 return;
281 if (NS_WARN_IF(!Register())) {
282 return;
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);
293 watcher->Register();
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
303 // notification.
304 bool KeyWatcher::Register() {
305 MOZ_ASSERT(mEvent);
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;
309 HRESULT rv =
310 RegNotifyChangeKeyValue(mKey.RawKey(), /* bWatchSubtree = */ TRUE, flags,
311 mEvent, /* fAsynchronous = */ TRUE);
312 return !NS_WARN_IF(FAILED(rv));
315 KeyWatcher::~KeyWatcher() {
316 if (mWaitObject) {
317 UnregisterWait(mWaitObject);
318 CloseHandle(mWaitObject);
320 if (mEvent) {
321 CloseHandle(mEvent);
325 } // namespace mozilla::widget::WinRegistry