/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "StreamFilterChild.h" #include "StreamFilter.h" #include "mozilla/Assertions.h" #include "mozilla/UniquePtr.h" namespace mozilla { namespace extensions { using mozilla::dom::StreamFilterStatus; using mozilla::ipc::IPCResult; /***************************************************************************** * Initialization and cleanup *****************************************************************************/ void StreamFilterChild::Cleanup() { switch (mState) { case State::Closing: case State::Closed: case State::Error: case State::Disconnecting: case State::Disconnected: break; default: ErrorResult rv; Disconnect(rv); break; } } /***************************************************************************** * State change methods *****************************************************************************/ void StreamFilterChild::Suspend(ErrorResult& aRv) { switch (mState) { case State::TransferringData: mState = State::Suspending; mNextState = State::Suspended; SendSuspend(); break; case State::Suspending: switch (mNextState) { case State::Suspended: case State::Resuming: mNextState = State::Suspended; break; default: aRv.Throw(NS_ERROR_FAILURE); return; } break; case State::Resuming: switch (mNextState) { case State::TransferringData: case State::Suspending: mNextState = State::Suspending; break; default: aRv.Throw(NS_ERROR_FAILURE); return; } break; case State::Suspended: break; default: aRv.Throw(NS_ERROR_FAILURE); break; } } void StreamFilterChild::Resume(ErrorResult& aRv) { switch (mState) { case State::Suspended: mState = State::Resuming; mNextState = State::TransferringData; SendResume(); break; case State::Suspending: switch (mNextState) { case State::Suspended: case State::Resuming: mNextState = State::Resuming; break; default: aRv.Throw(NS_ERROR_FAILURE); return; } break; case State::Resuming: case State::TransferringData: break; default: aRv.Throw(NS_ERROR_FAILURE); return; } FlushBufferedData(); } void StreamFilterChild::Disconnect(ErrorResult& aRv) { switch (mState) { case State::Suspended: case State::TransferringData: case State::FinishedTransferringData: mState = State::Disconnecting; mNextState = State::Disconnected; WriteBufferedData(); SendDisconnect(); break; case State::Suspending: case State::Resuming: switch (mNextState) { case State::Suspended: case State::Resuming: case State::Disconnecting: mNextState = State::Disconnecting; break; default: aRv.Throw(NS_ERROR_FAILURE); return; } break; case State::Disconnecting: case State::Disconnected: break; default: aRv.Throw(NS_ERROR_FAILURE); return; } } void StreamFilterChild::Close(ErrorResult& aRv) { switch (mState) { case State::Suspended: case State::TransferringData: case State::FinishedTransferringData: mState = State::Closing; mNextState = State::Closed; SendClose(); break; case State::Suspending: case State::Resuming: mNextState = State::Closing; break; case State::Closing: MOZ_DIAGNOSTIC_ASSERT(mNextState == State::Closed); break; case State::Closed: break; default: aRv.Throw(NS_ERROR_FAILURE); return; } mBufferedData.clear(); } /***************************************************************************** * Internal state management *****************************************************************************/ void StreamFilterChild::SetNextState() { mState = mNextState; switch (mNextState) { case State::Suspending: mNextState = State::Suspended; SendSuspend(); break; case State::Resuming: mNextState = State::TransferringData; SendResume(); break; case State::Closing: mNextState = State::Closed; SendClose(); break; case State::Disconnecting: mNextState = State::Disconnected; SendDisconnect(); break; case State::FinishedTransferringData: if (mStreamFilter) { mStreamFilter->FireEvent(u"stop"_ns); // We don't need access to the stream filter after this point, so break // our reference cycle, so that it can be collected if we're the last // reference. mStreamFilter = nullptr; } break; case State::TransferringData: FlushBufferedData(); break; case State::Closed: case State::Disconnected: case State::Error: mStreamFilter = nullptr; break; default: break; } } void StreamFilterChild::MaybeStopRequest() { if (!mReceivedOnStop || !mBufferedData.isEmpty()) { return; } if (mStreamFilter) { Unused << mStreamFilter->CheckAlive(); } switch (mState) { case State::Suspending: case State::Resuming: mNextState = State::FinishedTransferringData; return; case State::Disconnecting: case State::Closing: case State::Closed: break; default: mState = State::FinishedTransferringData; if (mStreamFilter) { mStreamFilter->FireEvent(u"stop"_ns); // We don't need access to the stream filter after this point, so break // our reference cycle, so that it can be collected if we're the last // reference. mStreamFilter = nullptr; } break; } } /***************************************************************************** * State change acknowledgment callbacks *****************************************************************************/ void StreamFilterChild::RecvInitialized(bool aSuccess) { MOZ_ASSERT(mState == State::Uninitialized); if (aSuccess) { mState = State::Initialized; } else { mState = State::Error; if (mStreamFilter) { mStreamFilter->FireErrorEvent(u"Invalid request ID"_ns); mStreamFilter = nullptr; } } } IPCResult StreamFilterChild::RecvError(const nsCString& aError) { mState = State::Error; if (mStreamFilter) { mStreamFilter->FireErrorEvent(NS_ConvertUTF8toUTF16(aError)); mStreamFilter = nullptr; } SendDestroy(); return IPC_OK(); } IPCResult StreamFilterChild::RecvClosed() { MOZ_DIAGNOSTIC_ASSERT(mState == State::Closing); SetNextState(); return IPC_OK(); } IPCResult StreamFilterChild::RecvSuspended() { MOZ_DIAGNOSTIC_ASSERT(mState == State::Suspending); SetNextState(); return IPC_OK(); } IPCResult StreamFilterChild::RecvResumed() { MOZ_DIAGNOSTIC_ASSERT(mState == State::Resuming); SetNextState(); return IPC_OK(); } IPCResult StreamFilterChild::RecvFlushData() { MOZ_DIAGNOSTIC_ASSERT(mState == State::Disconnecting); SendFlushedData(); SetNextState(); return IPC_OK(); } /***************************************************************************** * Other binding methods *****************************************************************************/ void StreamFilterChild::Write(Data&& aData, ErrorResult& aRv) { switch (mState) { case State::Suspending: case State::Resuming: switch (mNextState) { case State::Suspended: case State::TransferringData: break; default: aRv.Throw(NS_ERROR_FAILURE); return; } break; case State::Suspended: case State::TransferringData: case State::FinishedTransferringData: break; default: aRv.Throw(NS_ERROR_FAILURE); return; } SendWrite(std::move(aData)); } StreamFilterStatus StreamFilterChild::Status() const { switch (mState) { case State::Uninitialized: case State::Initialized: return StreamFilterStatus::Uninitialized; case State::TransferringData: return StreamFilterStatus::Transferringdata; case State::Suspended: return StreamFilterStatus::Suspended; case State::FinishedTransferringData: return StreamFilterStatus::Finishedtransferringdata; case State::Resuming: case State::Suspending: switch (mNextState) { case State::TransferringData: case State::Resuming: return StreamFilterStatus::Transferringdata; case State::Suspended: case State::Suspending: return StreamFilterStatus::Suspended; case State::Closing: return StreamFilterStatus::Closed; case State::Disconnecting: return StreamFilterStatus::Disconnected; default: MOZ_ASSERT_UNREACHABLE("Unexpected next state"); return StreamFilterStatus::Suspended; } break; case State::Closing: case State::Closed: return StreamFilterStatus::Closed; case State::Disconnecting: case State::Disconnected: return StreamFilterStatus::Disconnected; case State::Error: return StreamFilterStatus::Failed; }; MOZ_ASSERT_UNREACHABLE("Not reached"); return StreamFilterStatus::Failed; } /***************************************************************************** * Request state notifications *****************************************************************************/ IPCResult StreamFilterChild::RecvStartRequest() { MOZ_ASSERT(mState == State::Initialized); mState = State::TransferringData; if (mStreamFilter) { mStreamFilter->FireEvent(u"start"_ns); Unused << mStreamFilter->CheckAlive(); } return IPC_OK(); } IPCResult StreamFilterChild::RecvStopRequest(const nsresult& aStatus) { mReceivedOnStop = true; MaybeStopRequest(); return IPC_OK(); } /***************************************************************************** * Incoming request data handling *****************************************************************************/ void StreamFilterChild::EmitData(const Data& aData) { MOZ_ASSERT(CanFlushData()); if (mStreamFilter) { mStreamFilter->FireDataEvent(aData); } MaybeStopRequest(); } void StreamFilterChild::FlushBufferedData() { while (!mBufferedData.isEmpty() && CanFlushData()) { UniquePtr data(mBufferedData.popFirst()); EmitData(data->mData); } } void StreamFilterChild::WriteBufferedData() { while (!mBufferedData.isEmpty()) { UniquePtr data(mBufferedData.popFirst()); SendWrite(data->mData); } } IPCResult StreamFilterChild::RecvData(Data&& aData) { MOZ_ASSERT(!mReceivedOnStop); if (mStreamFilter) { Unused << mStreamFilter->CheckAlive(); } switch (mState) { case State::TransferringData: case State::Resuming: EmitData(aData); break; case State::FinishedTransferringData: MOZ_ASSERT_UNREACHABLE("Received data in unexpected state"); EmitData(aData); break; case State::Suspending: case State::Suspended: BufferData(std::move(aData)); break; case State::Disconnecting: SendWrite(std::move(aData)); break; case State::Closing: break; default: MOZ_ASSERT_UNREACHABLE("Received data in unexpected state"); return IPC_FAIL_NO_REASON(this); } return IPC_OK(); } /***************************************************************************** * Glue *****************************************************************************/ void StreamFilterChild::ActorDestroy(ActorDestroyReason aWhy) { mStreamFilter = nullptr; } } // namespace extensions } // namespace mozilla