FileSystemWritableFileStream.cpp (35545B)
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/. */ 6 7 #include "FileSystemWritableFileStream.h" 8 9 #include "fs/FileSystemAsyncCopy.h" 10 #include "fs/FileSystemShutdownBlocker.h" 11 #include "fs/FileSystemThreadSafeStreamOwner.h" 12 #include "mozilla/ErrorResult.h" 13 #include "mozilla/InputStreamLengthHelper.h" 14 #include "mozilla/MozPromise.h" 15 #include "mozilla/SpinEventLoopUntil.h" 16 #include "mozilla/TaskQueue.h" 17 #include "mozilla/dom/Blob.h" 18 #include "mozilla/dom/FileSystemHandle.h" 19 #include "mozilla/dom/FileSystemLog.h" 20 #include "mozilla/dom/FileSystemManager.h" 21 #include "mozilla/dom/FileSystemWritableFileStreamBinding.h" 22 #include "mozilla/dom/FileSystemWritableFileStreamChild.h" 23 #include "mozilla/dom/Promise.h" 24 #include "mozilla/dom/PromiseNativeHandler.h" 25 #include "mozilla/dom/WorkerCommon.h" 26 #include "mozilla/dom/WorkerPrivate.h" 27 #include "mozilla/dom/WorkerRef.h" 28 #include "mozilla/dom/WritableStreamDefaultController.h" 29 #include "mozilla/dom/quota/QuotaCommon.h" 30 #include "mozilla/dom/quota/ResultExtensions.h" 31 #include "mozilla/dom/quota/TargetPtrHolder.h" 32 #include "mozilla/ipc/RandomAccessStreamUtils.h" 33 #include "nsAsyncStreamCopier.h" 34 #include "nsIInputStream.h" 35 #include "nsIRequestObserver.h" 36 #include "nsISupportsImpl.h" 37 #include "nsNetCID.h" 38 #include "nsNetUtil.h" 39 #include "nsStreamUtils.h" 40 #include "nsStringStream.h" 41 42 namespace mozilla::dom { 43 44 namespace { 45 46 CopyableErrorResult RejectWithConvertedErrors(nsresult aRv) { 47 CopyableErrorResult err; 48 switch (aRv) { 49 case NS_ERROR_DOM_FILE_NOT_FOUND_ERR: 50 [[fallthrough]]; 51 case NS_ERROR_FILE_NOT_FOUND: 52 err.ThrowNotFoundError("File not found"); 53 break; 54 case NS_ERROR_FILE_NO_DEVICE_SPACE: 55 err.ThrowQuotaExceededError("Quota exceeded"); 56 break; 57 default: 58 err.Throw(aRv); 59 } 60 61 return err; 62 } 63 64 RefPtr<FileSystemWritableFileStream::WriteDataPromise> ResolvePromise( 65 const Int64Promise::ResolveOrRejectValue& aValue) { 66 MOZ_ASSERT(aValue.IsResolve()); 67 return FileSystemWritableFileStream::WriteDataPromise::CreateAndResolve( 68 Some(aValue.ResolveValue()), __func__); 69 } 70 71 RefPtr<FileSystemWritableFileStream::WriteDataPromise> ResolvePromise( 72 const BoolPromise::ResolveOrRejectValue& aValue) { 73 MOZ_ASSERT(aValue.IsResolve()); 74 return FileSystemWritableFileStream::WriteDataPromise::CreateAndResolve( 75 Nothing(), __func__); 76 } 77 78 class WritableFileStreamUnderlyingSinkAlgorithms final 79 : public UnderlyingSinkAlgorithmsWrapper { 80 NS_DECL_ISUPPORTS_INHERITED 81 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( 82 WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) 83 84 explicit WritableFileStreamUnderlyingSinkAlgorithms( 85 FileSystemWritableFileStream& aStream) 86 : mStream(&aStream) {} 87 88 already_AddRefed<Promise> WriteCallbackImpl( 89 JSContext* aCx, JS::Handle<JS::Value> aChunk, 90 WritableStreamDefaultController& aController, ErrorResult& aRv) override; 91 92 already_AddRefed<Promise> CloseCallbackImpl(JSContext* aCx, 93 ErrorResult& aRv) override; 94 95 already_AddRefed<Promise> AbortCallbackImpl( 96 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 97 ErrorResult& aRv) override; 98 99 void ReleaseObjects() override; 100 101 private: 102 ~WritableFileStreamUnderlyingSinkAlgorithms() = default; 103 104 RefPtr<FileSystemWritableFileStream> mStream; 105 }; 106 107 } // namespace 108 109 class FileSystemWritableFileStream::Command { 110 public: 111 explicit Command(RefPtr<FileSystemWritableFileStream> aWritableFileStream) 112 : mWritableFileStream(std::move(aWritableFileStream)) { 113 MOZ_ASSERT(mWritableFileStream); 114 } 115 116 NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::Command) 117 118 private: 119 ~Command() { mWritableFileStream->NoteFinishedCommand(); } 120 121 RefPtr<FileSystemWritableFileStream> mWritableFileStream; 122 }; 123 124 class FileSystemWritableFileStream::CloseHandler { 125 enum struct State : uint8_t { Initial = 0, Open, Closing, Closed }; 126 127 public: 128 CloseHandler() 129 : mShutdownBlocker(fs::FileSystemShutdownBlocker::CreateForWritable()), 130 mClosePromiseHolder(), 131 mState(State::Initial) {} 132 133 NS_INLINE_DECL_REFCOUNTING(FileSystemWritableFileStream::CloseHandler) 134 135 /** 136 * @brief Are we not yet closing? 137 */ 138 bool IsOpen() const { return State::Open == mState; } 139 140 /** 141 * @brief Are we not open and not closed? 142 */ 143 bool IsClosing() const { return State::Closing == mState; } 144 145 /** 146 * @brief Are we already fully closed? 147 */ 148 bool IsClosed() const { return State::Closed == mState; } 149 150 /** 151 * @brief Transition from open to closing state 152 * 153 * @return true if the state was open and became closing after the call 154 * @return false in all the other cases the previous state is preserved 155 */ 156 bool SetClosing() { 157 const bool isOpen = State::Open == mState; 158 159 if (isOpen) { 160 mState = State::Closing; 161 } 162 163 return isOpen; 164 } 165 166 RefPtr<BoolPromise> GetClosePromise() const { 167 MOZ_ASSERT(State::Open != mState, 168 "Please call SetClosing before GetClosePromise"); 169 170 if (State::Closing == mState) { 171 return mClosePromiseHolder.Ensure(__func__); 172 } 173 174 // Instant resolve for initial state due to early shutdown or closed state 175 return BoolPromise::CreateAndResolve(true, __func__); 176 } 177 178 /** 179 * @brief Transition from initial to open state. In initial state 180 * 181 */ 182 void Open(std::function<void()>&& aCallback) { 183 MOZ_ASSERT(State::Initial == mState); 184 185 mShutdownBlocker->SetCallback(std::move(aCallback)); 186 mShutdownBlocker->Block(); 187 188 mState = State::Open; 189 } 190 191 /** 192 * @brief Transition to closed state and resolve all pending promises. 193 * 194 */ 195 void Close() { 196 mShutdownBlocker->Unblock(); 197 mShutdownBlocker = nullptr; 198 mState = State::Closed; 199 mClosePromiseHolder.ResolveIfExists(true, __func__); 200 } 201 202 protected: 203 virtual ~CloseHandler() = default; 204 205 private: 206 RefPtr<fs::FileSystemShutdownBlocker> mShutdownBlocker; 207 208 mutable MozPromiseHolder<BoolPromise> mClosePromiseHolder; 209 210 State mState; 211 }; 212 213 FileSystemWritableFileStream::FileSystemWritableFileStream( 214 const nsCOMPtr<nsIGlobalObject>& aGlobal, 215 RefPtr<FileSystemManager>& aManager, 216 mozilla::ipc::RandomAccessStreamParams&& aStreamParams, 217 RefPtr<FileSystemWritableFileStreamChild> aActor, 218 already_AddRefed<TaskQueue> aTaskQueue, 219 const fs::FileSystemEntryMetadata& aMetadata) 220 : WritableStream(aGlobal, HoldDropJSObjectsCaller::Explicit), 221 mManager(aManager), 222 mActor(std::move(aActor)), 223 mTaskQueue(aTaskQueue), 224 mStreamParams(std::move(aStreamParams)), 225 mMetadata(std::move(aMetadata)), 226 mCloseHandler(MakeAndAddRef<CloseHandler>()), 227 mCommandActive(false) { 228 LOG(("Created WritableFileStream %p", this)); 229 230 // Connect with the actor directly in the constructor. This way the actor 231 // can call `FileSystemWritableFileStream::ClearActor` when we call 232 // `PFileSystemWritableFileStreamChild::Send__delete__` even when 233 // FileSystemWritableFileStream::Create fails, in which case the not yet 234 // fully constructed FileSystemWritableFileStream is being destroyed. 235 mActor->SetStream(this); 236 237 mozilla::HoldJSObjects(this); 238 } 239 240 FileSystemWritableFileStream::~FileSystemWritableFileStream() { 241 MOZ_ASSERT(!mCommandActive); 242 MOZ_ASSERT(IsDone()); 243 244 mozilla::DropJSObjects(this); 245 } 246 247 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream 248 // * This is fallible because of OOM handling of JSAPI. See bug 1762233. 249 // XXX(krosylight): _BOUNDARY because SetUpNative here can't run script because 250 // StartCallback here is no-op. Can we let the static check automatically detect 251 // this situation? 252 /* static */ 253 MOZ_CAN_RUN_SCRIPT_BOUNDARY 254 Result<RefPtr<FileSystemWritableFileStream>, nsresult> 255 FileSystemWritableFileStream::Create( 256 const nsCOMPtr<nsIGlobalObject>& aGlobal, 257 RefPtr<FileSystemManager>& aManager, 258 mozilla::ipc::RandomAccessStreamParams&& aStreamParams, 259 RefPtr<FileSystemWritableFileStreamChild> aActor, 260 const fs::FileSystemEntryMetadata& aMetadata) { 261 MOZ_ASSERT(aGlobal); 262 263 QM_TRY_UNWRAP(auto streamTransportService, 264 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>, 265 MOZ_SELECT_OVERLOAD(do_GetService), 266 NS_STREAMTRANSPORTSERVICE_CONTRACTID)); 267 268 RefPtr<TaskQueue> taskQueue = 269 TaskQueue::Create(streamTransportService.forget(), "WritableStreamQueue"); 270 MOZ_ASSERT(taskQueue); 271 272 AutoJSAPI jsapi; 273 if (!jsapi.Init(aGlobal)) { 274 return Err(NS_ERROR_FAILURE); 275 } 276 JSContext* cx = jsapi.cx(); 277 278 // Step 1. Let stream be a new FileSystemWritableFileStream in realm. 279 // Step 2. Set stream.[[file]] to file. (Covered by the constructor) 280 RefPtr<FileSystemWritableFileStream> stream = 281 new FileSystemWritableFileStream( 282 aGlobal, aManager, std::move(aStreamParams), std::move(aActor), 283 taskQueue.forget(), aMetadata); 284 285 auto autoClose = MakeScopeExit([stream] { 286 stream->mCloseHandler->Close(); 287 stream->mActor->SendClose(/* aAbort */ true); 288 }); 289 290 QM_TRY_UNWRAP( 291 RefPtr<StrongWorkerRef> workerRef, 292 ([stream]() -> Result<RefPtr<StrongWorkerRef>, nsresult> { 293 WorkerPrivate* const workerPrivate = GetCurrentThreadWorkerPrivate(); 294 if (!workerPrivate) { 295 return RefPtr<StrongWorkerRef>(); 296 } 297 298 RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create( 299 workerPrivate, "FileSystemWritableFileStream::Create", [stream]() { 300 if (stream->IsOpen()) { 301 // We don't need the promise, we just 302 // begin the closing process. 303 (void)stream->BeginAbort(); 304 } 305 }); 306 QM_TRY(MOZ_TO_RESULT(workerRef)); 307 308 return workerRef; 309 }())); 310 311 // Step 3 - 5 312 auto algorithms = 313 MakeRefPtr<WritableFileStreamUnderlyingSinkAlgorithms>(*stream); 314 315 // Step 8: Set up stream with writeAlgorithm set to writeAlgorithm, 316 // closeAlgorithm set to closeAlgorithm, abortAlgorithm set to 317 // abortAlgorithm, highWaterMark set to highWaterMark, and 318 // sizeAlgorithm set to sizeAlgorithm. 319 IgnoredErrorResult rv; 320 stream->SetUpNative(cx, *algorithms, 321 // Step 6. Let highWaterMark be 1. 322 Some(1), 323 // Step 7. Let sizeAlgorithm be an algorithm 324 // that returns 1. (nullptr returns 1, See 325 // WritableStream::Constructor for details) 326 nullptr, rv); 327 if (rv.Failed()) { 328 return Err(rv.StealNSResult()); 329 } 330 331 autoClose.release(); 332 333 stream->mWorkerRef = std::move(workerRef); 334 335 // The close handler passes this callback to `FileSystemShutdownBlocker` 336 // which has separate handling for debug and release builds. Basically, 337 // there's no content process shutdown blocking in release builds, so the 338 // callback might be executed in debug builds only. 339 stream->mCloseHandler->Open([stream]() { 340 if (stream->IsOpen()) { 341 // We don't need the promise, we just begin the closing process. 342 (void)stream->BeginAbort(); 343 } 344 }); 345 346 // Step 9: Return stream. 347 return stream; 348 } 349 350 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(FileSystemWritableFileStream, 351 WritableStream) 352 353 NS_IMPL_CYCLE_COLLECTION_CLASS(FileSystemWritableFileStream) 354 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileSystemWritableFileStream, 355 WritableStream) 356 // Per the comment for the FileSystemManager class, don't unlink mManager! 357 if (tmp->IsOpen()) { 358 (void)tmp->BeginAbort(); 359 } 360 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 361 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(FileSystemWritableFileStream, 362 WritableStream) 363 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mManager) 364 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 365 366 void FileSystemWritableFileStream::LastRelease() { 367 // We can't call `FileSystemWritableFileStream::Close` here because it may 368 // need to keep FileSystemWritableFileStream object alive which isn't possible 369 // when the object is about to be deleted. There are other mechanisms which 370 // ensure that the object is correctly closed before destruction. For example 371 // the object unlinking and the worker shutdown (we get notified about it via 372 // the callback passed to `StrongWorkerRef`) are used to close the object if 373 // it hasn't been closed yet. 374 375 if (mActor) { 376 PFileSystemWritableFileStreamChild::Send__delete__(mActor); 377 MOZ_ASSERT(!mActor); 378 } 379 } 380 381 RefPtr<FileSystemWritableFileStream::Command> 382 FileSystemWritableFileStream::CreateCommand() { 383 MOZ_ASSERT(!mCommandActive); 384 385 mCommandActive = true; 386 387 return MakeRefPtr<Command>(this); 388 } 389 390 bool FileSystemWritableFileStream::IsCommandActive() const { 391 return mCommandActive; 392 } 393 394 void FileSystemWritableFileStream::ClearActor() { 395 MOZ_ASSERT(mActor); 396 397 mActor = nullptr; 398 } 399 400 bool FileSystemWritableFileStream::IsOpen() const { 401 return mCloseHandler->IsOpen(); 402 } 403 404 bool FileSystemWritableFileStream::IsFinishing() const { 405 return mCloseHandler->IsClosing(); 406 } 407 408 bool FileSystemWritableFileStream::IsDone() const { 409 return mCloseHandler->IsClosed(); 410 } 411 412 RefPtr<BoolPromise> FileSystemWritableFileStream::BeginFinishing( 413 bool aShouldAbort) { 414 using ClosePromise = PFileSystemWritableFileStreamChild::ClosePromise; 415 MOZ_ASSERT(IsOpen()); 416 417 if (mCloseHandler->SetClosing()) { 418 Finish() 419 ->Then(mTaskQueue, __func__, 420 [selfHolder = quota::TargetPtrHolder(this)]() mutable { 421 if (selfHolder->mStreamOwner) { 422 selfHolder->mStreamOwner->Close(); 423 } else { 424 // If the stream was not deserialized, `mStreamParams` still 425 // contains a pre-opened file descriptor which needs to be 426 // closed here by moving `mStreamParams` to a local variable 427 // (the file descriptor will be closed for real when 428 // `streamParams` goes out of scope). 429 430 mozilla::ipc::RandomAccessStreamParams streamParams( 431 std::move(selfHolder->mStreamParams)); 432 } 433 434 return BoolPromise::CreateAndResolve(true, __func__); 435 }) 436 ->Then(GetCurrentSerialEventTarget(), __func__, 437 [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) { 438 return self->mTaskQueue->BeginShutdown(); 439 }) 440 ->Then(GetCurrentSerialEventTarget(), __func__, 441 [aShouldAbort, self = RefPtr(this)]( 442 const ShutdownPromise::ResolveOrRejectValue& /* aValue */) { 443 if (!self->mActor) { 444 return ClosePromise::CreateAndResolve(void_t(), __func__); 445 } 446 447 return self->mActor->SendClose(aShouldAbort); 448 }) 449 ->Then(GetCurrentSerialEventTarget(), __func__, 450 [self = RefPtr(this)]( 451 const ClosePromise::ResolveOrRejectValue& aValue) { 452 self->mWorkerRef = nullptr; 453 self->mCloseHandler->Close(); 454 455 QM_TRY(OkIf(aValue.IsResolve()), QM_VOID); 456 }); 457 } 458 459 return mCloseHandler->GetClosePromise(); 460 } 461 462 RefPtr<BoolPromise> FileSystemWritableFileStream::BeginClose() { 463 MOZ_ASSERT(IsOpen()); 464 return BeginFinishing(/* aShouldAbort */ false); 465 } 466 467 RefPtr<BoolPromise> FileSystemWritableFileStream::BeginAbort() { 468 MOZ_ASSERT(IsOpen()); 469 return BeginFinishing(/* aShouldAbort */ true); 470 } 471 472 RefPtr<BoolPromise> FileSystemWritableFileStream::OnDone() { 473 MOZ_ASSERT(!IsOpen()); 474 475 return mCloseHandler->GetClosePromise(); 476 } 477 478 already_AddRefed<Promise> FileSystemWritableFileStream::Write( 479 JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aError) { 480 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream 481 // Step 3. Let writeAlgorithm be an algorithm which takes a chunk argument 482 // and returns the result of running the write a chunk algorithm with stream 483 // and chunk. 484 485 aError.MightThrowJSException(); 486 487 // https://fs.spec.whatwg.org/#write-a-chunk 488 // Step 1. Let input be the result of converting chunk to a 489 // FileSystemWriteChunkType. 490 491 ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams data; 492 if (!data.Init(aCx, aChunk)) { 493 aError.StealExceptionFromJSContext(aCx); 494 // XXX(krosylight): The Streams spec does not provide a way to catch errors 495 // thrown from the write algorithm, and the File System spec does not try 496 // catching them either. This is unfortunate, as per the spec the type error 497 // from write() must immediately return a rejected promise without any file 498 // handle closure. For now we handle it here manually. See also: 499 // - https://github.com/whatwg/streams/issues/636 500 // - https://github.com/whatwg/fs/issues/153 501 if (IsOpen()) { 502 (void)BeginAbort(); 503 } 504 return nullptr; 505 } 506 507 // Step 2. Let p be a new promise. 508 RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); 509 510 RefPtr<Command> command = CreateCommand(); 511 512 // Step 3.3. 513 // XXX: This should ideally be also handled by the streams but we don't 514 // currently have the hook. See https://github.com/whatwg/streams/issues/620. 515 Write(data)->Then( 516 GetCurrentSerialEventTarget(), __func__, 517 [self = RefPtr{this}, command, 518 promise](const WriteDataPromise::ResolveOrRejectValue& aValue) { 519 MOZ_ASSERT(!aValue.IsNothing()); 520 if (aValue.IsResolve()) { 521 const Maybe<int64_t>& maybeWritten = aValue.ResolveValue(); 522 if (maybeWritten.isSome()) { 523 promise->MaybeResolve(maybeWritten.value()); 524 return; 525 } 526 527 promise->MaybeResolveWithUndefined(); 528 return; 529 } 530 531 CopyableErrorResult err = aValue.RejectValue(); 532 533 if (self->IsOpen()) { 534 self->BeginAbort()->Then( 535 GetCurrentSerialEventTarget(), __func__, 536 [promise, err = std::move(err)]( 537 const BoolPromise::ResolveOrRejectValue&) mutable { 538 // Do not capture command to this context: 539 // close cannot proceed 540 promise->MaybeReject(std::move(err)); 541 }); 542 } else if (self->IsFinishing()) { 543 self->OnDone()->Then( 544 GetCurrentSerialEventTarget(), __func__, 545 [promise, err = std::move(err)]( 546 const BoolPromise::ResolveOrRejectValue&) mutable { 547 // Do not capture command to this context: 548 // close cannot proceed 549 promise->MaybeReject(std::move(err)); 550 }); 551 552 } else { 553 promise->MaybeReject(std::move(err)); 554 } 555 }); 556 557 return promise.forget(); 558 } 559 560 RefPtr<FileSystemWritableFileStream::WriteDataPromise> 561 FileSystemWritableFileStream::Write( 562 ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData) { 563 auto rejectWithTypeError = [](const auto& aMessage) { 564 CopyableErrorResult err; 565 err.ThrowTypeError(aMessage); 566 return WriteDataPromise::CreateAndReject(err, __func__); 567 }; 568 569 auto rejectWithSyntaxError = [](const auto& aMessage) { 570 CopyableErrorResult err; 571 err.ThrowSyntaxError(aMessage); 572 return WriteDataPromise::CreateAndReject(err, __func__); 573 }; 574 575 if (!IsOpen()) { 576 return rejectWithTypeError("WritableFileStream closed"); 577 } 578 579 auto tryResolve = [self = RefPtr{this}](const auto& aValue) 580 -> RefPtr<FileSystemWritableFileStream::WriteDataPromise> { 581 MOZ_ASSERT(self->IsCommandActive()); 582 583 if (aValue.IsResolve()) { 584 return ResolvePromise(aValue); 585 } 586 587 MOZ_ASSERT(aValue.IsReject()); 588 return WriteDataPromise::CreateAndReject( 589 RejectWithConvertedErrors(aValue.RejectValue()), __func__); 590 }; 591 592 auto tryResolveInt64 = 593 [tryResolve](const Int64Promise::ResolveOrRejectValue& aValue) { 594 return tryResolve(aValue); 595 }; 596 597 auto tryResolveBool = 598 [tryResolve](const BoolPromise::ResolveOrRejectValue& aValue) { 599 return tryResolve(aValue); 600 }; 601 602 // Step 3.3. Let command be input.type if input is a WriteParams, ... 603 if (aData.IsWriteParams()) { 604 const WriteParams& params = aData.GetAsWriteParams(); 605 switch (params.mType) { 606 // Step 3.4. If command is "write": 607 case WriteCommandType::Write: { 608 if (!params.mData.WasPassed()) { 609 return rejectWithSyntaxError("write() requires data"); 610 } 611 612 // Step 3.4.2. If data is undefined, reject p with a TypeError and 613 // abort. 614 if (params.mData.Value().IsNull()) { 615 return rejectWithTypeError("write() of null data"); 616 } 617 618 Maybe<uint64_t> position; 619 620 if (params.mPosition.WasPassed()) { 621 if (params.mPosition.Value().IsNull()) { 622 return rejectWithTypeError("write() with null position"); 623 } 624 625 position = Some(params.mPosition.Value().Value()); 626 } 627 628 return Write(params.mData.Value().Value(), position) 629 ->Then(GetCurrentSerialEventTarget(), __func__, 630 std::move(tryResolveInt64)); 631 } 632 633 // Step 3.5. Otherwise, if command is "seek": 634 case WriteCommandType::Seek: 635 if (!params.mPosition.WasPassed()) { 636 return rejectWithSyntaxError("seek() requires a position"); 637 } 638 639 // Step 3.5.1. If chunk.position is undefined, reject p with a 640 // TypeError and abort. 641 if (params.mPosition.Value().IsNull()) { 642 return rejectWithTypeError("seek() with null position"); 643 } 644 645 return Seek(params.mPosition.Value().Value()) 646 ->Then(GetCurrentSerialEventTarget(), __func__, 647 std::move(tryResolveBool)); 648 649 // Step 3.6. Otherwise, if command is "truncate": 650 case WriteCommandType::Truncate: 651 if (!params.mSize.WasPassed()) { 652 return rejectWithSyntaxError("truncate() requires a size"); 653 } 654 655 // Step 3.6.1. If chunk.size is undefined, reject p with a TypeError 656 // and abort. 657 if (params.mSize.Value().IsNull()) { 658 return rejectWithTypeError("truncate() with null size"); 659 } 660 661 return Truncate(params.mSize.Value().Value()) 662 ->Then(GetCurrentSerialEventTarget(), __func__, 663 std::move(tryResolveBool)); 664 665 default: 666 MOZ_CRASH("Bad WriteParams value!"); 667 } 668 } 669 670 // Step 3.3. ... and "write" otherwise. 671 // Step 3.4. If command is "write": 672 return Write(aData, Nothing()) 673 ->Then(GetCurrentSerialEventTarget(), __func__, 674 std::move(tryResolveInt64)); 675 } 676 677 // WebIDL Boilerplate 678 679 JSObject* FileSystemWritableFileStream::WrapObject( 680 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 681 return FileSystemWritableFileStream_Binding::Wrap(aCx, this, aGivenProto); 682 } 683 684 // WebIDL Interface 685 686 already_AddRefed<Promise> FileSystemWritableFileStream::Write( 687 const ArrayBufferViewOrArrayBufferOrBlobOrUTF8StringOrWriteParams& aData, 688 ErrorResult& aError) { 689 // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-write 690 // Step 1. Let writer be the result of getting a writer for this. 691 RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError); 692 if (aError.Failed()) { 693 return nullptr; 694 } 695 696 // Step 2. Let result be the result of writing a chunk to writer given data. 697 AutoJSAPI jsapi; 698 if (!jsapi.Init(GetParentObject())) { 699 aError.ThrowUnknownError("Internal error"); 700 return nullptr; 701 } 702 703 JSContext* cx = jsapi.cx(); 704 705 JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); 706 707 JS::Rooted<JS::Value> val(cx); 708 if (!aData.ToJSVal(cx, global, &val)) { 709 aError.ThrowUnknownError("Internal error"); 710 return nullptr; 711 } 712 713 RefPtr<Promise> promise = writer->Write(cx, val, aError); 714 715 // Step 3. Release writer. 716 writer->ReleaseLock(cx); 717 718 // Step 4. Return result. 719 return promise.forget(); 720 } 721 722 already_AddRefed<Promise> FileSystemWritableFileStream::Seek( 723 uint64_t aPosition, ErrorResult& aError) { 724 // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-seek 725 // Step 1. Let writer be the result of getting a writer for this. 726 RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError); 727 if (aError.Failed()) { 728 return nullptr; 729 } 730 731 // Step 2. Let result be the result of writing a chunk to writer given 732 // «[ "type" → "seek", "position" → position ]». 733 AutoJSAPI jsapi; 734 if (!jsapi.Init(GetParentObject())) { 735 aError.ThrowUnknownError("Internal error"); 736 return nullptr; 737 } 738 739 JSContext* cx = jsapi.cx(); 740 741 RootedDictionary<WriteParams> writeParams(cx); 742 writeParams.mType = WriteCommandType::Seek; 743 writeParams.mPosition.Construct(aPosition); 744 745 JS::Rooted<JS::Value> val(cx); 746 if (!ToJSValue(cx, writeParams, &val)) { 747 aError.ThrowUnknownError("Internal error"); 748 return nullptr; 749 } 750 751 RefPtr<Promise> promise = writer->Write(cx, val, aError); 752 753 // Step 3. Release writer. 754 writer->ReleaseLock(cx); 755 756 // Step 4. Return result. 757 return promise.forget(); 758 } 759 760 already_AddRefed<Promise> FileSystemWritableFileStream::Truncate( 761 uint64_t aSize, ErrorResult& aError) { 762 // https://fs.spec.whatwg.org/#dom-filesystemwritablefilestream-truncate 763 // Step 1. Let writer be the result of getting a writer for this. 764 RefPtr<WritableStreamDefaultWriter> writer = GetWriter(aError); 765 if (aError.Failed()) { 766 return nullptr; 767 } 768 769 // Step 2. Let result be the result of writing a chunk to writer given 770 // «[ "type" → "truncate", "size" → size ]». 771 AutoJSAPI jsapi; 772 if (!jsapi.Init(GetParentObject())) { 773 aError.ThrowUnknownError("Internal error"); 774 return nullptr; 775 } 776 777 JSContext* cx = jsapi.cx(); 778 779 RootedDictionary<WriteParams> writeParams(cx); 780 writeParams.mType = WriteCommandType::Truncate; 781 writeParams.mSize.Construct(aSize); 782 783 JS::Rooted<JS::Value> val(cx); 784 if (!ToJSValue(cx, writeParams, &val)) { 785 aError.ThrowUnknownError("Internal error"); 786 return nullptr; 787 } 788 789 RefPtr<Promise> promise = writer->Write(cx, val, aError); 790 791 // Step 3. Release writer. 792 writer->ReleaseLock(cx); 793 794 // Step 4. Return result. 795 return promise.forget(); 796 } 797 798 template <typename T> 799 RefPtr<Int64Promise> FileSystemWritableFileStream::Write( 800 const T& aData, const Maybe<uint64_t> aPosition) { 801 MOZ_ASSERT(IsOpen()); 802 803 nsCOMPtr<nsIInputStream> inputStream; 804 805 // https://fs.spec.whatwg.org/#write-a-chunk 806 // Step 3.4.6 If data is a BufferSource, let dataBytes be a copy of data. 807 auto vectorFromTypedArray = CreateFromTypedArrayData<Vector<uint8_t>>(aData); 808 if (vectorFromTypedArray.isSome()) { 809 Maybe<Vector<uint8_t>>& maybeVector = vectorFromTypedArray.ref(); 810 QM_TRY(MOZ_TO_RESULT(maybeVector.isSome()), CreateAndRejectInt64Promise); 811 812 // Here we copy 813 814 size_t length = maybeVector->length(); 815 QM_TRY(MOZ_TO_RESULT(NS_NewByteInputStream( 816 getter_AddRefs(inputStream), 817 AsChars(Span(maybeVector->extractOrCopyRawBuffer(), length)), 818 NS_ASSIGNMENT_ADOPT)), 819 CreateAndRejectInt64Promise); 820 821 return WriteImpl(std::move(inputStream), aPosition); 822 } 823 824 // Step 3.4.7 Otherwise, if data is a Blob ... 825 if (aData.IsBlob()) { 826 Blob& blob = aData.GetAsBlob(); 827 828 ErrorResult error; 829 blob.CreateInputStream(getter_AddRefs(inputStream), error); 830 QM_TRY((MOZ_TO_RESULT(!error.Failed()).mapErr([&error](const nsresult rv) { 831 return error.StealNSResult(); 832 })), 833 CreateAndRejectInt64Promise); 834 835 return WriteImpl(std::move(inputStream), aPosition); 836 } 837 838 // Step 3.4.8 Otherwise ... 839 MOZ_ASSERT(aData.IsUTF8String()); 840 841 // Here we copy 842 nsCString dataString; 843 if (!dataString.Assign(aData.GetAsUTF8String(), mozilla::fallible)) { 844 return Int64Promise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); 845 } 846 847 // Input stream takes ownership 848 QM_TRY(MOZ_TO_RESULT(NS_NewCStringInputStream(getter_AddRefs(inputStream), 849 std::move(dataString))), 850 CreateAndRejectInt64Promise); 851 852 return WriteImpl(std::move(inputStream), aPosition); 853 } 854 855 RefPtr<Int64Promise> FileSystemWritableFileStream::WriteImpl( 856 nsCOMPtr<nsIInputStream> aInputStream, const Maybe<uint64_t> aPosition) { 857 return InvokeAsync( 858 mTaskQueue, __func__, 859 [selfHolder = quota::TargetPtrHolder(this), 860 inputStream = std::move(aInputStream), aPosition]() { 861 QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), 862 CreateAndRejectInt64Promise); 863 864 if (aPosition.isSome()) { 865 LOG(("%p: Seeking to %" PRIu64, selfHolder->mStreamOwner.get(), 866 aPosition.value())); 867 868 QM_TRY( 869 MOZ_TO_RESULT(selfHolder->mStreamOwner->Seek(aPosition.value())), 870 CreateAndRejectInt64Promise); 871 } 872 873 nsCOMPtr<nsIOutputStream> streamSink = 874 selfHolder->mStreamOwner->OutputStream(); 875 876 auto written = std::make_shared<int64_t>(0); 877 auto writingProgress = [written](uint32_t aDelta) { 878 *written += static_cast<int64_t>(aDelta); 879 }; 880 881 auto promiseHolder = MakeUnique<MozPromiseHolder<Int64Promise>>(); 882 RefPtr<Int64Promise> promise = promiseHolder->Ensure(__func__); 883 884 auto writingCompletion = 885 [written, 886 promiseHolder = std::move(promiseHolder)](nsresult aStatus) { 887 if (NS_SUCCEEDED(aStatus)) { 888 promiseHolder->ResolveIfExists(*written, __func__); 889 return; 890 } 891 892 promiseHolder->RejectIfExists(aStatus, __func__); 893 }; 894 895 QM_TRY(MOZ_TO_RESULT(fs::AsyncCopy( 896 inputStream, streamSink, selfHolder->mTaskQueue, 897 nsAsyncCopyMode::NS_ASYNCCOPY_VIA_READSEGMENTS, 898 /* aCloseSource */ true, /* aCloseSink */ false, 899 std::move(writingProgress), std::move(writingCompletion))), 900 CreateAndRejectInt64Promise); 901 902 return promise; 903 }); 904 } 905 906 RefPtr<BoolPromise> FileSystemWritableFileStream::Seek(uint64_t aPosition) { 907 MOZ_ASSERT(IsOpen()); 908 909 LOG_VERBOSE(("%p: Seeking to %" PRIu64, mStreamOwner.get(), aPosition)); 910 911 return InvokeAsync( 912 mTaskQueue, __func__, 913 [selfHolder = quota::TargetPtrHolder(this), aPosition]() mutable { 914 QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), 915 CreateAndRejectBoolPromise); 916 917 QM_TRY(MOZ_TO_RESULT(selfHolder->mStreamOwner->Seek(aPosition)), 918 CreateAndRejectBoolPromise); 919 920 return BoolPromise::CreateAndResolve(true, __func__); 921 }); 922 } 923 924 RefPtr<BoolPromise> FileSystemWritableFileStream::Truncate(uint64_t aSize) { 925 MOZ_ASSERT(IsOpen()); 926 927 return InvokeAsync( 928 mTaskQueue, __func__, 929 [selfHolder = quota::TargetPtrHolder(this), aSize]() mutable { 930 QM_TRY(MOZ_TO_RESULT(selfHolder->EnsureStream()), 931 CreateAndRejectBoolPromise); 932 933 QM_TRY(MOZ_TO_RESULT(selfHolder->mStreamOwner->Truncate(aSize)), 934 CreateAndRejectBoolPromise); 935 936 return BoolPromise::CreateAndResolve(true, __func__); 937 }); 938 } 939 940 nsresult FileSystemWritableFileStream::EnsureStream() { 941 if (!mStreamOwner) { 942 QM_TRY_UNWRAP(MovingNotNull<nsCOMPtr<nsIRandomAccessStream>> stream, 943 DeserializeRandomAccessStream(mStreamParams), 944 NS_ERROR_FAILURE); 945 946 mozilla::ipc::RandomAccessStreamParams streamParams( 947 std::move(mStreamParams)); 948 949 mStreamOwner = MakeRefPtr<fs::FileSystemThreadSafeStreamOwner>( 950 this, std::move(stream)); 951 } 952 953 return NS_OK; 954 } 955 956 void FileSystemWritableFileStream::NoteFinishedCommand() { 957 MOZ_ASSERT(mCommandActive); 958 959 mCommandActive = false; 960 961 mFinishPromiseHolder.ResolveIfExists(true, __func__); 962 } 963 964 RefPtr<BoolPromise> FileSystemWritableFileStream::Finish() { 965 if (!mCommandActive) { 966 return BoolPromise::CreateAndResolve(true, __func__); 967 } 968 969 return mFinishPromiseHolder.Ensure(__func__); 970 } 971 972 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0( 973 WritableFileStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) 974 NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableFileStreamUnderlyingSinkAlgorithms, 975 UnderlyingSinkAlgorithmsBase, mStream) 976 977 // Step 3 of 978 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream 979 already_AddRefed<Promise> 980 WritableFileStreamUnderlyingSinkAlgorithms::WriteCallbackImpl( 981 JSContext* aCx, JS::Handle<JS::Value> aChunk, 982 WritableStreamDefaultController& aController, ErrorResult& aRv) { 983 return mStream->Write(aCx, aChunk, aRv); 984 } 985 986 // Step 4 of 987 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream 988 already_AddRefed<Promise> 989 WritableFileStreamUnderlyingSinkAlgorithms::CloseCallbackImpl( 990 JSContext* aCx, ErrorResult& aRv) { 991 RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv); 992 if (aRv.Failed()) { 993 return nullptr; 994 } 995 996 if (!mStream->IsOpen()) { 997 promise->MaybeRejectWithTypeError("WritableFileStream closed"); 998 return promise.forget(); 999 } 1000 1001 mStream->BeginClose()->Then( 1002 GetCurrentSerialEventTarget(), __func__, 1003 [promise](const BoolPromise::ResolveOrRejectValue& aValue) { 1004 // Step 2.3. Return a promise resolved with undefined. 1005 if (aValue.IsResolve()) { 1006 promise->MaybeResolveWithUndefined(); 1007 return; 1008 } 1009 promise->MaybeRejectWithAbortError( 1010 "Internal error closing file stream"); 1011 }); 1012 1013 return promise.forget(); 1014 } 1015 1016 // Step 5 of 1017 // https://fs.spec.whatwg.org/#create-a-new-filesystemwritablefilestream 1018 already_AddRefed<Promise> 1019 WritableFileStreamUnderlyingSinkAlgorithms::AbortCallbackImpl( 1020 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& /* aReason */, 1021 ErrorResult& aRv) { 1022 // https://streams.spec.whatwg.org/#writablestream-set-up 1023 // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps: 1024 // Step 3.3. Return a promise resolved with undefined. 1025 1026 RefPtr<Promise> promise = Promise::Create(mStream->GetParentObject(), aRv); 1027 if (aRv.Failed()) { 1028 return nullptr; 1029 } 1030 1031 if (!mStream->IsOpen()) { 1032 promise->MaybeRejectWithTypeError("WritableFileStream closed"); 1033 return promise.forget(); 1034 } 1035 1036 mStream->BeginAbort()->Then( 1037 GetCurrentSerialEventTarget(), __func__, 1038 [promise](const BoolPromise::ResolveOrRejectValue& aValue) { 1039 // Step 2.3. Return a promise resolved with undefined. 1040 if (aValue.IsResolve()) { 1041 promise->MaybeResolveWithUndefined(); 1042 return; 1043 } 1044 promise->MaybeRejectWithAbortError( 1045 "Internal error closing file stream"); 1046 }); 1047 1048 return promise.forget(); 1049 } 1050 1051 void WritableFileStreamUnderlyingSinkAlgorithms::ReleaseObjects() { 1052 // WritableStream transitions to errored state whenever a rejected promise is 1053 // returned. At the end of the transition, ReleaseObjects is called. 1054 // Because there is no way to release the locks synchronously, 1055 // we assume this has been initiated before the rejected promise is returned. 1056 MOZ_ASSERT(!mStream->IsOpen()); 1057 } 1058 1059 } // namespace mozilla::dom