Bug 1688354 [wpt PR 27298] - Treat 'rem' as an absolute unit for font size, a=testonly
[gecko.git] / dom / quota / QuotaCommon.cpp
blob16e73f4ed67cc294e263f8d29a98a5e2822e0b6c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "QuotaCommon.h"
9 #include "mozIStorageConnection.h"
10 #include "mozIStorageStatement.h"
11 #include "mozilla/ErrorNames.h"
12 #include "mozilla/Logging.h"
13 #include "mozilla/Telemetry.h"
14 #include "mozilla/TelemetryComms.h"
15 #include "mozilla/TelemetryEventEnums.h"
16 #include "mozilla/TextUtils.h"
17 #include "nsIConsoleService.h"
18 #include "nsIFile.h"
19 #include "nsServiceManagerUtils.h"
20 #include "nsStringFlags.h"
21 #include "nsTStringRepr.h"
22 #include "nsUnicharUtils.h"
23 #include "nsXPCOM.h"
24 #include "nsXULAppAPI.h"
26 #ifdef XP_WIN
27 # include "mozilla/Atomics.h"
28 # include "mozilla/ipc/BackgroundParent.h"
29 # include "mozilla/StaticPrefs_dom.h"
30 # include "nsILocalFileWin.h"
31 #endif
33 namespace mozilla::dom::quota {
35 using namespace mozilla::Telemetry;
37 namespace {
39 #ifdef DEBUG
40 constexpr auto kDSStoreFileName = u".DS_Store"_ns;
41 constexpr auto kDesktopFileName = u".desktop"_ns;
42 constexpr auto kDesktopIniFileName = u"desktop.ini"_ns;
43 constexpr auto kThumbsDbFileName = u"thumbs.db"_ns;
44 #endif
46 #ifdef XP_WIN
47 Atomic<int32_t> gUseDOSDevicePathSyntax(-1);
48 #endif
50 LazyLogModule gLogger("QuotaManager");
52 void AnonymizeCString(nsACString& aCString, uint32_t aStart) {
53 MOZ_ASSERT(!aCString.IsEmpty());
54 MOZ_ASSERT(aStart < aCString.Length());
56 char* iter = aCString.BeginWriting() + aStart;
57 char* end = aCString.EndWriting();
59 while (iter != end) {
60 char c = *iter;
62 if (IsAsciiAlpha(c)) {
63 *iter = 'a';
64 } else if (IsAsciiDigit(c)) {
65 *iter = 'D';
68 ++iter;
72 } // namespace
74 const char kQuotaGenericDelimiter = '|';
76 #ifdef NIGHTLY_BUILD
77 const nsLiteralCString kQuotaInternalError = "internal"_ns;
78 const nsLiteralCString kQuotaExternalError = "external"_ns;
79 #endif
81 LogModule* GetQuotaManagerLogger() { return gLogger; }
83 void AnonymizeCString(nsACString& aCString) {
84 if (aCString.IsEmpty()) {
85 return;
87 AnonymizeCString(aCString, /* aStart */ 0);
90 void AnonymizeOriginString(nsACString& aOriginString) {
91 if (aOriginString.IsEmpty()) {
92 return;
95 int32_t start = aOriginString.FindChar(':');
96 if (start < 0) {
97 start = 0;
100 AnonymizeCString(aOriginString, start);
103 #ifdef XP_WIN
104 void CacheUseDOSDevicePathSyntaxPrefValue() {
105 MOZ_ASSERT(XRE_IsParentProcess());
106 AssertIsOnBackgroundThread();
108 if (gUseDOSDevicePathSyntax == -1) {
109 bool useDOSDevicePathSyntax =
110 StaticPrefs::dom_quotaManager_useDOSDevicePathSyntax_DoNotUseDirectly();
111 gUseDOSDevicePathSyntax = useDOSDevicePathSyntax ? 1 : 0;
114 #endif
116 Result<nsCOMPtr<nsIFile>, nsresult> QM_NewLocalFile(const nsAString& aPath) {
117 QM_TRY_UNWRAP(auto file,
118 ToResultInvoke<nsCOMPtr<nsIFile>>(NS_NewLocalFile, aPath,
119 /* aFollowLinks */ false),
120 QM_PROPAGATE, [&aPath](const nsresult rv) {
121 QM_WARNING("Failed to construct a file for path (%s)",
122 NS_ConvertUTF16toUTF8(aPath).get());
125 #ifdef XP_WIN
126 MOZ_ASSERT(gUseDOSDevicePathSyntax != -1);
128 if (gUseDOSDevicePathSyntax) {
129 QM_TRY_INSPECT(const auto& winFile,
130 ToResultGet<nsCOMPtr<nsILocalFileWin>>(
131 MOZ_SELECT_OVERLOAD(do_QueryInterface), file));
133 MOZ_ASSERT(winFile);
134 winFile->SetUseDOSDevicePathSyntax(true);
136 #endif
138 return file;
141 nsDependentCSubstring GetLeafName(const nsACString& aPath) {
142 nsACString::const_iterator start, end;
143 aPath.BeginReading(start);
144 aPath.EndReading(end);
146 bool found = RFindInReadable("/"_ns, start, end);
147 if (found) {
148 start = end;
151 aPath.EndReading(end);
153 return nsDependentCSubstring(start.get(), end.get());
156 Result<nsCOMPtr<nsIFile>, nsresult> CloneFileAndAppend(
157 nsIFile& aDirectory, const nsAString& aPathElement) {
158 QM_TRY_UNWRAP(auto resultFile, MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<nsIFile>,
159 aDirectory, Clone));
161 QM_TRY(resultFile->Append(aPathElement));
163 return resultFile;
166 Result<nsIFileKind, nsresult> GetDirEntryKind(nsIFile& aFile) {
167 QM_TRY_RETURN(
168 MOZ_TO_RESULT_INVOKE(aFile, IsDirectory)
169 .map([](const bool isDirectory) {
170 return isDirectory ? nsIFileKind::ExistsAsDirectory
171 : nsIFileKind::ExistsAsFile;
173 .orElse([](const nsresult rv) -> Result<nsIFileKind, nsresult> {
174 if (rv == NS_ERROR_FILE_NOT_FOUND ||
175 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
176 return nsIFileKind::DoesNotExist;
179 return Err(rv);
180 }));
183 Result<nsCOMPtr<mozIStorageStatement>, nsresult> CreateStatement(
184 mozIStorageConnection& aConnection, const nsACString& aStatementString) {
185 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageStatement>,
186 aConnection, CreateStatement,
187 aStatementString));
190 template <SingleStepResult ResultHandling>
191 Result<SingleStepSuccessType<ResultHandling>, nsresult> ExecuteSingleStep(
192 nsCOMPtr<mozIStorageStatement>&& aStatement) {
193 QM_TRY_INSPECT(const bool& hasResult,
194 MOZ_TO_RESULT_INVOKE(aStatement, ExecuteStep));
196 if constexpr (ResultHandling == SingleStepResult::AssertHasResult) {
197 MOZ_ASSERT(hasResult);
198 (void)hasResult;
200 return WrapNotNullUnchecked(std::move(aStatement));
201 } else {
202 return hasResult ? std::move(aStatement) : nullptr;
206 template Result<SingleStepSuccessType<SingleStepResult::AssertHasResult>,
207 nsresult>
208 ExecuteSingleStep<SingleStepResult::AssertHasResult>(
209 nsCOMPtr<mozIStorageStatement>&&);
211 template Result<SingleStepSuccessType<SingleStepResult::ReturnNullIfNoResult>,
212 nsresult>
213 ExecuteSingleStep<SingleStepResult::ReturnNullIfNoResult>(
214 nsCOMPtr<mozIStorageStatement>&&);
216 template <SingleStepResult ResultHandling>
217 Result<SingleStepSuccessType<ResultHandling>, nsresult>
218 CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection,
219 const nsACString& aStatementString) {
220 QM_TRY_UNWRAP(auto stmt, MOZ_TO_RESULT_INVOKE_TYPED(
221 nsCOMPtr<mozIStorageStatement>, aConnection,
222 CreateStatement, aStatementString));
224 return ExecuteSingleStep<ResultHandling>(std::move(stmt));
227 template Result<SingleStepSuccessType<SingleStepResult::AssertHasResult>,
228 nsresult>
229 CreateAndExecuteSingleStepStatement<SingleStepResult::AssertHasResult>(
230 mozIStorageConnection& aConnection, const nsACString& aStatementString);
232 template Result<SingleStepSuccessType<SingleStepResult::ReturnNullIfNoResult>,
233 nsresult>
234 CreateAndExecuteSingleStepStatement<SingleStepResult::ReturnNullIfNoResult>(
235 mozIStorageConnection& aConnection, const nsACString& aStatementString);
237 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
238 MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sQueryValue;
239 MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sContextValue;
241 /* static */
242 auto ScopedLogExtraInfo::FindSlot(const char* aTag) {
243 // XXX For now, don't use a real map but just allow the known tag values.
245 if (aTag == kTagQuery) {
246 return &sQueryValue;
249 if (aTag == kTagContext) {
250 return &sContextValue;
253 MOZ_CRASH("Unknown tag!");
256 ScopedLogExtraInfo::~ScopedLogExtraInfo() {
257 if (mTag) {
258 MOZ_ASSERT(&mCurrentValue == FindSlot(mTag)->get(),
259 "Bad scoping of ScopedLogExtraInfo, must not be interleaved!");
261 FindSlot(mTag)->set(mPreviousValue);
265 ScopedLogExtraInfo::ScopedLogExtraInfo(ScopedLogExtraInfo&& aOther)
266 : mTag(aOther.mTag),
267 mPreviousValue(aOther.mPreviousValue),
268 mCurrentValue(std::move(aOther.mCurrentValue)) {
269 aOther.mTag = nullptr;
270 FindSlot(mTag)->set(&mCurrentValue);
273 /* static */ ScopedLogExtraInfo::ScopedLogExtraInfoMap
274 ScopedLogExtraInfo::GetExtraInfoMap() {
275 // This could be done in a cheaper way, but this is never called on a hot
276 // path, so we anticipate using a real map inside here to make use simpler for
277 // the caller(s).
279 ScopedLogExtraInfoMap map;
280 if (XRE_IsParentProcess()) {
281 if (sQueryValue.get()) {
282 map.emplace(kTagQuery, sQueryValue.get());
285 if (sContextValue.get()) {
286 map.emplace(kTagContext, sContextValue.get());
289 return map;
292 /* static */ void ScopedLogExtraInfo::Initialize() {
293 MOZ_ALWAYS_TRUE(sQueryValue.init());
294 MOZ_ALWAYS_TRUE(sContextValue.init());
297 void ScopedLogExtraInfo::AddInfo() {
298 auto* slot = FindSlot(mTag);
299 MOZ_ASSERT(slot);
300 mPreviousValue = slot->get();
302 slot->set(&mCurrentValue);
304 #endif
306 void LogError(const nsLiteralCString& aModule, const nsACString& aExpr,
307 const nsACString& aSourceFile, int32_t aSourceLine,
308 Maybe<nsresult> aRv) {
309 nsAutoCString extraInfosString;
311 nsAutoCString rvName;
312 if (aRv) {
313 if (NS_ERROR_GET_MODULE(*aRv) == NS_ERROR_MODULE_WIN32) {
314 // XXX We could also try to get the Win32 error name here.
315 rvName = nsPrintfCString("WIN32(0x%" PRIX16 ")", NS_ERROR_GET_CODE(*aRv));
316 } else {
317 rvName = mozilla::GetStaticErrorName(*aRv);
319 extraInfosString.AppendPrintf(
320 " failed with "
321 "result 0x%" PRIX32 "%s%s%s",
322 static_cast<uint32_t>(*aRv), !rvName.IsEmpty() ? " (" : "",
323 !rvName.IsEmpty() ? rvName.get() : "", !rvName.IsEmpty() ? ")" : "");
326 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
327 const auto& extraInfos = ScopedLogExtraInfo::GetExtraInfoMap();
328 for (const auto& item : extraInfos) {
329 extraInfosString.Append(", "_ns + nsDependentCString(item.first) + "="_ns +
330 *item.second);
332 #endif
334 #ifdef DEBUG
335 NS_DebugBreak(NS_DEBUG_WARNING, nsAutoCString(aModule + " failure"_ns).get(),
336 (extraInfosString.IsEmpty()
337 ? nsPromiseFlatCString(aExpr)
338 : static_cast<const nsCString&>(
339 nsAutoCString(aExpr + extraInfosString)))
340 .get(),
341 nsPromiseFlatCString(aSourceFile).get(), aSourceLine);
342 #endif
344 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
345 nsCOMPtr<nsIConsoleService> console =
346 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
347 if (console) {
348 NS_ConvertUTF8toUTF16 message(aModule + " failure: '"_ns + aExpr +
349 "', file "_ns + GetLeafName(aSourceFile) +
350 ", line "_ns + IntToCString(aSourceLine) +
351 extraInfosString);
353 // The concatenation above results in a message like:
354 // QuotaManager failure: 'EXP', file XYZ, line N)
356 console->LogStringMessage(message.get());
359 # ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
360 if (const auto contextIt = extraInfos.find(ScopedLogExtraInfo::kTagContext);
361 contextIt != extraInfos.cend()) {
362 // For now, we don't include aExpr in the telemetry event. It might help to
363 // match locations across versions, but they might be large.
364 auto extra = Some([&] {
365 auto res = CopyableTArray<EventExtraEntry>{};
366 res.SetCapacity(5);
367 res.AppendElement(EventExtraEntry{"module"_ns, aModule});
368 res.AppendElement(EventExtraEntry{"source_file"_ns,
369 nsCString(GetLeafName(aSourceFile))});
370 res.AppendElement(
371 EventExtraEntry{"source_line"_ns, IntToCString(aSourceLine)});
372 res.AppendElement(EventExtraEntry{
373 "context"_ns, nsPromiseFlatCString{*contextIt->second}});
375 if (!rvName.IsEmpty()) {
376 res.AppendElement(EventExtraEntry{"result"_ns, nsCString{rvName}});
379 static Atomic<int32_t> sSequenceNumber{0};
381 res.AppendElement(
382 EventExtraEntry{"seq"_ns, IntToCString(++sSequenceNumber)});
384 return res;
385 }());
387 Telemetry::RecordEvent(Telemetry::EventID::DomQuotaTry_Error_Step,
388 Nothing(), extra);
390 # endif
391 #endif
394 #ifdef DEBUG
395 Result<bool, nsresult> WarnIfFileIsUnknown(nsIFile& aFile,
396 const char* aSourceFile,
397 const int32_t aSourceLine) {
398 nsString leafName;
399 nsresult rv = aFile.GetLeafName(leafName);
400 if (NS_WARN_IF(NS_FAILED(rv))) {
401 return Err(rv);
404 bool isDirectory;
405 rv = aFile.IsDirectory(&isDirectory);
406 if (NS_WARN_IF(NS_FAILED(rv))) {
407 return Err(rv);
410 if (!isDirectory) {
411 // Don't warn about OS metadata files. These files are only used in
412 // different platforms, but the profile can be shared across different
413 // operating systems, so we check it on all platforms.
414 if (leafName.Equals(kDSStoreFileName) ||
415 leafName.Equals(kDesktopFileName) ||
416 leafName.Equals(kDesktopIniFileName,
417 nsCaseInsensitiveStringComparator) ||
418 leafName.Equals(kThumbsDbFileName, nsCaseInsensitiveStringComparator)) {
419 return false;
422 // Don't warn about files starting with ".".
423 if (leafName.First() == char16_t('.')) {
424 return false;
428 NS_DebugBreak(
429 NS_DEBUG_WARNING,
430 nsPrintfCString("Something (%s) in the directory that doesn't belong!",
431 NS_ConvertUTF16toUTF8(leafName).get())
432 .get(),
433 nullptr, aSourceFile, aSourceLine);
435 return true;
437 #endif
439 } // namespace mozilla::dom::quota