michael@0: /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/plugins/BrowserStreamChild.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/plugins/PluginInstanceChild.h" michael@0: #include "mozilla/plugins/StreamNotifyChild.h" michael@0: michael@0: namespace mozilla { michael@0: namespace plugins { michael@0: michael@0: BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, michael@0: const nsCString& url, michael@0: const uint32_t& length, michael@0: const uint32_t& lastmodified, michael@0: StreamNotifyChild* notifyData, michael@0: const nsCString& headers, michael@0: const nsCString& mimeType, michael@0: const bool& seekable, michael@0: NPError* rv, michael@0: uint16_t* stype) michael@0: : mInstance(instance) michael@0: , mStreamStatus(kStreamOpen) michael@0: , mDestroyPending(NOT_DESTROYED) michael@0: , mNotifyPending(false) michael@0: , mStreamAsFilePending(false) michael@0: , mInstanceDying(false) michael@0: , mState(CONSTRUCTING) michael@0: , mURL(url) michael@0: , mHeaders(headers) michael@0: , mStreamNotify(notifyData) michael@0: , mDeliveryTracker(MOZ_THIS_IN_INITIALIZER_LIST()) michael@0: { michael@0: PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s, %s)", FULLFUNCTION, michael@0: url.get(), length, lastmodified, (void*) notifyData, michael@0: headers.get(), mimeType.get())); michael@0: michael@0: AssertPluginThread(); michael@0: michael@0: memset(&mStream, 0, sizeof(mStream)); michael@0: mStream.ndata = static_cast(this); michael@0: mStream.url = NullableStringGet(mURL); michael@0: mStream.end = length; michael@0: mStream.lastmodified = lastmodified; michael@0: mStream.headers = NullableStringGet(mHeaders); michael@0: if (notifyData) michael@0: mStream.notifyData = notifyData->mClosure; michael@0: } michael@0: michael@0: NPError michael@0: BrowserStreamChild::StreamConstructed( michael@0: const nsCString& mimeType, michael@0: const bool& seekable, michael@0: uint16_t* stype) michael@0: { michael@0: NPError rv = NPERR_NO_ERROR; michael@0: michael@0: *stype = NP_NORMAL; michael@0: rv = mInstance->mPluginIface->newstream( michael@0: &mInstance->mData, const_cast(NullableStringGet(mimeType)), michael@0: &mStream, seekable, stype); michael@0: if (rv != NPERR_NO_ERROR) { michael@0: mState = DELETING; michael@0: mStreamNotify = nullptr; michael@0: } michael@0: else { michael@0: mState = ALIVE; michael@0: michael@0: if (mStreamNotify) michael@0: mStreamNotify->SetAssociatedStream(this); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: BrowserStreamChild::~BrowserStreamChild() michael@0: { michael@0: NS_ASSERTION(!mStreamNotify, "Should have nulled it by now!"); michael@0: } michael@0: michael@0: bool michael@0: BrowserStreamChild::RecvWrite(const int32_t& offset, michael@0: const Buffer& data, michael@0: const uint32_t& newlength) michael@0: { michael@0: PLUGIN_LOG_DEBUG_FUNCTION; michael@0: michael@0: AssertPluginThread(); michael@0: michael@0: if (ALIVE != mState) michael@0: NS_RUNTIMEABORT("Unexpected state: received data after NPP_DestroyStream?"); michael@0: michael@0: if (kStreamOpen != mStreamStatus) michael@0: return true; michael@0: michael@0: mStream.end = newlength; michael@0: michael@0: NS_ASSERTION(data.Length() > 0, "Empty data"); michael@0: michael@0: PendingData* newdata = mPendingData.AppendElement(); michael@0: newdata->offset = offset; michael@0: newdata->data = data; michael@0: newdata->curpos = 0; michael@0: michael@0: EnsureDeliveryPending(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BrowserStreamChild::RecvNPP_StreamAsFile(const nsCString& fname) michael@0: { michael@0: PLUGIN_LOG_DEBUG(("%s (fname=%s)", FULLFUNCTION, fname.get())); michael@0: michael@0: AssertPluginThread(); michael@0: michael@0: if (ALIVE != mState) michael@0: NS_RUNTIMEABORT("Unexpected state: received file after NPP_DestroyStream?"); michael@0: michael@0: if (kStreamOpen != mStreamStatus) michael@0: return true; michael@0: michael@0: mStreamAsFilePending = true; michael@0: mStreamAsFileName = fname; michael@0: EnsureDeliveryPending(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BrowserStreamChild::RecvNPP_DestroyStream(const NPReason& reason) michael@0: { michael@0: PLUGIN_LOG_DEBUG_METHOD; michael@0: michael@0: if (ALIVE != mState) michael@0: NS_RUNTIMEABORT("Unexpected state: recevied NPP_DestroyStream twice?"); michael@0: michael@0: mState = DYING; michael@0: mDestroyPending = DESTROY_PENDING; michael@0: if (NPRES_DONE != reason) michael@0: mStreamStatus = reason; michael@0: michael@0: EnsureDeliveryPending(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: BrowserStreamChild::Recv__delete__() michael@0: { michael@0: AssertPluginThread(); michael@0: michael@0: if (DELETING != mState) michael@0: NS_RUNTIMEABORT("Bad state, not DELETING"); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NPError michael@0: BrowserStreamChild::NPN_RequestRead(NPByteRange* aRangeList) michael@0: { michael@0: PLUGIN_LOG_DEBUG_FUNCTION; michael@0: michael@0: AssertPluginThread(); michael@0: michael@0: if (ALIVE != mState || kStreamOpen != mStreamStatus) michael@0: return NPERR_GENERIC_ERROR; michael@0: michael@0: IPCByteRanges ranges; michael@0: for (; aRangeList; aRangeList = aRangeList->next) { michael@0: IPCByteRange br = {aRangeList->offset, aRangeList->length}; michael@0: ranges.push_back(br); michael@0: } michael@0: michael@0: NPError result; michael@0: CallNPN_RequestRead(ranges, &result); michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: BrowserStreamChild::NPN_DestroyStream(NPReason reason) michael@0: { michael@0: mStreamStatus = reason; michael@0: if (ALIVE == mState) michael@0: SendNPN_DestroyStream(reason); michael@0: michael@0: EnsureDeliveryPending(); michael@0: } michael@0: michael@0: void michael@0: BrowserStreamChild::EnsureDeliveryPending() michael@0: { michael@0: MessageLoop::current()->PostTask(FROM_HERE, michael@0: mDeliveryTracker.NewRunnableMethod(&BrowserStreamChild::Deliver)); michael@0: } michael@0: michael@0: void michael@0: BrowserStreamChild::Deliver() michael@0: { michael@0: while (kStreamOpen == mStreamStatus && mPendingData.Length()) { michael@0: if (DeliverPendingData() && kStreamOpen == mStreamStatus) { michael@0: SetSuspendedTimer(); michael@0: return; michael@0: } michael@0: } michael@0: ClearSuspendedTimer(); michael@0: michael@0: NS_ASSERTION(kStreamOpen != mStreamStatus || 0 == mPendingData.Length(), michael@0: "Exit out of the data-delivery loop with pending data"); michael@0: mPendingData.Clear(); michael@0: michael@0: // NPP_StreamAsFile() is documented (at MDN) to be called "when the stream michael@0: // is complete" -- i.e. after all calls to NPP_WriteReady() and NPP_Write() michael@0: // have finished. We make these calls asynchronously (from michael@0: // DeliverPendingData()). So we need to make sure all the "pending data" michael@0: // has been "delivered" before calling NPP_StreamAsFile() (also michael@0: // asynchronously). Doing this resolves bug 687610, bug 670036 and possibly michael@0: // also other bugs. michael@0: if (mStreamAsFilePending) { michael@0: if (mStreamStatus == kStreamOpen) michael@0: mInstance->mPluginIface->asfile(&mInstance->mData, &mStream, michael@0: mStreamAsFileName.get()); michael@0: mStreamAsFilePending = false; michael@0: } michael@0: michael@0: if (DESTROY_PENDING == mDestroyPending) { michael@0: mDestroyPending = DESTROYED; michael@0: if (mState != DYING) michael@0: NS_RUNTIMEABORT("mDestroyPending but state not DYING"); michael@0: michael@0: NS_ASSERTION(NPRES_DONE != mStreamStatus, "Success status set too early!"); michael@0: if (kStreamOpen == mStreamStatus) michael@0: mStreamStatus = NPRES_DONE; michael@0: michael@0: (void) mInstance->mPluginIface michael@0: ->destroystream(&mInstance->mData, &mStream, mStreamStatus); michael@0: } michael@0: if (DESTROYED == mDestroyPending && mNotifyPending) { michael@0: NS_ASSERTION(mStreamNotify, "mDestroyPending but no mStreamNotify?"); michael@0: michael@0: mNotifyPending = false; michael@0: mStreamNotify->NPP_URLNotify(mStreamStatus); michael@0: delete mStreamNotify; michael@0: mStreamNotify = nullptr; michael@0: } michael@0: if (DYING == mState && DESTROYED == mDestroyPending michael@0: && !mStreamNotify && !mInstanceDying) { michael@0: SendStreamDestroyed(); michael@0: mState = DELETING; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: BrowserStreamChild::DeliverPendingData() michael@0: { michael@0: if (mState != ALIVE && mState != DYING) michael@0: NS_RUNTIMEABORT("Unexpected state"); michael@0: michael@0: NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending"); michael@0: michael@0: while (mPendingData[0].curpos < static_cast(mPendingData[0].data.Length())) { michael@0: int32_t r = mInstance->mPluginIface->writeready(&mInstance->mData, &mStream); michael@0: if (kStreamOpen != mStreamStatus) michael@0: return false; michael@0: if (0 == r) // plugin wants to suspend delivery michael@0: return true; michael@0: michael@0: r = mInstance->mPluginIface->write( michael@0: &mInstance->mData, &mStream, michael@0: mPendingData[0].offset + mPendingData[0].curpos, // offset michael@0: mPendingData[0].data.Length() - mPendingData[0].curpos, // length michael@0: const_cast(mPendingData[0].data.BeginReading() + mPendingData[0].curpos)); michael@0: if (kStreamOpen != mStreamStatus) michael@0: return false; michael@0: if (0 == r) michael@0: return true; michael@0: if (r < 0) { // error condition michael@0: NPN_DestroyStream(NPRES_NETWORK_ERR); michael@0: return false; michael@0: } michael@0: mPendingData[0].curpos += r; michael@0: } michael@0: mPendingData.RemoveElementAt(0); michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: BrowserStreamChild::SetSuspendedTimer() michael@0: { michael@0: if (mSuspendedTimer.IsRunning()) michael@0: return; michael@0: mSuspendedTimer.Start( michael@0: base::TimeDelta::FromMilliseconds(100), // 100ms copied from Mozilla plugin host michael@0: this, &BrowserStreamChild::Deliver); michael@0: } michael@0: michael@0: void michael@0: BrowserStreamChild::ClearSuspendedTimer() michael@0: { michael@0: mSuspendedTimer.Stop(); michael@0: } michael@0: michael@0: } /* namespace plugins */ michael@0: } /* namespace mozilla */