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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 #include "ErrorList.h"
12 #include "js/ArrayBuffer.h"
14 #include "js/Utility.h"
15 #include "js/experimental/TypedData.h"
16 #include "jsfriendapi.h"
17 #include "mozilla/AutoRestore.h"
18 #include "mozilla/Compression.h"
19 #include "mozilla/Encoding.h"
20 #include "mozilla/EndianUtils.h"
21 #include "mozilla/ErrorNames.h"
22 #include "mozilla/Maybe.h"
23 #include "mozilla/ResultExtensions.h"
24 #include "mozilla/Services.h"
25 #include "mozilla/Span.h"
26 #include "mozilla/StaticPtr.h"
27 #include "mozilla/TextUtils.h"
28 #include "mozilla/Unused.h"
29 #include "mozilla/Utf8.h"
30 #include "mozilla/dom/IOUtilsBinding.h"
31 #include "mozilla/dom/Promise.h"
34 #include "nsFileStreams.h"
35 #include "nsIDirectoryEnumerator.h"
37 #include "nsIGlobalObject.h"
38 #include "nsISupports.h"
39 #include "nsLocalFile.h"
40 #include "nsPrintfCString.h"
41 #include "nsReadableUtils.h"
43 #include "nsStringFwd.h"
45 #include "nsThreadManager.h"
46 #include "nsXULAppAPI.h"
53 # include "nsSystemInfo.h"
56 #define REJECT_IF_INIT_PATH_FAILED(_file, _path, _promise) \
58 if (nsresult _rv = (_file)->InitWithPath((_path)); NS_FAILED(_rv)) { \
59 (_promise)->MaybeRejectWithOperationError( \
60 FormatErrorMessage(_rv, "Could not parse path (%s)", \
61 NS_ConvertUTF16toUTF8(_path).get())); \
62 return (_promise).forget(); \
66 static constexpr auto SHUTDOWN_ERROR
=
67 "IOUtils: Shutting down and refusing additional I/O tasks"_ns
;
69 namespace mozilla::dom
{
71 // static helper functions
74 * Platform-specific (e.g. Windows, Unix) implementations of XPCOM APIs may
75 * report I/O errors inconsistently. For convenience, this function will attempt
76 * to match a |nsresult| against known results which imply a file cannot be
79 * @see nsLocalFileWin.cpp
80 * @see nsLocalFileUnix.cpp
82 static bool IsFileNotFound(nsresult aResult
) {
83 return aResult
== NS_ERROR_FILE_NOT_FOUND
||
84 aResult
== NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
;
87 * Like |IsFileNotFound|, but checks for known results that suggest a file
90 static bool IsNotDirectory(nsresult aResult
) {
91 return aResult
== NS_ERROR_FILE_DESTINATION_NOT_DIR
||
92 aResult
== NS_ERROR_FILE_NOT_DIRECTORY
;
96 * Formats an error message and appends the error name to the end.
98 template <typename
... Args
>
99 static nsCString
FormatErrorMessage(nsresult aError
, const char* const aMessage
,
101 nsPrintfCString
msg(aMessage
, aArgs
...);
103 if (const char* errName
= GetStaticErrorName(aError
)) {
104 msg
.AppendPrintf(": %s", errName
);
106 // In the exceptional case where there is no error name, print the literal
107 // integer value of the nsresult as an upper case hex value so it can be
108 // located easily in searchfox.
109 msg
.AppendPrintf(": 0x%" PRIX32
, static_cast<uint32_t>(aError
));
112 return std::move(msg
);
115 static nsCString
FormatErrorMessage(nsresult aError
,
116 const char* const aMessage
) {
117 const char* errName
= GetStaticErrorName(aError
);
119 return nsPrintfCString("%s: %s", aMessage
, errName
);
121 // In the exceptional case where there is no error name, print the literal
122 // integer value of the nsresult as an upper case hex value so it can be
123 // located easily in searchfox.
124 return nsPrintfCString("%s: 0x%" PRIX32
, aMessage
,
125 static_cast<uint32_t>(aError
));
128 [[nodiscard
]] inline bool ToJSValue(
129 JSContext
* aCx
, const IOUtils::InternalFileInfo
& aInternalFileInfo
,
130 JS::MutableHandle
<JS::Value
> aValue
) {
132 info
.mPath
.Construct(aInternalFileInfo
.mPath
);
133 info
.mType
.Construct(aInternalFileInfo
.mType
);
134 info
.mSize
.Construct(aInternalFileInfo
.mSize
);
135 info
.mLastModified
.Construct(aInternalFileInfo
.mLastModified
);
137 if (aInternalFileInfo
.mCreationTime
.isSome()) {
138 info
.mCreationTime
.Construct(aInternalFileInfo
.mCreationTime
.ref());
141 info
.mPermissions
.Construct(aInternalFileInfo
.mPermissions
);
143 return ToJSValue(aCx
, info
, aValue
);
146 template <typename T
>
147 static void ResolveJSPromise(Promise
* aPromise
, T
&& aValue
) {
148 if constexpr (std::is_same_v
<T
, Ok
>) {
149 aPromise
->MaybeResolveWithUndefined();
151 aPromise
->MaybeResolve(std::forward
<T
>(aValue
));
155 static void RejectJSPromise(Promise
* aPromise
, const IOUtils::IOError
& aError
) {
156 const auto& errMsg
= aError
.Message();
158 switch (aError
.Code()) {
159 case NS_ERROR_FILE_UNRESOLVABLE_SYMLINK
:
160 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
161 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST
:
162 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
163 case NS_ERROR_FILE_NOT_FOUND
:
164 [[fallthrough
]]; // to NS_ERROR_FILE_INVALID_PATH
165 case NS_ERROR_FILE_INVALID_PATH
:
166 aPromise
->MaybeRejectWithNotFoundError(errMsg
.refOr("File not found"_ns
));
168 case NS_ERROR_FILE_IS_LOCKED
:
169 [[fallthrough
]]; // to NS_ERROR_FILE_ACCESS_DENIED
170 case NS_ERROR_FILE_ACCESS_DENIED
:
171 aPromise
->MaybeRejectWithNotAllowedError(
172 errMsg
.refOr("Access was denied to the target file"_ns
));
174 case NS_ERROR_FILE_TOO_BIG
:
175 aPromise
->MaybeRejectWithNotReadableError(
176 errMsg
.refOr("Target file is too big"_ns
));
178 case NS_ERROR_FILE_NO_DEVICE_SPACE
:
179 aPromise
->MaybeRejectWithNotReadableError(
180 errMsg
.refOr("Target device is full"_ns
));
182 case NS_ERROR_FILE_ALREADY_EXISTS
:
183 aPromise
->MaybeRejectWithNoModificationAllowedError(
184 errMsg
.refOr("Target file already exists"_ns
));
186 case NS_ERROR_FILE_COPY_OR_MOVE_FAILED
:
187 aPromise
->MaybeRejectWithOperationError(
188 errMsg
.refOr("Failed to copy or move the target file"_ns
));
190 case NS_ERROR_FILE_READ_ONLY
:
191 aPromise
->MaybeRejectWithReadOnlyError(
192 errMsg
.refOr("Target file is read only"_ns
));
194 case NS_ERROR_FILE_NOT_DIRECTORY
:
195 [[fallthrough
]]; // to NS_ERROR_FILE_DESTINATION_NOT_DIR
196 case NS_ERROR_FILE_DESTINATION_NOT_DIR
:
197 aPromise
->MaybeRejectWithInvalidAccessError(
198 errMsg
.refOr("Target file is not a directory"_ns
));
200 case NS_ERROR_FILE_IS_DIRECTORY
:
201 aPromise
->MaybeRejectWithInvalidAccessError(
202 errMsg
.refOr("Target file is a directory"_ns
));
204 case NS_ERROR_FILE_UNKNOWN_TYPE
:
205 aPromise
->MaybeRejectWithInvalidAccessError(
206 errMsg
.refOr("Target file is of unknown type"_ns
));
208 case NS_ERROR_FILE_NAME_TOO_LONG
:
209 aPromise
->MaybeRejectWithOperationError(
210 errMsg
.refOr("Target file path is too long"_ns
));
212 case NS_ERROR_FILE_UNRECOGNIZED_PATH
:
213 aPromise
->MaybeRejectWithOperationError(
214 errMsg
.refOr("Target file path is not recognized"_ns
));
216 case NS_ERROR_FILE_DIR_NOT_EMPTY
:
217 aPromise
->MaybeRejectWithOperationError(
218 errMsg
.refOr("Target directory is not empty"_ns
));
220 case NS_ERROR_FILE_DEVICE_FAILURE
:
221 [[fallthrough
]]; // to NS_ERROR_FILE_FS_CORRUPTED
222 case NS_ERROR_FILE_FS_CORRUPTED
:
223 aPromise
->MaybeRejectWithNotReadableError(
224 errMsg
.refOr("Target file system may be corrupt or unavailable"_ns
));
226 case NS_ERROR_FILE_CORRUPTED
:
227 aPromise
->MaybeRejectWithNotReadableError(
228 errMsg
.refOr("Target file could not be read and may be corrupt"_ns
));
230 case NS_ERROR_ILLEGAL_INPUT
:
231 [[fallthrough
]]; // NS_ERROR_ILLEGAL_VALUE
232 case NS_ERROR_ILLEGAL_VALUE
:
233 aPromise
->MaybeRejectWithDataError(
234 errMsg
.refOr("Argument is not allowed"_ns
));
237 aPromise
->MaybeRejectWithAbortError(errMsg
.refOr("Operation aborted"_ns
));
240 aPromise
->MaybeRejectWithUnknownError(
241 errMsg
.refOr(FormatErrorMessage(aError
.Code(), "Unexpected error")));
245 static void RejectShuttingDown(Promise
* aPromise
) {
246 RejectJSPromise(aPromise
,
247 IOUtils::IOError(NS_ERROR_ABORT
).WithMessage(SHUTDOWN_ERROR
));
250 // IOUtils implementation
252 IOUtils::StateMutex
IOUtils::sState
{"IOUtils::sState"};
255 template <typename OkT
, typename Fn
>
256 void IOUtils::DispatchAndResolve(IOUtils::EventQueue
* aQueue
, Promise
* aPromise
,
258 if (RefPtr
<IOPromise
<OkT
>> p
= aQueue
->Dispatch
<OkT
, Fn
>(std::move(aFunc
))) {
260 GetCurrentSerialEventTarget(), __func__
,
261 [promise
= RefPtr(aPromise
)](OkT
&& ok
) {
262 ResolveJSPromise(promise
, std::forward
<OkT
>(ok
));
264 [promise
= RefPtr(aPromise
)](const IOError
& err
) {
265 RejectJSPromise(promise
, err
);
271 already_AddRefed
<Promise
> IOUtils::Read(GlobalObject
& aGlobal
,
272 const nsAString
& aPath
,
273 const ReadOptions
& aOptions
) {
274 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
275 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
280 if (auto state
= GetState()) {
281 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
282 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
284 Maybe
<uint32_t> toRead
= Nothing();
285 if (!aOptions
.mMaxBytes
.IsNull()) {
286 if (aOptions
.mMaxBytes
.Value() == 0) {
287 // Resolve with an empty buffer.
288 nsTArray
<uint8_t> arr(0);
289 promise
->MaybeResolve(TypedArrayCreator
<Uint8Array
>(arr
));
290 return promise
.forget();
292 toRead
.emplace(aOptions
.mMaxBytes
.Value());
295 DispatchAndResolve
<JsBuffer
>(
296 state
.ref()->mEventQueue
, promise
,
297 [file
= std::move(file
), offset
= aOptions
.mOffset
, toRead
,
298 decompress
= aOptions
.mDecompress
]() {
299 return ReadSync(file
, offset
, toRead
, decompress
,
300 BufferKind::Uint8Array
);
303 RejectShuttingDown(promise
);
305 return promise
.forget();
309 already_AddRefed
<Promise
> IOUtils::ReadUTF8(GlobalObject
& aGlobal
,
310 const nsAString
& aPath
,
311 const ReadUTF8Options
& aOptions
) {
312 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
313 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
318 if (auto state
= GetState()) {
319 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
320 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
322 DispatchAndResolve
<JsBuffer
>(
323 state
.ref()->mEventQueue
, promise
,
324 [file
= std::move(file
), decompress
= aOptions
.mDecompress
]() {
325 return ReadUTF8Sync(file
, decompress
);
328 RejectShuttingDown(promise
);
330 return promise
.forget();
334 already_AddRefed
<Promise
> IOUtils::ReadJSON(GlobalObject
& aGlobal
,
335 const nsAString
& aPath
,
336 const ReadUTF8Options
& aOptions
) {
337 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
338 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
343 if (auto state
= GetState()) {
344 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
345 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
349 ->Dispatch
<JsBuffer
>([file
, decompress
= aOptions
.mDecompress
]() {
350 return ReadUTF8Sync(file
, decompress
);
353 GetCurrentSerialEventTarget(), __func__
,
354 [promise
, file
](JsBuffer
&& aBuffer
) {
356 if (NS_WARN_IF(!jsapi
.Init(promise
->GetGlobalObject()))) {
357 promise
->MaybeRejectWithUnknownError(
358 "Could not initialize JS API");
361 JSContext
* cx
= jsapi
.cx();
363 JS::Rooted
<JSString
*> jsonStr(
364 cx
, IOUtils::JsBuffer::IntoString(cx
, std::move(aBuffer
)));
366 RejectJSPromise(promise
, IOError(NS_ERROR_OUT_OF_MEMORY
));
370 JS::Rooted
<JS::Value
> val(cx
);
371 if (!JS_ParseJSON(cx
, jsonStr
, &val
)) {
372 JS::Rooted
<JS::Value
> exn(cx
);
373 if (JS_GetPendingException(cx
, &exn
)) {
374 JS_ClearPendingException(cx
);
375 promise
->MaybeReject(exn
);
379 IOError(NS_ERROR_DOM_UNKNOWN_ERR
)
381 "ParseJSON threw an uncatchable exception "
382 "while parsing file(%s)",
383 file
->HumanReadablePath().get()));
389 promise
->MaybeResolve(val
);
391 [promise
](const IOError
& aErr
) { RejectJSPromise(promise
, aErr
); });
393 RejectShuttingDown(promise
);
395 return promise
.forget();
399 already_AddRefed
<Promise
> IOUtils::Write(GlobalObject
& aGlobal
,
400 const nsAString
& aPath
,
401 const Uint8Array
& aData
,
402 const WriteOptions
& aOptions
) {
403 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
404 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
409 if (auto state
= GetState()) {
410 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
411 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
413 aData
.ComputeState();
414 auto buf
= Buffer
<uint8_t>::CopyFrom(Span(aData
.Data(), aData
.Length()));
415 if (buf
.isNothing()) {
416 promise
->MaybeRejectWithOperationError(
417 "Out of memory: Could not allocate buffer while writing to file");
418 return promise
.forget();
421 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
423 RejectJSPromise(promise
, opts
.unwrapErr());
424 return promise
.forget();
427 DispatchAndResolve
<uint32_t>(
428 state
.ref()->mEventQueue
, promise
,
429 [file
= std::move(file
), buf
= std::move(*buf
),
430 opts
= opts
.unwrap()]() { return WriteSync(file
, buf
, opts
); });
432 RejectShuttingDown(promise
);
434 return promise
.forget();
438 already_AddRefed
<Promise
> IOUtils::WriteUTF8(GlobalObject
& aGlobal
,
439 const nsAString
& aPath
,
440 const nsACString
& aString
,
441 const WriteOptions
& aOptions
) {
442 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
443 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
448 if (auto state
= GetState()) {
449 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
450 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
452 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
454 RejectJSPromise(promise
, opts
.unwrapErr());
455 return promise
.forget();
458 DispatchAndResolve
<uint32_t>(
459 state
.ref()->mEventQueue
, promise
,
460 [file
= std::move(file
), str
= nsCString(aString
),
461 opts
= opts
.unwrap()]() {
462 return WriteSync(file
, AsBytes(Span(str
)), opts
);
465 RejectShuttingDown(promise
);
467 return promise
.forget();
470 static bool AppendJsonAsUtf8(const char16_t
* aData
, uint32_t aLen
, void* aStr
) {
471 nsCString
* str
= static_cast<nsCString
*>(aStr
);
472 return AppendUTF16toUTF8(Span
<const char16_t
>(aData
, aLen
), *str
, fallible
);
476 already_AddRefed
<Promise
> IOUtils::WriteJSON(GlobalObject
& aGlobal
,
477 const nsAString
& aPath
,
478 JS::Handle
<JS::Value
> aValue
,
479 const WriteOptions
& aOptions
) {
480 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
481 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
486 if (auto state
= GetState()) {
487 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
488 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
490 auto opts
= InternalWriteOpts::FromBinding(aOptions
);
492 RejectJSPromise(promise
, opts
.unwrapErr());
493 return promise
.forget();
496 if (opts
.inspect().mMode
== WriteMode::Append
) {
497 promise
->MaybeRejectWithNotSupportedError(
498 "IOUtils.writeJSON does not support appending to files."_ns
);
499 return promise
.forget();
502 JSContext
* cx
= aGlobal
.Context();
503 JS::Rooted
<JS::Value
> rootedValue(cx
, aValue
);
506 if (!JS_Stringify(cx
, &rootedValue
, nullptr, JS::NullHandleValue
,
507 AppendJsonAsUtf8
, &utf8Str
)) {
508 JS::Rooted
<JS::Value
> exn(cx
, JS::UndefinedValue());
509 if (JS_GetPendingException(cx
, &exn
)) {
510 JS_ClearPendingException(cx
);
511 promise
->MaybeReject(exn
);
513 RejectJSPromise(promise
,
514 IOError(NS_ERROR_DOM_UNKNOWN_ERR
)
515 .WithMessage("Could not serialize object to JSON"));
517 return promise
.forget();
520 DispatchAndResolve
<uint32_t>(
521 state
.ref()->mEventQueue
, promise
,
522 [file
= std::move(file
), utf8Str
= std::move(utf8Str
),
523 opts
= opts
.unwrap()]() {
524 return WriteSync(file
, AsBytes(Span(utf8Str
)), opts
);
527 RejectShuttingDown(promise
);
529 return promise
.forget();
533 already_AddRefed
<Promise
> IOUtils::Move(GlobalObject
& aGlobal
,
534 const nsAString
& aSourcePath
,
535 const nsAString
& aDestPath
,
536 const MoveOptions
& aOptions
) {
537 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
538 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
543 if (auto state
= GetState()) {
544 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
545 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
);
547 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
548 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
);
550 DispatchAndResolve
<Ok
>(
551 state
.ref()->mEventQueue
, promise
,
552 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
553 noOverwrite
= aOptions
.mNoOverwrite
]() {
554 return MoveSync(sourceFile
, destFile
, noOverwrite
);
557 RejectShuttingDown(promise
);
559 return promise
.forget();
563 already_AddRefed
<Promise
> IOUtils::Remove(GlobalObject
& aGlobal
,
564 const nsAString
& aPath
,
565 const RemoveOptions
& aOptions
) {
566 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
567 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
572 if (auto state
= GetState()) {
573 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
574 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
576 DispatchAndResolve
<Ok
>(
577 state
.ref()->mEventQueue
, promise
,
578 [file
= std::move(file
), ignoreAbsent
= aOptions
.mIgnoreAbsent
,
579 recursive
= aOptions
.mRecursive
]() {
580 return RemoveSync(file
, ignoreAbsent
, recursive
);
583 RejectShuttingDown(promise
);
585 return promise
.forget();
589 already_AddRefed
<Promise
> IOUtils::MakeDirectory(
590 GlobalObject
& aGlobal
, const nsAString
& aPath
,
591 const MakeDirectoryOptions
& aOptions
) {
592 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
593 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
598 if (auto state
= GetState()) {
599 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
600 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
602 DispatchAndResolve
<Ok
>(
603 state
.ref()->mEventQueue
, promise
,
604 [file
= std::move(file
), createAncestors
= aOptions
.mCreateAncestors
,
605 ignoreExisting
= aOptions
.mIgnoreExisting
,
606 permissions
= aOptions
.mPermissions
]() {
607 return MakeDirectorySync(file
, createAncestors
, ignoreExisting
,
611 RejectShuttingDown(promise
);
613 return promise
.forget();
616 already_AddRefed
<Promise
> IOUtils::Stat(GlobalObject
& aGlobal
,
617 const nsAString
& aPath
) {
618 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
619 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
624 if (auto state
= GetState()) {
625 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
626 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
628 DispatchAndResolve
<InternalFileInfo
>(
629 state
.ref()->mEventQueue
, promise
,
630 [file
= std::move(file
)]() { return StatSync(file
); });
632 RejectShuttingDown(promise
);
634 return promise
.forget();
638 already_AddRefed
<Promise
> IOUtils::Copy(GlobalObject
& aGlobal
,
639 const nsAString
& aSourcePath
,
640 const nsAString
& aDestPath
,
641 const CopyOptions
& aOptions
) {
642 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
643 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
648 if (auto state
= GetState()) {
649 nsCOMPtr
<nsIFile
> sourceFile
= new nsLocalFile();
650 REJECT_IF_INIT_PATH_FAILED(sourceFile
, aSourcePath
, promise
);
652 nsCOMPtr
<nsIFile
> destFile
= new nsLocalFile();
653 REJECT_IF_INIT_PATH_FAILED(destFile
, aDestPath
, promise
);
655 DispatchAndResolve
<Ok
>(
656 state
.ref()->mEventQueue
, promise
,
657 [sourceFile
= std::move(sourceFile
), destFile
= std::move(destFile
),
658 noOverwrite
= aOptions
.mNoOverwrite
,
659 recursive
= aOptions
.mRecursive
]() {
660 return CopySync(sourceFile
, destFile
, noOverwrite
, recursive
);
663 RejectShuttingDown(promise
);
665 return promise
.forget();
669 already_AddRefed
<Promise
> IOUtils::Touch(
670 GlobalObject
& aGlobal
, const nsAString
& aPath
,
671 const Optional
<int64_t>& aModification
) {
672 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
673 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
678 if (auto state
= GetState()) {
679 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
680 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
682 Maybe
<int64_t> newTime
= Nothing();
683 if (aModification
.WasPassed()) {
684 newTime
= Some(aModification
.Value());
686 DispatchAndResolve
<int64_t>(state
.ref()->mEventQueue
, promise
,
687 [file
= std::move(file
), newTime
]() {
688 return TouchSync(file
, newTime
);
691 RejectShuttingDown(promise
);
693 return promise
.forget();
697 already_AddRefed
<Promise
> IOUtils::GetChildren(GlobalObject
& aGlobal
,
698 const nsAString
& aPath
) {
699 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
700 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
705 if (auto state
= GetState()) {
706 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
707 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
709 DispatchAndResolve
<nsTArray
<nsString
>>(
710 state
.ref()->mEventQueue
, promise
,
711 [file
= std::move(file
)]() { return GetChildrenSync(file
); });
713 RejectShuttingDown(promise
);
715 return promise
.forget();
719 already_AddRefed
<Promise
> IOUtils::SetPermissions(GlobalObject
& aGlobal
,
720 const nsAString
& aPath
,
721 uint32_t aPermissions
,
722 const bool aHonorUmask
) {
723 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
724 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
729 #if defined(XP_UNIX) && !defined(ANDROID)
731 aPermissions
&= ~nsSystemInfo::gUserUmask
;
735 if (auto state
= GetState()) {
736 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
737 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
739 DispatchAndResolve
<Ok
>(
740 state
.ref()->mEventQueue
, promise
,
741 [file
= std::move(file
), permissions
= aPermissions
]() {
742 return SetPermissionsSync(file
, permissions
);
745 RejectShuttingDown(promise
);
747 return promise
.forget();
751 already_AddRefed
<Promise
> IOUtils::Exists(GlobalObject
& aGlobal
,
752 const nsAString
& aPath
) {
753 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
754 RefPtr
<Promise
> promise
= CreateJSPromise(aGlobal
);
759 if (auto state
= GetState()) {
760 nsCOMPtr
<nsIFile
> file
= new nsLocalFile();
761 REJECT_IF_INIT_PATH_FAILED(file
, aPath
, promise
);
763 DispatchAndResolve
<bool>(
764 state
.ref()->mEventQueue
, promise
,
765 [file
= std::move(file
)]() { return ExistsSync(file
); });
767 RejectShuttingDown(promise
);
769 return promise
.forget();
773 already_AddRefed
<Promise
> IOUtils::CreateJSPromise(GlobalObject
& aGlobal
) {
775 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
776 RefPtr
<Promise
> promise
= Promise::Create(global
, er
);
781 return do_AddRef(promise
);
785 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadSync(
786 nsIFile
* aFile
, const uint32_t aOffset
, const Maybe
<uint32_t> aMaxBytes
,
787 const bool aDecompress
, IOUtils::BufferKind aBufferKind
) {
788 MOZ_ASSERT(!NS_IsMainThread());
790 if (aMaxBytes
.isSome() && aDecompress
) {
792 IOError(NS_ERROR_ILLEGAL_INPUT
)
794 "The `maxBytes` and `decompress` options are not compatible"));
797 RefPtr
<nsFileStream
> stream
= new nsFileStream();
799 stream
->Init(aFile
, PR_RDONLY
| nsIFile::OS_READAHEAD
, 0666, 0);
801 return Err(IOError(rv
).WithMessage("Could not open the file at %s",
802 aFile
->HumanReadablePath().get()));
806 if (aMaxBytes
.isNothing()) {
807 // Limitation: We cannot read files that are larger than the max size of a
808 // TypedArray (UINT32_MAX bytes). Reject if the file is too
811 int64_t streamSize
= -1;
812 if (nsresult rv
= stream
->GetSize(&streamSize
); NS_FAILED(rv
)) {
813 return Err(IOError(NS_ERROR_FILE_ACCESS_DENIED
)
814 .WithMessage("Could not get info for the file at %s",
815 aFile
->HumanReadablePath().get()));
817 MOZ_RELEASE_ASSERT(streamSize
>= 0);
819 if (streamSize
> static_cast<int64_t>(UINT32_MAX
)) {
821 IOError(NS_ERROR_FILE_TOO_BIG
)
822 .WithMessage("Could not read the file at %s because it is too "
823 "large(size=%" PRId64
" bytes)",
824 aFile
->HumanReadablePath().get(), streamSize
));
826 bufSize
= static_cast<uint32_t>(streamSize
);
828 if (aOffset
>= bufSize
) {
831 bufSize
= bufSize
- aOffset
;
834 bufSize
= aMaxBytes
.value();
838 if (nsresult rv
= stream
->Seek(PR_SEEK_SET
, aOffset
); NS_FAILED(rv
)) {
839 return Err(IOError(rv
).WithMessage(
840 "Could not seek to position %" PRId64
" in file %s", aOffset
,
841 aFile
->HumanReadablePath().get()));
845 JsBuffer buffer
= JsBuffer::CreateEmpty(aBufferKind
);
848 auto result
= JsBuffer::Create(aBufferKind
, bufSize
);
849 if (result
.isErr()) {
850 return result
.propagateErr();
852 buffer
= result
.unwrap();
853 Span
<char> toRead
= buffer
.BeginWriting();
855 // Read the file from disk.
856 uint32_t totalRead
= 0;
857 while (totalRead
!= bufSize
) {
858 uint32_t bytesRead
= 0;
860 stream
->Read(toRead
.Elements(), bufSize
- totalRead
, &bytesRead
);
862 return Err(IOError(rv
).WithMessage(
863 "Encountered an unexpected error while reading file(%s)",
864 aFile
->HumanReadablePath().get()));
866 if (bytesRead
== 0) {
869 totalRead
+= bytesRead
;
870 toRead
= toRead
.From(bytesRead
);
873 buffer
.SetLength(totalRead
);
876 // Decompress the file contents, if required.
878 return MozLZ4::Decompress(AsBytes(buffer
.BeginReading()), aBufferKind
);
881 return std::move(buffer
);
885 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::ReadUTF8Sync(
886 nsIFile
* aFile
, bool aDecompress
) {
887 auto result
= ReadSync(aFile
, 0, Nothing
{}, aDecompress
, BufferKind::String
);
888 if (result
.isErr()) {
889 return result
.propagateErr();
892 JsBuffer buffer
= result
.unwrap();
893 if (!IsUtf8(buffer
.BeginReading())) {
895 IOError(NS_ERROR_FILE_CORRUPTED
)
897 "Could not read file(%s) because it is not UTF-8 encoded",
898 aFile
->HumanReadablePath().get()));
905 Result
<uint32_t, IOUtils::IOError
> IOUtils::WriteSync(
906 nsIFile
* aFile
, const Span
<const uint8_t>& aByteArray
,
907 const IOUtils::InternalWriteOpts
& aOptions
) {
908 MOZ_ASSERT(!NS_IsMainThread());
910 nsIFile
* backupFile
= aOptions
.mBackupFile
;
911 nsIFile
* tempFile
= aOptions
.mTmpFile
;
914 MOZ_TRY(aFile
->Exists(&exists
));
916 if (exists
&& aOptions
.mMode
== WriteMode::Create
) {
917 return Err(IOError(NS_ERROR_DOM_TYPE_MISMATCH_ERR
)
918 .WithMessage("Refusing to overwrite the file at %s\n"
919 "Specify `mode: \"overwrite\"` to allow "
920 "overwriting the destination",
921 aFile
->HumanReadablePath().get()));
924 // If backupFile was specified, perform the backup as a move.
925 if (exists
&& backupFile
) {
926 // We copy `destFile` here to a new `nsIFile` because
927 // `nsIFile::MoveToFollowingLinks` will update the path of the file. If we
928 // did not do this, we would end up having `destFile` point to the same
929 // location as `backupFile`. Then, when we went to write to `destFile`, we
930 // would end up overwriting `backupFile` and never actually write to the
931 // file we were supposed to.
932 nsCOMPtr
<nsIFile
> toMove
;
933 MOZ_ALWAYS_SUCCEEDS(aFile
->Clone(getter_AddRefs(toMove
)));
935 bool noOverwrite
= aOptions
.mMode
== WriteMode::Create
;
937 if (MoveSync(toMove
, backupFile
, noOverwrite
).isErr()) {
938 return Err(IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
939 .WithMessage("Failed to backup the source file(%s) to %s",
940 aFile
->HumanReadablePath().get(),
941 backupFile
->HumanReadablePath().get()));
945 // If tempFile was specified, we will write to there first, then perform a
946 // move to ensure the file ends up at the final requested destination.
950 writeFile
= tempFile
;
955 int32_t flags
= PR_WRONLY
;
957 switch (aOptions
.mMode
) {
958 case WriteMode::Overwrite
:
959 flags
|= PR_TRUNCATE
| PR_CREATE_FILE
;
962 case WriteMode::Append
:
966 case WriteMode::Create
:
967 flags
|= PR_CREATE_FILE
| PR_EXCL
;
971 MOZ_CRASH("IOUtils: unknown write mode");
974 if (aOptions
.mFlush
) {
978 // Try to perform the write and ensure that the file is closed before
980 uint32_t totalWritten
= 0;
982 // Compress the byte array if required.
983 nsTArray
<uint8_t> compressed
;
984 Span
<const char> bytes
;
985 if (aOptions
.mCompress
) {
986 auto rv
= MozLZ4::Compress(aByteArray
);
988 return rv
.propagateErr();
990 compressed
= rv
.unwrap();
991 bytes
= Span(reinterpret_cast<const char*>(compressed
.Elements()),
992 compressed
.Length());
994 bytes
= Span(reinterpret_cast<const char*>(aByteArray
.Elements()),
995 aByteArray
.Length());
998 RefPtr
<nsFileOutputStream
> stream
= new nsFileOutputStream();
999 if (nsresult rv
= stream
->Init(writeFile
, flags
, 0666, 0); NS_FAILED(rv
)) {
1000 // Normalize platform-specific errors for opening a directory to an access
1002 if (rv
== nsresult::NS_ERROR_FILE_IS_DIRECTORY
) {
1003 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
1006 IOError(rv
).WithMessage("Could not open the file at %s for writing",
1007 writeFile
->HumanReadablePath().get()));
1010 // nsFileStream::Write uses PR_Write under the hood, which accepts a
1011 // *int32_t* for the chunk size.
1012 uint32_t chunkSize
= INT32_MAX
;
1013 Span
<const char> pendingBytes
= bytes
;
1015 while (pendingBytes
.Length() > 0) {
1016 if (pendingBytes
.Length() < chunkSize
) {
1017 chunkSize
= pendingBytes
.Length();
1020 uint32_t bytesWritten
= 0;
1022 stream
->Write(pendingBytes
.Elements(), chunkSize
, &bytesWritten
);
1024 return Err(IOError(rv
).WithMessage(
1025 "Could not write chunk (size = %" PRIu32
1026 ") to file %s. The file may be corrupt.",
1027 chunkSize
, writeFile
->HumanReadablePath().get()));
1029 pendingBytes
= pendingBytes
.From(bytesWritten
);
1030 totalWritten
+= bytesWritten
;
1034 // If tempFile was passed, check destFile against writeFile and, if they
1035 // differ, the operation is finished by performing a move.
1037 nsAutoStringN
<256> destPath
;
1038 nsAutoStringN
<256> writePath
;
1040 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(destPath
));
1041 MOZ_ALWAYS_SUCCEEDS(writeFile
->GetPath(writePath
));
1043 // nsIFile::MoveToFollowingLinks will only update the path of the file if
1044 // the move succeeds.
1045 if (destPath
!= writePath
) {
1046 if (aOptions
.mTmpFile
) {
1048 if (nsresult rv
= aFile
->IsDirectory(&isDir
);
1049 NS_FAILED(rv
) && !IsFileNotFound(rv
)) {
1050 return Err(IOError(rv
).WithMessage("Could not stat the file at %s",
1051 aFile
->HumanReadablePath().get()));
1054 // If we attempt to write to a directory *without* a temp file, we get a
1055 // permission error.
1057 // However, if we are writing to a temp file first, when we copy the
1058 // temp file over the destination file, we actually end up copying it
1059 // inside the directory, which is not what we want. In this case, we are
1060 // just going to bail out early.
1063 IOError(NS_ERROR_FILE_ACCESS_DENIED
)
1064 .WithMessage("Could not open the file at %s for writing",
1065 aFile
->HumanReadablePath().get()));
1069 if (MoveSync(writeFile
, aFile
, /* aNoOverwrite = */ false).isErr()) {
1071 IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1073 "Could not move temporary file(%s) to destination(%s)",
1074 writeFile
->HumanReadablePath().get(),
1075 aFile
->HumanReadablePath().get()));
1079 return totalWritten
;
1083 Result
<Ok
, IOUtils::IOError
> IOUtils::MoveSync(nsIFile
* aSourceFile
,
1085 bool aNoOverwrite
) {
1086 MOZ_ASSERT(!NS_IsMainThread());
1088 // Ensure the source file exists before continuing. If it doesn't exist,
1089 // subsequent operations can fail in different ways on different platforms.
1090 bool srcExists
= false;
1091 MOZ_TRY(aSourceFile
->Exists(&srcExists
));
1094 IOError(NS_ERROR_FILE_NOT_FOUND
)
1096 "Could not move source file(%s) because it does not exist",
1097 aSourceFile
->HumanReadablePath().get()));
1100 return CopyOrMoveSync(&nsIFile::MoveToFollowingLinks
, "move", aSourceFile
,
1101 aDestFile
, aNoOverwrite
);
1105 Result
<Ok
, IOUtils::IOError
> IOUtils::CopySync(nsIFile
* aSourceFile
,
1109 MOZ_ASSERT(!NS_IsMainThread());
1111 // Ensure the source file exists before continuing. If it doesn't exist,
1112 // subsequent operations can fail in different ways on different platforms.
1114 MOZ_TRY(aSourceFile
->Exists(&srcExists
));
1117 IOError(NS_ERROR_FILE_NOT_FOUND
)
1119 "Could not copy source file(%s) because it does not exist",
1120 aSourceFile
->HumanReadablePath().get()));
1123 // If source is a directory, fail immediately unless the recursive option is
1125 bool srcIsDir
= false;
1126 MOZ_TRY(aSourceFile
->IsDirectory(&srcIsDir
));
1127 if (srcIsDir
&& !aRecursive
) {
1129 IOError(NS_ERROR_FILE_COPY_OR_MOVE_FAILED
)
1131 "Refused to copy source directory(%s) to the destination(%s)\n"
1132 "Specify the `recursive: true` option to allow copying "
1134 aSourceFile
->HumanReadablePath().get(),
1135 aDestFile
->HumanReadablePath().get()));
1138 return CopyOrMoveSync(&nsIFile::CopyToFollowingLinks
, "copy", aSourceFile
,
1139 aDestFile
, aNoOverwrite
);
1143 template <typename CopyOrMoveFn
>
1144 Result
<Ok
, IOUtils::IOError
> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod
,
1145 const char* aMethodName
,
1148 bool aNoOverwrite
) {
1149 MOZ_ASSERT(!NS_IsMainThread());
1151 // Case 1: Destination is an existing directory. Copy/move source into dest.
1152 bool destIsDir
= false;
1153 bool destExists
= true;
1155 nsresult rv
= aDest
->IsDirectory(&destIsDir
);
1156 if (NS_SUCCEEDED(rv
) && destIsDir
) {
1157 rv
= (aSource
->*aMethod
)(aDest
, u
""_ns
);
1158 if (NS_FAILED(rv
)) {
1159 return Err(IOError(rv
).WithMessage(
1160 "Could not %s source file(%s) to destination directory(%s)",
1161 aMethodName
, aSource
->HumanReadablePath().get(),
1162 aDest
->HumanReadablePath().get()));
1167 if (NS_FAILED(rv
)) {
1168 if (!IsFileNotFound(rv
)) {
1169 // It's ok if the dest file doesn't exist. Case 2 handles this below.
1170 // Bail out early for any other kind of error though.
1171 return Err(IOError(rv
));
1176 // Case 2: Destination is a file which may or may not exist.
1177 // Try to copy or rename the source to the destination.
1178 // If the destination exists and the source is not a regular file,
1179 // then this may fail.
1180 if (aNoOverwrite
&& destExists
) {
1182 IOError(NS_ERROR_FILE_ALREADY_EXISTS
)
1184 "Could not %s source file(%s) to destination(%s) because the "
1185 "destination already exists and overwrites are not allowed\n"
1186 "Specify the `noOverwrite: false` option to mitigate this "
1188 aMethodName
, aSource
->HumanReadablePath().get(),
1189 aDest
->HumanReadablePath().get()));
1191 if (destExists
&& !destIsDir
) {
1192 // If the source file is a directory, but the target is a file, abort early.
1193 // Different implementations of |CopyTo| and |MoveTo| seem to handle this
1194 // error case differently (or not at all), so we explicitly handle it here.
1195 bool srcIsDir
= false;
1196 MOZ_TRY(aSource
->IsDirectory(&srcIsDir
));
1198 return Err(IOError(NS_ERROR_FILE_DESTINATION_NOT_DIR
)
1199 .WithMessage("Could not %s the source directory(%s) to "
1200 "the destination(%s) because the destination "
1201 "is not a directory",
1203 aSource
->HumanReadablePath().get(),
1204 aDest
->HumanReadablePath().get()));
1208 nsCOMPtr
<nsIFile
> destDir
;
1209 nsAutoString destName
;
1210 MOZ_TRY(aDest
->GetLeafName(destName
));
1211 MOZ_TRY(aDest
->GetParent(getter_AddRefs(destDir
)));
1213 // We know `destName` is a file and therefore must have a parent directory.
1214 MOZ_RELEASE_ASSERT(destDir
);
1216 // NB: if destDir doesn't exist, then |CopyToFollowingLinks| or
1217 // |MoveToFollowingLinks| will create it.
1218 rv
= (aSource
->*aMethod
)(destDir
, destName
);
1219 if (NS_FAILED(rv
)) {
1220 return Err(IOError(rv
).WithMessage(
1221 "Could not %s the source file(%s) to the destination(%s)", aMethodName
,
1222 aSource
->HumanReadablePath().get(), aDest
->HumanReadablePath().get()));
1228 Result
<Ok
, IOUtils::IOError
> IOUtils::RemoveSync(nsIFile
* aFile
,
1231 MOZ_ASSERT(!NS_IsMainThread());
1233 nsresult rv
= aFile
->Remove(aRecursive
);
1234 if (aIgnoreAbsent
&& IsFileNotFound(rv
)) {
1237 if (NS_FAILED(rv
)) {
1239 if (IsFileNotFound(rv
)) {
1240 return Err(err
.WithMessage(
1241 "Could not remove the file at %s because it does not exist.\n"
1242 "Specify the `ignoreAbsent: true` option to mitigate this error",
1243 aFile
->HumanReadablePath().get()));
1245 if (rv
== NS_ERROR_FILE_DIR_NOT_EMPTY
) {
1246 return Err(err
.WithMessage(
1247 "Could not remove the non-empty directory at %s.\n"
1248 "Specify the `recursive: true` option to mitigate this error",
1249 aFile
->HumanReadablePath().get()));
1251 return Err(err
.WithMessage("Could not remove the file at %s",
1252 aFile
->HumanReadablePath().get()));
1258 Result
<Ok
, IOUtils::IOError
> IOUtils::MakeDirectorySync(nsIFile
* aFile
,
1259 bool aCreateAncestors
,
1260 bool aIgnoreExisting
,
1262 MOZ_ASSERT(!NS_IsMainThread());
1264 nsCOMPtr
<nsIFile
> parent
;
1265 MOZ_TRY(aFile
->GetParent(getter_AddRefs(parent
)));
1267 // If we don't have a parent directory, we were called with a
1268 // root directory. If the directory doesn't already exist (e.g., asking
1269 // for a drive on Windows that does not exist), we will not be able to
1272 // Calling `nsLocalFile::Create()` on Windows can fail with
1273 // `NS_ERROR_ACCESS_DENIED` trying to create a root directory, but we
1274 // would rather the call succeed, so return early if the directory exists.
1276 // Otherwise, we fall through to `nsiFile::Create()` and let it fail there
1278 bool exists
= false;
1279 MOZ_TRY(aFile
->Exists(&exists
));
1286 aFile
->Create(nsIFile::DIRECTORY_TYPE
, aMode
, !aCreateAncestors
);
1287 if (NS_FAILED(rv
)) {
1288 if (rv
== NS_ERROR_FILE_ALREADY_EXISTS
) {
1289 // NB: We may report a success only if the target is an existing
1290 // directory. We don't want to silence errors that occur if the target is
1291 // an existing file, since trying to create a directory where a regular
1292 // file exists may be indicative of a logic error.
1294 MOZ_TRY(aFile
->IsDirectory(&isDirectory
));
1296 return Err(IOError(NS_ERROR_FILE_NOT_DIRECTORY
)
1297 .WithMessage("Could not create directory because the "
1298 "target file(%s) exists "
1299 "and is not a directory",
1300 aFile
->HumanReadablePath().get()));
1302 // The directory exists.
1303 // The caller may suppress this error.
1304 if (aIgnoreExisting
) {
1307 // Otherwise, forward it.
1308 return Err(IOError(rv
).WithMessage(
1309 "Could not create directory because it already exists at %s\n"
1310 "Specify the `ignoreExisting: true` option to mitigate this "
1312 aFile
->HumanReadablePath().get()));
1314 return Err(IOError(rv
).WithMessage("Could not create directory at %s",
1315 aFile
->HumanReadablePath().get()));
1320 Result
<IOUtils::InternalFileInfo
, IOUtils::IOError
> IOUtils::StatSync(
1322 MOZ_ASSERT(!NS_IsMainThread());
1324 InternalFileInfo info
;
1325 MOZ_ALWAYS_SUCCEEDS(aFile
->GetPath(info
.mPath
));
1327 bool isRegular
= false;
1328 // IsFile will stat and cache info in the file object. If the file doesn't
1329 // exist, or there is an access error, we'll discover it here.
1330 // Any subsequent errors are unexpected and will just be forwarded.
1331 nsresult rv
= aFile
->IsFile(&isRegular
);
1332 if (NS_FAILED(rv
)) {
1334 if (IsFileNotFound(rv
)) {
1336 err
.WithMessage("Could not stat file(%s) because it does not exist",
1337 aFile
->HumanReadablePath().get()));
1342 // Now we can populate the info object by querying the file.
1343 info
.mType
= FileType::Regular
;
1346 MOZ_TRY(aFile
->IsDirectory(&isDir
));
1347 info
.mType
= isDir
? FileType::Directory
: FileType::Other
;
1351 if (info
.mType
== FileType::Regular
) {
1352 MOZ_TRY(aFile
->GetFileSize(&size
));
1355 PRTime lastModified
= 0;
1356 MOZ_TRY(aFile
->GetLastModifiedTime(&lastModified
));
1357 info
.mLastModified
= static_cast<int64_t>(lastModified
);
1359 PRTime creationTime
= 0;
1360 if (nsresult rv
= aFile
->GetCreationTime(&creationTime
); NS_SUCCEEDED(rv
)) {
1361 info
.mCreationTime
.emplace(static_cast<int64_t>(creationTime
));
1362 } else if (NS_FAILED(rv
) && rv
!= NS_ERROR_NOT_IMPLEMENTED
) {
1363 // This field is only supported on some platforms.
1364 return Err(IOError(rv
));
1367 MOZ_TRY(aFile
->GetPermissions(&info
.mPermissions
));
1373 Result
<int64_t, IOUtils::IOError
> IOUtils::TouchSync(
1374 nsIFile
* aFile
, const Maybe
<int64_t>& aNewModTime
) {
1375 MOZ_ASSERT(!NS_IsMainThread());
1377 int64_t now
= aNewModTime
.valueOrFrom([]() {
1378 // NB: PR_Now reports time in microseconds since the Unix epoch
1379 // (1970-01-01T00:00:00Z). Both nsLocalFile's lastModifiedTime and
1380 // JavaScript's Date primitive values are to be expressed in
1381 // milliseconds since Epoch.
1382 int64_t nowMicros
= PR_Now();
1383 int64_t nowMillis
= nowMicros
/ PR_USEC_PER_MSEC
;
1387 // nsIFile::SetLastModifiedTime will *not* do what is expected when passed 0
1388 // as an argument. Rather than setting the time to 0, it will recalculate the
1389 // system time and set it to that value instead. We explicit forbid this,
1390 // because this side effect is surprising.
1392 // If it ever becomes possible to set a file time to 0, this check should be
1393 // removed, though this use case seems rare.
1396 IOError(NS_ERROR_ILLEGAL_VALUE
)
1398 "Refusing to set the modification time of file(%s) to 0.\n"
1399 "To use the current system time, call `touch` with no "
1401 aFile
->HumanReadablePath().get()));
1404 nsresult rv
= aFile
->SetLastModifiedTime(now
);
1406 if (NS_FAILED(rv
)) {
1408 if (IsFileNotFound(rv
)) {
1410 err
.WithMessage("Could not touch file(%s) because it does not exist",
1411 aFile
->HumanReadablePath().get()));
1419 Result
<nsTArray
<nsString
>, IOUtils::IOError
> IOUtils::GetChildrenSync(
1421 MOZ_ASSERT(!NS_IsMainThread());
1423 nsCOMPtr
<nsIDirectoryEnumerator
> iter
;
1424 nsresult rv
= aFile
->GetDirectoryEntries(getter_AddRefs(iter
));
1425 if (NS_FAILED(rv
)) {
1427 if (IsFileNotFound(rv
)) {
1428 return Err(err
.WithMessage(
1429 "Could not get children of file(%s) because it does not exist",
1430 aFile
->HumanReadablePath().get()));
1432 if (IsNotDirectory(rv
)) {
1433 return Err(err
.WithMessage(
1434 "Could not get children of file(%s) because it is not a directory",
1435 aFile
->HumanReadablePath().get()));
1439 nsTArray
<nsString
> children
;
1441 bool hasMoreElements
= false;
1442 MOZ_TRY(iter
->HasMoreElements(&hasMoreElements
));
1443 while (hasMoreElements
) {
1444 nsCOMPtr
<nsIFile
> child
;
1445 MOZ_TRY(iter
->GetNextFile(getter_AddRefs(child
)));
1448 MOZ_TRY(child
->GetPath(path
));
1449 children
.AppendElement(path
);
1451 MOZ_TRY(iter
->HasMoreElements(&hasMoreElements
));
1458 Result
<Ok
, IOUtils::IOError
> IOUtils::SetPermissionsSync(
1459 nsIFile
* aFile
, const uint32_t aPermissions
) {
1460 MOZ_ASSERT(!NS_IsMainThread());
1462 MOZ_TRY(aFile
->SetPermissions(aPermissions
));
1467 Result
<bool, IOUtils::IOError
> IOUtils::ExistsSync(nsIFile
* aFile
) {
1468 MOZ_ASSERT(!NS_IsMainThread());
1470 bool exists
= false;
1471 MOZ_TRY(aFile
->Exists(&exists
));
1477 void IOUtils::GetProfileBeforeChange(GlobalObject
& aGlobal
,
1478 JS::MutableHandle
<JS::Value
> aClient
,
1480 MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
1481 MOZ_RELEASE_ASSERT(NS_IsMainThread());
1483 if (auto state
= GetState()) {
1484 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
!=
1485 ShutdownBlockerStatus::Uninitialized
);
1487 if (state
.ref()->mBlockerStatus
== ShutdownBlockerStatus::Failed
) {
1488 aRv
.ThrowAbortError("IOUtils: could not register shutdown blockers");
1492 MOZ_RELEASE_ASSERT(state
.ref()->mBlockerStatus
==
1493 ShutdownBlockerStatus::Initialized
);
1494 auto result
= state
.ref()->mEventQueue
->GetProfileBeforeChangeClient();
1495 if (result
.isErr()) {
1496 aRv
.ThrowAbortError("IOUtils: could not get shutdown client");
1500 RefPtr
<nsIAsyncShutdownClient
> client
= result
.unwrap();
1501 MOZ_RELEASE_ASSERT(client
);
1502 if (nsresult rv
= client
->GetJsclient(aClient
); NS_FAILED(rv
)) {
1503 aRv
.ThrowAbortError("IOUtils: Could not get shutdown jsclient");
1508 aRv
.ThrowAbortError(
1509 "IOUtils: profileBeforeChange phase has already finished");
1513 Maybe
<IOUtils::StateMutex::AutoLock
> IOUtils::GetState() {
1514 auto state
= sState
.Lock();
1515 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
1519 if (state
->mQueueStatus
== EventQueueStatus::Uninitialized
) {
1520 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
1521 state
->mEventQueue
= new EventQueue();
1522 state
->mQueueStatus
= EventQueueStatus::Initialized
;
1524 MOZ_RELEASE_ASSERT(state
->mBlockerStatus
==
1525 ShutdownBlockerStatus::Uninitialized
);
1528 if (NS_IsMainThread() &&
1529 state
->mBlockerStatus
== ShutdownBlockerStatus::Uninitialized
) {
1530 state
->SetShutdownHooks();
1533 return Some(std::move(state
));
1536 IOUtils::EventQueue::EventQueue() {
1537 MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue(
1538 "IOUtils::EventQueue", getter_AddRefs(mBackgroundEventTarget
)));
1540 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
1543 void IOUtils::State::SetShutdownHooks() {
1544 if (mBlockerStatus
!= ShutdownBlockerStatus::Uninitialized
) {
1548 if (NS_WARN_IF(NS_FAILED(mEventQueue
->SetShutdownHooks()))) {
1549 mBlockerStatus
= ShutdownBlockerStatus::Failed
;
1551 mBlockerStatus
= ShutdownBlockerStatus::Initialized
;
1554 if (mBlockerStatus
!= ShutdownBlockerStatus::Initialized
) {
1555 NS_WARNING("IOUtils: could not register shutdown blockers.");
1559 nsresult
IOUtils::EventQueue::SetShutdownHooks() {
1560 MOZ_RELEASE_ASSERT(NS_IsMainThread());
1562 nsCOMPtr
<nsIAsyncShutdownService
> svc
= services::GetAsyncShutdownService();
1564 return NS_ERROR_NOT_AVAILABLE
;
1567 nsCOMPtr
<nsIAsyncShutdownBlocker
> blocker
= new IOUtilsShutdownBlocker(
1568 IOUtilsShutdownBlocker::Phase::ProfileBeforeChange
);
1570 nsCOMPtr
<nsIAsyncShutdownClient
> profileBeforeChange
;
1571 MOZ_TRY(svc
->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange
)));
1572 MOZ_RELEASE_ASSERT(profileBeforeChange
);
1574 MOZ_TRY(profileBeforeChange
->AddBlocker(
1575 blocker
, NS_LITERAL_STRING_FROM_CSTRING(__FILE__
), __LINE__
,
1576 u
"IOUtils::EventQueue::SetShutdownHooks"_ns
));
1578 nsCOMPtr
<nsIAsyncShutdownClient
> xpcomWillShutdown
;
1579 MOZ_TRY(svc
->GetXpcomWillShutdown(getter_AddRefs(xpcomWillShutdown
)));
1580 MOZ_RELEASE_ASSERT(xpcomWillShutdown
);
1582 blocker
= new IOUtilsShutdownBlocker(
1583 IOUtilsShutdownBlocker::Phase::XpcomWillShutdown
);
1584 MOZ_TRY(xpcomWillShutdown
->AddBlocker(
1585 blocker
, NS_LITERAL_STRING_FROM_CSTRING(__FILE__
), __LINE__
,
1586 u
"IOUtils::EventQueue::SetShutdownHooks"_ns
));
1588 MOZ_TRY(svc
->MakeBarrier(
1589 u
"IOUtils: waiting for profileBeforeChange IO to complete"_ns
,
1590 getter_AddRefs(mProfileBeforeChangeBarrier
)));
1591 MOZ_RELEASE_ASSERT(mProfileBeforeChangeBarrier
);
1596 template <typename OkT
, typename Fn
>
1597 RefPtr
<IOUtils::IOPromise
<OkT
>> IOUtils::EventQueue::Dispatch(Fn aFunc
) {
1598 MOZ_RELEASE_ASSERT(mBackgroundEventTarget
);
1601 MakeRefPtr
<typename
IOUtils::IOPromise
<OkT
>::Private
>(__func__
);
1602 mBackgroundEventTarget
->Dispatch(
1603 NS_NewRunnableFunction("IOUtils::EventQueue::Dispatch",
1604 [promise
, func
= std::move(aFunc
)] {
1605 Result
<OkT
, IOError
> result
= func();
1606 if (result
.isErr()) {
1607 promise
->Reject(result
.unwrapErr(), __func__
);
1609 promise
->Resolve(result
.unwrap(), __func__
);
1612 NS_DISPATCH_EVENT_MAY_BLOCK
);
1616 Result
<already_AddRefed
<nsIAsyncShutdownClient
>, nsresult
>
1617 IOUtils::EventQueue::GetProfileBeforeChangeClient() {
1618 if (!mProfileBeforeChangeBarrier
) {
1619 return Err(NS_ERROR_NOT_AVAILABLE
);
1622 nsCOMPtr
<nsIAsyncShutdownClient
> profileBeforeChange
;
1623 MOZ_TRY(mProfileBeforeChangeBarrier
->GetClient(
1624 getter_AddRefs(profileBeforeChange
)));
1625 return profileBeforeChange
.forget();
1628 Result
<already_AddRefed
<nsIAsyncShutdownBarrier
>, nsresult
>
1629 IOUtils::EventQueue::GetProfileBeforeChangeBarrier() {
1630 if (!mProfileBeforeChangeBarrier
) {
1631 return Err(NS_ERROR_NOT_AVAILABLE
);
1634 return do_AddRef(mProfileBeforeChangeBarrier
);
1638 Result
<nsTArray
<uint8_t>, IOUtils::IOError
> IOUtils::MozLZ4::Compress(
1639 Span
<const uint8_t> aUncompressed
) {
1640 nsTArray
<uint8_t> result
;
1641 size_t worstCaseSize
=
1642 Compression::LZ4::maxCompressedSize(aUncompressed
.Length()) + HEADER_SIZE
;
1643 if (!result
.SetCapacity(worstCaseSize
, fallible
)) {
1644 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
)
1645 .WithMessage("Could not allocate buffer to compress data"));
1647 result
.AppendElements(Span(MAGIC_NUMBER
.data(), MAGIC_NUMBER
.size()));
1648 std::array
<uint8_t, sizeof(uint32_t)> contentSizeBytes
{};
1649 LittleEndian::writeUint32(contentSizeBytes
.data(), aUncompressed
.Length());
1650 result
.AppendElements(Span(contentSizeBytes
.data(), contentSizeBytes
.size()));
1652 if (aUncompressed
.Length() == 0) {
1653 // Don't try to compress an empty buffer.
1654 // Just return the correctly formed header.
1655 result
.SetLength(HEADER_SIZE
);
1659 size_t compressed
= Compression::LZ4::compress(
1660 reinterpret_cast<const char*>(aUncompressed
.Elements()),
1661 aUncompressed
.Length(),
1662 reinterpret_cast<char*>(result
.Elements()) + HEADER_SIZE
);
1665 IOError(NS_ERROR_UNEXPECTED
).WithMessage("Could not compress data"));
1667 result
.SetLength(HEADER_SIZE
+ compressed
);
1672 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::MozLZ4::Decompress(
1673 Span
<const uint8_t> aFileContents
, IOUtils::BufferKind aBufferKind
) {
1674 if (aFileContents
.LengthBytes() < HEADER_SIZE
) {
1676 IOError(NS_ERROR_FILE_CORRUPTED
)
1678 "Could not decompress file because the buffer is too short"));
1680 auto header
= aFileContents
.To(HEADER_SIZE
);
1681 if (!std::equal(std::begin(MAGIC_NUMBER
), std::end(MAGIC_NUMBER
),
1682 std::begin(header
))) {
1685 for (; i
< header
.Length() - 1; ++i
) {
1686 magicStr
.AppendPrintf("%02X ", header
.at(i
));
1688 magicStr
.AppendPrintf("%02X", header
.at(i
));
1690 return Err(IOError(NS_ERROR_FILE_CORRUPTED
)
1691 .WithMessage("Could not decompress file because it has an "
1692 "invalid LZ4 header (wrong magic number: '%s')",
1695 size_t numBytes
= sizeof(uint32_t);
1696 Span
<const uint8_t> sizeBytes
= header
.Last(numBytes
);
1697 uint32_t expectedDecompressedSize
=
1698 LittleEndian::readUint32(sizeBytes
.data());
1699 if (expectedDecompressedSize
== 0) {
1700 return JsBuffer::CreateEmpty(aBufferKind
);
1702 auto contents
= aFileContents
.From(HEADER_SIZE
);
1703 auto result
= JsBuffer::Create(aBufferKind
, expectedDecompressedSize
);
1704 if (result
.isErr()) {
1705 return result
.propagateErr();
1708 JsBuffer decompressed
= result
.unwrap();
1709 size_t actualSize
= 0;
1710 if (!Compression::LZ4::decompress(
1711 reinterpret_cast<const char*>(contents
.Elements()), contents
.Length(),
1712 reinterpret_cast<char*>(decompressed
.Elements()),
1713 expectedDecompressedSize
, &actualSize
)) {
1715 IOError(NS_ERROR_FILE_CORRUPTED
)
1717 "Could not decompress file contents, the file may be corrupt"));
1719 decompressed
.SetLength(actualSize
);
1720 return decompressed
;
1723 NS_IMPL_ISUPPORTS(IOUtilsShutdownBlocker
, nsIAsyncShutdownBlocker
,
1724 nsIAsyncShutdownCompletionCallback
);
1726 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetName(nsAString
& aName
) {
1727 aName
= u
"IOUtils Blocker ("_ns
;
1730 case Phase::ProfileBeforeChange
:
1731 aName
.Append(u
"profile-before-change"_ns
);
1734 case Phase::XpcomWillShutdown
:
1735 aName
.Append(u
"xpcom-will-shutdown"_ns
);
1739 MOZ_CRASH("Unknown shutdown phase");
1746 NS_IMETHODIMP
IOUtilsShutdownBlocker::BlockShutdown(
1747 nsIAsyncShutdownClient
* aBarrierClient
) {
1748 using EventQueueStatus
= IOUtils::EventQueueStatus
;
1750 MOZ_RELEASE_ASSERT(NS_IsMainThread());
1752 nsCOMPtr
<nsIAsyncShutdownBarrier
> barrier
;
1755 auto state
= IOUtils::sState
.Lock();
1756 if (state
->mQueueStatus
== EventQueueStatus::Shutdown
) {
1757 // If the blocker for profile-before-change has already run, then the
1758 // event queue is already torn down and we have nothing to do.
1760 MOZ_RELEASE_ASSERT(mPhase
== Phase::XpcomWillShutdown
);
1761 MOZ_RELEASE_ASSERT(!state
->mEventQueue
);
1763 Unused
<< NS_WARN_IF(NS_FAILED(aBarrierClient
->RemoveBlocker(this)));
1764 mParentClient
= nullptr;
1769 MOZ_RELEASE_ASSERT(state
->mEventQueue
);
1771 mParentClient
= aBarrierClient
;
1774 state
->mEventQueue
->GetProfileBeforeChangeBarrier().unwrapOr(nullptr);
1777 // We cannot barrier->Wait() while holding the mutex because it will lead to
1779 if (!barrier
|| NS_WARN_IF(NS_FAILED(barrier
->Wait(this)))) {
1780 // If we don't have a barrier, we still need to flush the IOUtils event
1781 // queue and disable task submission.
1783 // Likewise, if waiting on the barrier failed, we are going to make our best
1784 // attempt to clean up.
1791 NS_IMETHODIMP
IOUtilsShutdownBlocker::Done() {
1792 using EventQueueStatus
= IOUtils::EventQueueStatus
;
1794 MOZ_RELEASE_ASSERT(NS_IsMainThread());
1796 auto state
= IOUtils::sState
.Lock();
1797 MOZ_RELEASE_ASSERT(state
->mEventQueue
);
1799 // This method is called once we have served all shutdown clients. Now we
1800 // flush the remaining IO queue and forbid additional IO requests.
1801 state
->mEventQueue
->Dispatch
<Ok
>([]() { return Ok
{}; })
1802 ->Then(GetMainThreadSerialEventTarget(), __func__
,
1803 [self
= RefPtr(this)]() {
1804 if (self
->mParentClient
) {
1805 Unused
<< NS_WARN_IF(
1806 NS_FAILED(self
->mParentClient
->RemoveBlocker(self
)));
1807 self
->mParentClient
= nullptr;
1809 auto state
= IOUtils::sState
.Lock();
1810 MOZ_RELEASE_ASSERT(state
->mEventQueue
);
1811 state
->mEventQueue
= nullptr;
1815 MOZ_RELEASE_ASSERT(state
->mQueueStatus
== EventQueueStatus::Initialized
);
1816 state
->mQueueStatus
= EventQueueStatus::Shutdown
;
1821 NS_IMETHODIMP
IOUtilsShutdownBlocker::GetState(nsIPropertyBag
** aState
) {
1825 Result
<IOUtils::InternalWriteOpts
, IOUtils::IOError
>
1826 IOUtils::InternalWriteOpts::FromBinding(const WriteOptions
& aOptions
) {
1827 InternalWriteOpts opts
;
1828 opts
.mFlush
= aOptions
.mFlush
;
1829 opts
.mMode
= aOptions
.mMode
;
1831 if (aOptions
.mBackupFile
.WasPassed()) {
1832 opts
.mBackupFile
= new nsLocalFile();
1834 opts
.mBackupFile
->InitWithPath(aOptions
.mBackupFile
.Value());
1836 return Err(IOUtils::IOError(rv
).WithMessage(
1837 "Could not parse path of backupFile (%s)",
1838 NS_ConvertUTF16toUTF8(aOptions
.mBackupFile
.Value()).get()));
1842 if (aOptions
.mTmpPath
.WasPassed()) {
1843 opts
.mTmpFile
= new nsLocalFile();
1844 if (nsresult rv
= opts
.mTmpFile
->InitWithPath(aOptions
.mTmpPath
.Value());
1846 return Err(IOUtils::IOError(rv
).WithMessage(
1847 "Could not parse path of temp file (%s)",
1848 NS_ConvertUTF16toUTF8(aOptions
.mTmpPath
.Value()).get()));
1852 opts
.mCompress
= aOptions
.mCompress
;
1857 Result
<IOUtils::JsBuffer
, IOUtils::IOError
> IOUtils::JsBuffer::Create(
1858 IOUtils::BufferKind aBufferKind
, size_t aCapacity
) {
1859 JsBuffer
buffer(aBufferKind
, aCapacity
);
1860 if (aCapacity
!= 0 && !buffer
.mBuffer
) {
1861 return Err(IOError(NS_ERROR_OUT_OF_MEMORY
)
1862 .WithMessage("Could not allocate buffer"));
1868 IOUtils::JsBuffer
IOUtils::JsBuffer::CreateEmpty(
1869 IOUtils::BufferKind aBufferKind
) {
1870 JsBuffer
buffer(aBufferKind
, 0);
1871 MOZ_RELEASE_ASSERT(buffer
.mBuffer
== nullptr);
1875 IOUtils::JsBuffer::JsBuffer(IOUtils::BufferKind aBufferKind
, size_t aCapacity
)
1876 : mBufferKind(aBufferKind
), mCapacity(aCapacity
), mLength(0) {
1878 if (aBufferKind
== BufferKind::String
) {
1879 mBuffer
= JS::UniqueChars(
1880 js_pod_arena_malloc
<char>(js::StringBufferArena
, mCapacity
));
1882 MOZ_RELEASE_ASSERT(aBufferKind
== BufferKind::Uint8Array
);
1883 mBuffer
= JS::UniqueChars(
1884 js_pod_arena_malloc
<char>(js::ArrayBufferContentsArena
, mCapacity
));
1889 IOUtils::JsBuffer::JsBuffer(IOUtils::JsBuffer
&& aOther
) noexcept
1890 : mBufferKind(aOther
.mBufferKind
),
1891 mCapacity(aOther
.mCapacity
),
1892 mLength(aOther
.mLength
),
1893 mBuffer(std::move(aOther
.mBuffer
)) {
1894 aOther
.mCapacity
= 0;
1898 IOUtils::JsBuffer
& IOUtils::JsBuffer::operator=(
1899 IOUtils::JsBuffer
&& aOther
) noexcept
{
1900 mBufferKind
= aOther
.mBufferKind
;
1901 mCapacity
= aOther
.mCapacity
;
1902 mLength
= aOther
.mLength
;
1903 mBuffer
= std::move(aOther
.mBuffer
);
1905 // Invalidate aOther.
1906 aOther
.mCapacity
= 0;
1913 JSString
* IOUtils::JsBuffer::IntoString(JSContext
* aCx
, JsBuffer aBuffer
) {
1914 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::String
);
1916 if (!aBuffer
.mCapacity
) {
1917 return JS_GetEmptyString(aCx
);
1920 if (IsAscii(aBuffer
.BeginReading())) {
1921 // If the string is just plain ASCII, then we can hand the buffer off to
1922 // JavaScript as a Latin1 string (since ASCII is a subset of Latin1).
1923 JS::UniqueLatin1Chars
asLatin1(
1924 reinterpret_cast<JS::Latin1Char
*>(aBuffer
.mBuffer
.release()));
1925 return JS_NewLatin1String(aCx
, std::move(asLatin1
), aBuffer
.mLength
);
1928 // If the string is encodable as Latin1, we need to deflate the string to a
1929 // Latin1 string to accoutn for UTF-8 characters that are encoded as more than
1932 // Otherwise, the string contains characters outside Latin1 so we have to
1933 // inflate to UTF-16.
1934 return JS_NewStringCopyUTF8N(
1935 aCx
, JS::UTF8Chars(aBuffer
.mBuffer
.get(), aBuffer
.mLength
));
1939 JSObject
* IOUtils::JsBuffer::IntoUint8Array(JSContext
* aCx
, JsBuffer aBuffer
) {
1940 MOZ_RELEASE_ASSERT(aBuffer
.mBufferKind
== IOUtils::BufferKind::Uint8Array
);
1942 if (!aBuffer
.mCapacity
) {
1943 return JS_NewUint8Array(aCx
, 0);
1946 char* rawBuffer
= aBuffer
.mBuffer
.release();
1947 MOZ_RELEASE_ASSERT(rawBuffer
);
1948 JS::Rooted
<JSObject
*> arrayBuffer(
1949 aCx
, JS::NewArrayBufferWithContents(aCx
, aBuffer
.mLength
,
1950 reinterpret_cast<void*>(rawBuffer
)));
1953 // The array buffer does not take ownership of the data pointer unless
1954 // creation succeeds. We are still on the hook to free it.
1956 // aBuffer will be destructed at end of scope, but its destructor does not
1957 // take into account |mCapacity| or |mLength|, so it is OK for them to be
1958 // non-zero here with a null |mBuffer|.
1963 return JS_NewUint8ArrayWithBuffer(aCx
, arrayBuffer
, 0, aBuffer
.mLength
);
1966 [[nodiscard
]] bool ToJSValue(JSContext
* aCx
, IOUtils::JsBuffer
&& aBuffer
,
1967 JS::MutableHandle
<JS::Value
> aValue
) {
1968 if (aBuffer
.mBufferKind
== IOUtils::BufferKind::String
) {
1969 JSString
* str
= IOUtils::JsBuffer::IntoString(aCx
, std::move(aBuffer
));
1974 aValue
.setString(str
);
1978 JSObject
* array
= IOUtils::JsBuffer::IntoUint8Array(aCx
, std::move(aBuffer
));
1983 aValue
.setObject(*array
);
1987 } // namespace mozilla::dom
1989 #undef REJECT_IF_INIT_PATH_FAILED