/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ /* 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 "mozilla/plugins/BrowserStreamChild.h" #include "mozilla/Attributes.h" #include "mozilla/plugins/PluginInstanceChild.h" #include "mozilla/plugins/StreamNotifyChild.h" namespace mozilla::plugins { BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, StreamNotifyChild* notifyData, const nsCString& headers) : mInstance(instance), mStreamStatus(kStreamOpen), mDestroyPending(NOT_DESTROYED), mNotifyPending(false), mInstanceDying(false), mState(CONSTRUCTING), mURL(url), mHeaders(headers), mStreamNotify(notifyData), mDeliveryTracker(this) { PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s)", FULLFUNCTION, url.get(), length, lastmodified, (void*)notifyData, headers.get())); AssertPluginThread(); memset(&mStream, 0, sizeof(mStream)); mStream.ndata = static_cast(this); mStream.url = NullableStringGet(mURL); mStream.end = length; mStream.lastmodified = lastmodified; mStream.headers = NullableStringGet(mHeaders); if (notifyData) { mStream.notifyData = notifyData->mClosure; notifyData->SetAssociatedStream(this); } } NPError BrowserStreamChild::StreamConstructed(const nsCString& mimeType, const bool& seekable, uint16_t* stype) { NPError rv = NPERR_NO_ERROR; *stype = NP_NORMAL; rv = mInstance->mPluginIface->newstream( &mInstance->mData, const_cast(NullableStringGet(mimeType)), &mStream, seekable, stype); // NP_NORMAL is the only permissible stream type if (*stype != NP_NORMAL) { rv = NPERR_INVALID_PARAM; // The plugin thinks the stream is alive, so we kill it explicitly (void)mInstance->mPluginIface->destroystream(&mInstance->mData, &mStream, NPRES_NETWORK_ERR); } if (rv != NPERR_NO_ERROR) { mState = DELETING; if (mStreamNotify) { mStreamNotify->SetAssociatedStream(nullptr); mStreamNotify = nullptr; } } else { mState = ALIVE; } return rv; } BrowserStreamChild::~BrowserStreamChild() { NS_ASSERTION(!mStreamNotify, "Should have nulled it by now!"); } mozilla::ipc::IPCResult BrowserStreamChild::RecvWrite(const int32_t& offset, const uint32_t& newlength, const Buffer& data) { PLUGIN_LOG_DEBUG_FUNCTION; AssertPluginThread(); if (ALIVE != mState) MOZ_CRASH("Unexpected state: received data after NPP_DestroyStream?"); if (kStreamOpen != mStreamStatus) return IPC_OK(); mStream.end = newlength; NS_ASSERTION(data.Length() > 0, "Empty data"); PendingData* newdata = mPendingData.AppendElement(); newdata->offset = offset; newdata->data = data; newdata->curpos = 0; EnsureDeliveryPending(); return IPC_OK(); } mozilla::ipc::IPCResult BrowserStreamChild::RecvNPP_DestroyStream( const NPReason& reason) { PLUGIN_LOG_DEBUG_METHOD; if (ALIVE != mState) MOZ_CRASH("Unexpected state: recevied NPP_DestroyStream twice?"); mState = DYING; mDestroyPending = DESTROY_PENDING; if (NPRES_DONE != reason) mStreamStatus = reason; EnsureDeliveryPending(); return IPC_OK(); } mozilla::ipc::IPCResult BrowserStreamChild::Recv__delete__() { AssertPluginThread(); if (DELETING != mState) MOZ_CRASH("Bad state, not DELETING"); return IPC_OK(); } void BrowserStreamChild::EnsureDeliveryPending() { MessageLoop::current()->PostTask( mDeliveryTracker.NewRunnableMethod(&BrowserStreamChild::Deliver)); } void BrowserStreamChild::Deliver() { while (kStreamOpen == mStreamStatus && mPendingData.Length()) { if (DeliverPendingData() && kStreamOpen == mStreamStatus) { SetSuspendedTimer(); return; } } ClearSuspendedTimer(); NS_ASSERTION(kStreamOpen != mStreamStatus || 0 == mPendingData.Length(), "Exit out of the data-delivery loop with pending data"); mPendingData.Clear(); if (DESTROY_PENDING == mDestroyPending) { mDestroyPending = DESTROYED; if (mState != DYING) MOZ_CRASH("mDestroyPending but state not DYING"); NS_ASSERTION(NPRES_DONE != mStreamStatus, "Success status set too early!"); if (kStreamOpen == mStreamStatus) mStreamStatus = NPRES_DONE; (void)mInstance->mPluginIface->destroystream(&mInstance->mData, &mStream, mStreamStatus); } if (DESTROYED == mDestroyPending && mNotifyPending) { NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?"); mNotifyPending = false; mStreamNotify->NPP_URLNotify(mStreamStatus); delete mStreamNotify; mStreamNotify = nullptr; } if (DYING == mState && DESTROYED == mDestroyPending && !mStreamNotify && !mInstanceDying) { SendStreamDestroyed(); mState = DELETING; } } bool BrowserStreamChild::DeliverPendingData() { if (mState != ALIVE && mState != DYING) MOZ_CRASH("Unexpected state"); NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending"); while (mPendingData[0].curpos < static_cast(mPendingData[0].data.Length())) { int32_t r = mInstance->mPluginIface->writeready(&mInstance->mData, &mStream); if (kStreamOpen != mStreamStatus) return false; if (0 == r) // plugin wants to suspend delivery return true; r = mInstance->mPluginIface->write( &mInstance->mData, &mStream, mPendingData[0].offset + mPendingData[0].curpos, // offset mPendingData[0].data.Length() - mPendingData[0].curpos, // length const_cast(mPendingData[0].data.BeginReading() + mPendingData[0].curpos)); if (kStreamOpen != mStreamStatus) return false; if (0 == r) return true; if (r < 0) { // error condition mStreamStatus = NPRES_NETWORK_ERR; // Set up stream destruction EnsureDeliveryPending(); return false; } mPendingData[0].curpos += r; } mPendingData.RemoveElementAt(0); return false; } void BrowserStreamChild::SetSuspendedTimer() { if (mSuspendedTimer.IsRunning()) return; mSuspendedTimer.Start(base::TimeDelta::FromMilliseconds( 100), // 100ms copied from Mozilla plugin host this, &BrowserStreamChild::Deliver); } void BrowserStreamChild::ClearSuspendedTimer() { mSuspendedTimer.Stop(); } } // namespace mozilla::plugins