Bug 1700051: part 35) Reduce accessibility of `mSoftText.mDOMMapping` to `private...
[gecko.git] / dom / quota / QuotaCommon.cpp
blob6890bed2ba76dc6de04369260315053a7d534718
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(QM_OR_ELSE_WARN(
168 MOZ_TO_RESULT_INVOKE(aFile, IsDirectory).map([](const bool isDirectory) {
169 return isDirectory ? nsIFileKind::ExistsAsDirectory
170 : nsIFileKind::ExistsAsFile;
172 ([](const nsresult rv) -> Result<nsIFileKind, nsresult> {
173 if (rv == NS_ERROR_FILE_NOT_FOUND ||
174 rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
175 #ifdef WIN32
176 // We treat ERROR_FILE_CORRUPT as if the file did not exist at
177 // all.
178 || (NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_WIN32 &&
179 NS_ERROR_GET_CODE(rv) == ERROR_FILE_CORRUPT)
180 #endif
182 return nsIFileKind::DoesNotExist;
185 return Err(rv);
186 })));
189 Result<nsCOMPtr<mozIStorageStatement>, nsresult> CreateStatement(
190 mozIStorageConnection& aConnection, const nsACString& aStatementString) {
191 QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_TYPED(nsCOMPtr<mozIStorageStatement>,
192 aConnection, CreateStatement,
193 aStatementString));
196 template <SingleStepResult ResultHandling>
197 Result<SingleStepSuccessType<ResultHandling>, nsresult> ExecuteSingleStep(
198 nsCOMPtr<mozIStorageStatement>&& aStatement) {
199 QM_TRY_INSPECT(const bool& hasResult,
200 MOZ_TO_RESULT_INVOKE(aStatement, ExecuteStep));
202 if constexpr (ResultHandling == SingleStepResult::AssertHasResult) {
203 MOZ_ASSERT(hasResult);
204 (void)hasResult;
206 return WrapNotNullUnchecked(std::move(aStatement));
207 } else {
208 return hasResult ? std::move(aStatement) : nullptr;
212 template Result<SingleStepSuccessType<SingleStepResult::AssertHasResult>,
213 nsresult>
214 ExecuteSingleStep<SingleStepResult::AssertHasResult>(
215 nsCOMPtr<mozIStorageStatement>&&);
217 template Result<SingleStepSuccessType<SingleStepResult::ReturnNullIfNoResult>,
218 nsresult>
219 ExecuteSingleStep<SingleStepResult::ReturnNullIfNoResult>(
220 nsCOMPtr<mozIStorageStatement>&&);
222 template <SingleStepResult ResultHandling>
223 Result<SingleStepSuccessType<ResultHandling>, nsresult>
224 CreateAndExecuteSingleStepStatement(mozIStorageConnection& aConnection,
225 const nsACString& aStatementString) {
226 QM_TRY_UNWRAP(auto stmt, MOZ_TO_RESULT_INVOKE_TYPED(
227 nsCOMPtr<mozIStorageStatement>, aConnection,
228 CreateStatement, aStatementString));
230 return ExecuteSingleStep<ResultHandling>(std::move(stmt));
233 template Result<SingleStepSuccessType<SingleStepResult::AssertHasResult>,
234 nsresult>
235 CreateAndExecuteSingleStepStatement<SingleStepResult::AssertHasResult>(
236 mozIStorageConnection& aConnection, const nsACString& aStatementString);
238 template Result<SingleStepSuccessType<SingleStepResult::ReturnNullIfNoResult>,
239 nsresult>
240 CreateAndExecuteSingleStepStatement<SingleStepResult::ReturnNullIfNoResult>(
241 mozIStorageConnection& aConnection, const nsACString& aStatementString);
243 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
244 MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sQueryValue;
245 MOZ_THREAD_LOCAL(const nsACString*) ScopedLogExtraInfo::sContextValue;
247 /* static */
248 auto ScopedLogExtraInfo::FindSlot(const char* aTag) {
249 // XXX For now, don't use a real map but just allow the known tag values.
251 if (aTag == kTagQuery) {
252 return &sQueryValue;
255 if (aTag == kTagContext) {
256 return &sContextValue;
259 MOZ_CRASH("Unknown tag!");
262 ScopedLogExtraInfo::~ScopedLogExtraInfo() {
263 if (mTag) {
264 MOZ_ASSERT(&mCurrentValue == FindSlot(mTag)->get(),
265 "Bad scoping of ScopedLogExtraInfo, must not be interleaved!");
267 FindSlot(mTag)->set(mPreviousValue);
271 ScopedLogExtraInfo::ScopedLogExtraInfo(ScopedLogExtraInfo&& aOther)
272 : mTag(aOther.mTag),
273 mPreviousValue(aOther.mPreviousValue),
274 mCurrentValue(std::move(aOther.mCurrentValue)) {
275 aOther.mTag = nullptr;
276 FindSlot(mTag)->set(&mCurrentValue);
279 /* static */ ScopedLogExtraInfo::ScopedLogExtraInfoMap
280 ScopedLogExtraInfo::GetExtraInfoMap() {
281 // This could be done in a cheaper way, but this is never called on a hot
282 // path, so we anticipate using a real map inside here to make use simpler for
283 // the caller(s).
285 ScopedLogExtraInfoMap map;
286 if (XRE_IsParentProcess()) {
287 if (sQueryValue.get()) {
288 map.emplace(kTagQuery, sQueryValue.get());
291 if (sContextValue.get()) {
292 map.emplace(kTagContext, sContextValue.get());
295 return map;
298 /* static */ void ScopedLogExtraInfo::Initialize() {
299 MOZ_ALWAYS_TRUE(sQueryValue.init());
300 MOZ_ALWAYS_TRUE(sContextValue.init());
303 void ScopedLogExtraInfo::AddInfo() {
304 auto* slot = FindSlot(mTag);
305 MOZ_ASSERT(slot);
306 mPreviousValue = slot->get();
308 slot->set(&mCurrentValue);
310 #endif
312 namespace detail {
314 nsDependentCSubstring GetSourceTreeBase() {
315 static constexpr auto thisFileRelativeSourceFileName =
316 "/dom/quota/QuotaCommon.cpp"_ns;
318 static constexpr auto path = nsLiteralCString(__FILE__);
320 MOZ_ASSERT(StringEndsWith(path, thisFileRelativeSourceFileName));
321 return Substring(path, 0,
322 path.Length() - thisFileRelativeSourceFileName.Length());
325 nsDependentCSubstring MakeRelativeSourceFileName(
326 const nsACString& aSourceFile) {
327 static constexpr auto error = "ERROR"_ns;
329 static const auto sourceTreeBase = GetSourceTreeBase();
331 if (MOZ_LIKELY(StringBeginsWith(aSourceFile, sourceTreeBase))) {
332 return Substring(aSourceFile, sourceTreeBase.Length() + 1);
335 nsCString::const_iterator begin, end;
336 if (RFindInReadable("/"_ns, aSourceFile.BeginReading(begin),
337 aSourceFile.EndReading(end))) {
338 // Use the basename as a fallback, to avoid exposing any user parts of the
339 // path.
340 ++begin;
341 return Substring(begin, aSourceFile.EndReading(end));
344 return nsDependentCSubstring{static_cast<mozilla::Span<const char>>(
345 static_cast<const nsCString&>(error))};
348 } // namespace detail
350 void LogError(const nsACString& aExpr, const Maybe<nsresult> aRv,
351 const nsACString& aSourceFile, const int32_t aSourceLine,
352 const Severity aSeverity) {
353 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
354 nsAutoCString extraInfosString;
356 nsAutoCString rvName;
357 if (aRv) {
358 if (NS_ERROR_GET_MODULE(*aRv) == NS_ERROR_MODULE_WIN32) {
359 // XXX We could also try to get the Win32 error name here.
360 rvName = nsPrintfCString("WIN32(0x%" PRIX16 ")", NS_ERROR_GET_CODE(*aRv));
361 } else {
362 rvName = mozilla::GetStaticErrorName(*aRv);
364 extraInfosString.AppendPrintf(
365 " failed with "
366 "result 0x%" PRIX32 "%s%s%s",
367 static_cast<uint32_t>(*aRv), !rvName.IsEmpty() ? " (" : "",
368 !rvName.IsEmpty() ? rvName.get() : "", !rvName.IsEmpty() ? ")" : "");
371 const auto relativeSourceFile =
372 detail::MakeRelativeSourceFileName(aSourceFile);
374 const auto severityString = [&aSeverity]() -> nsLiteralCString {
375 switch (aSeverity) {
376 case Severity::Error:
377 return "ERROR"_ns;
378 case Severity::Warning:
379 return "WARNING"_ns;
380 case Severity::Note:
381 return "NOTE"_ns;
383 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad severity value!");
384 }();
385 #endif
387 #ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
388 const auto& extraInfos = ScopedLogExtraInfo::GetExtraInfoMap();
389 for (const auto& item : extraInfos) {
390 extraInfosString.Append(", "_ns + nsDependentCString(item.first) + "="_ns +
391 *item.second);
393 #endif
395 #ifdef DEBUG
396 NS_DebugBreak(
397 NS_DEBUG_WARNING,
398 nsAutoCString("QM_TRY failure ("_ns + severityString + ")"_ns).get(),
399 (extraInfosString.IsEmpty() ? nsPromiseFlatCString(aExpr)
400 : static_cast<const nsCString&>(nsAutoCString(
401 aExpr + extraInfosString)))
402 .get(),
403 nsPromiseFlatCString(relativeSourceFile).get(), aSourceLine);
404 #endif
406 #if defined(EARLY_BETA_OR_EARLIER) || defined(DEBUG)
407 nsCOMPtr<nsIConsoleService> console =
408 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
409 if (console) {
410 NS_ConvertUTF8toUTF16 message("QM_TRY failure ("_ns + severityString +
411 ")"_ns + ": '"_ns + aExpr + "' at "_ns +
412 relativeSourceFile + ":"_ns +
413 IntToCString(aSourceLine) + extraInfosString);
415 // The concatenation above results in a message like:
416 // QM_TRY failure: 'EXPR' failed with result NS_ERROR_FAILURE at
417 // dom/quota/Foo.cpp:12345
419 console->LogStringMessage(message.get());
422 # ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO
423 if (const auto contextIt = extraInfos.find(ScopedLogExtraInfo::kTagContext);
424 contextIt != extraInfos.cend()) {
425 // For now, we don't include aExpr in the telemetry event. It might help to
426 // match locations across versions, but they might be large.
427 auto extra = Some([&] {
428 auto res = CopyableTArray<EventExtraEntry>{};
429 res.SetCapacity(6);
430 // TODO We could still fill the module field, based on the source
431 // directory, but we probably don't need to.
432 // res.AppendElement(EventExtraEntry{"module"_ns, aModule});
433 res.AppendElement(
434 EventExtraEntry{"source_file"_ns, nsCString(relativeSourceFile)});
435 res.AppendElement(
436 EventExtraEntry{"source_line"_ns, IntToCString(aSourceLine)});
437 res.AppendElement(EventExtraEntry{
438 "context"_ns, nsPromiseFlatCString{*contextIt->second}});
439 res.AppendElement(EventExtraEntry{"severity"_ns, severityString});
441 if (!rvName.IsEmpty()) {
442 res.AppendElement(EventExtraEntry{"result"_ns, nsCString{rvName}});
445 // The sequence number is currently per-process, and we don't record the
446 // thread id. This is ok as long as we only record during storage
447 // initialization, and that happens (mostly) from a single thread. It's
448 // safe even if errors from multiple threads are interleaved, but the data
449 // will be hard to analyze then. In that case, we should record a pair of
450 // thread id and thread-local sequence number.
451 static Atomic<int32_t> sSequenceNumber{0};
453 res.AppendElement(
454 EventExtraEntry{"seq"_ns, IntToCString(++sSequenceNumber)});
456 return res;
457 }());
459 Telemetry::RecordEvent(Telemetry::EventID::DomQuotaTry_Error_Step,
460 Nothing(), extra);
462 # endif
463 #endif
466 #ifdef DEBUG
467 Result<bool, nsresult> WarnIfFileIsUnknown(nsIFile& aFile,
468 const char* aSourceFile,
469 const int32_t aSourceLine) {
470 nsString leafName;
471 nsresult rv = aFile.GetLeafName(leafName);
472 if (NS_WARN_IF(NS_FAILED(rv))) {
473 return Err(rv);
476 bool isDirectory;
477 rv = aFile.IsDirectory(&isDirectory);
478 if (NS_WARN_IF(NS_FAILED(rv))) {
479 return Err(rv);
482 if (!isDirectory) {
483 // Don't warn about OS metadata files. These files are only used in
484 // different platforms, but the profile can be shared across different
485 // operating systems, so we check it on all platforms.
486 if (leafName.Equals(kDSStoreFileName) ||
487 leafName.Equals(kDesktopFileName) ||
488 leafName.Equals(kDesktopIniFileName,
489 nsCaseInsensitiveStringComparator) ||
490 leafName.Equals(kThumbsDbFileName, nsCaseInsensitiveStringComparator)) {
491 return false;
494 // Don't warn about files starting with ".".
495 if (leafName.First() == char16_t('.')) {
496 return false;
500 NS_DebugBreak(
501 NS_DEBUG_WARNING,
502 nsPrintfCString("Something (%s) in the directory that doesn't belong!",
503 NS_ConvertUTF16toUTF8(leafName).get())
504 .get(),
505 nullptr, aSourceFile, aSourceLine);
507 return true;
509 #endif
511 } // namespace mozilla::dom::quota