diff --git a/dom/plugins/BrowserStreamChild.cpp b/dom/plugins/BrowserStreamChild.cpp index 250322b7cdb..312e2e6eec4 100644 --- a/dom/plugins/BrowserStreamChild.cpp +++ b/dom/plugins/BrowserStreamChild.cpp @@ -46,19 +46,22 @@ BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, - const PStreamNotifyChild* notifyData, + StreamNotifyChild* notifyData, const nsCString& headers, const nsCString& mimeType, const bool& seekable, NPError* rv, uint16_t* stype) : mInstance(instance) - , mClosed(false) + , mStreamStatus(kStreamOpen) + , mDestroyPending(NOT_DESTROYED) + , mNotifyPending(false) + , mInstanceDying(false) , mState(CONSTRUCTING) , mURL(url) , mHeaders(headers) - , mDestroyPending(kDestroyNotPending) - , mDeliverDataTracker(this) + , mStreamNotify(notifyData) + , mDeliveryTracker(this) { PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s, %s)", FULLFUNCTION, url.get(), length, lastmodified, (void*) notifyData, @@ -71,19 +74,13 @@ BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, mStream.url = NullableStringGet(mURL); mStream.end = length; mStream.lastmodified = lastmodified; - if (notifyData) - mStream.notifyData = - static_cast(notifyData)->mClosure; mStream.headers = NullableStringGet(mHeaders); + if (notifyData) + mStream.notifyData = notifyData->mClosure; } NPError BrowserStreamChild::StreamConstructed( - const nsCString& url, - const uint32_t& length, - const uint32_t& lastmodified, - PStreamNotifyChild* notifyData, - const nsCString& headers, const nsCString& mimeType, const bool& seekable, uint16_t* stype) @@ -95,16 +92,24 @@ BrowserStreamChild::StreamConstructed( &mInstance->mData, const_cast(NullableStringGet(mimeType)), &mStream, seekable, stype); if (rv != NPERR_NO_ERROR) { - mClosed = true; mState = DELETING; + mStreamNotify = NULL; } else { mState = ALIVE; + + if (mStreamNotify) + mStreamNotify->SetAssociatedStream(this); } return rv; } +BrowserStreamChild::~BrowserStreamChild() +{ + NS_ASSERTION(!mStreamNotify, "Should have nulled it by now!"); +} + bool BrowserStreamChild::RecvWrite(const int32_t& offset, const Buffer& data, @@ -117,7 +122,7 @@ BrowserStreamChild::RecvWrite(const int32_t& offset, if (ALIVE != mState) NS_RUNTIMEABORT("Unexpected state: received data after NPP_DestroyStream?"); - if (mClosed) + if (kStreamOpen != mStreamStatus) return true; mStream.end = newlength; @@ -129,9 +134,7 @@ BrowserStreamChild::RecvWrite(const int32_t& offset, newdata->data = data; newdata->curpos = 0; - if (mDeliverDataTracker.empty()) - MessageLoop::current()->PostTask(FROM_HERE, - mDeliverDataTracker.NewRunnableMethod(&BrowserStreamChild::DeliverData)); + EnsureDeliveryPending(); return true; } @@ -146,7 +149,7 @@ BrowserStreamChild::AnswerNPP_StreamAsFile(const nsCString& fname) if (ALIVE != mState) NS_RUNTIMEABORT("Unexpected state: received file after NPP_DestroyStream?"); - if (mClosed) + if (kStreamOpen != mStreamStatus) return true; mInstance->mPluginIface->asfile(&mInstance->mData, &mStream, @@ -163,12 +166,11 @@ BrowserStreamChild::RecvNPP_DestroyStream(const NPReason& reason) NS_RUNTIMEABORT("Unexpected state: recevied NPP_DestroyStream twice?"); mState = DYING; - mDestroyPending = reason; - - if (mDeliverDataTracker.empty()) - MessageLoop::current()->PostTask(FROM_HERE, - mDeliverDataTracker.NewRunnableMethod(&BrowserStreamChild::DeliverData)); + mDestroyPending = DESTROY_PENDING; + if (NPRES_DONE != reason) + mStreamStatus = reason; + EnsureDeliveryPending(); return true; } @@ -190,7 +192,7 @@ BrowserStreamChild::NPN_RequestRead(NPByteRange* aRangeList) AssertPluginThread(); - if (ALIVE != mState || mClosed) + if (ALIVE != mState || kStreamOpen != mStreamStatus) return NPERR_GENERIC_ERROR; IPCByteRanges ranges; @@ -207,84 +209,95 @@ BrowserStreamChild::NPN_RequestRead(NPByteRange* aRangeList) void BrowserStreamChild::NPN_DestroyStream(NPReason reason) { - mClosed = true; + mStreamStatus = reason; if (ALIVE == mState) - SendNPN_DestroyStream(NPRES_NETWORK_ERR); + SendNPN_DestroyStream(reason); - ClearSuspendedTimer(); - mPendingData.Clear(); - MaybeDeliverNPP_DestroyStream(); + EnsureDeliveryPending(); } void -BrowserStreamChild::DeliverData() +BrowserStreamChild::EnsureDeliveryPending() { - if (mState != ALIVE && mState != DYING) - NS_RUNTIMEABORT("Unexpected state"); + MessageLoop::current()->PostTask(FROM_HERE, + mDeliveryTracker.NewRunnableMethod(&BrowserStreamChild::Deliver)); +} - if (mClosed) { - ClearSuspendedTimer(); - MaybeDeliverNPP_DestroyStream(); - return; - } - - while (mPendingData.Length()) { - while (mPendingData[0].curpos < mPendingData[0].data.Length()) { - int32_t r = mInstance->mPluginIface->writeready(&mInstance->mData, &mStream); - if (r == 0) { - SetSuspendedTimer(); - return; - } - if (!mPendingData.Length()) - break; - 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 (r == 0) { - SetSuspendedTimer(); - return; - } - if (r < 0) { // error condition - NPN_DestroyStream(NPRES_NETWORK_ERR); - return; - } - if (!mPendingData.Length()) - break; - - mPendingData[0].curpos += r; +void +BrowserStreamChild::Deliver() +{ + while (kStreamOpen == mStreamStatus && mPendingData.Length()) { + if (DeliverPendingData() && kStreamOpen == mStreamStatus) { + SetSuspendedTimer(); + return; } - if (!mPendingData.Length()) - break; - mPendingData.RemoveElementAt(0); } - ClearSuspendedTimer(); - MaybeDeliverNPP_DestroyStream(); -} -void -BrowserStreamChild::MaybeDeliverNPP_DestroyStream() -{ - if (kDestroyNotPending != mDestroyPending) { - NS_ASSERTION(mPendingData.Length() == 0, "Pending data?"); + 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) NS_RUNTIMEABORT("mDestroyPending but state not DYING"); - mClosed = true; - NPReason reason = mDestroyPending; - mDestroyPending = kDestroyNotPending; + NS_ASSERTION(NPRES_DONE != mStreamStatus, "Success status set too early!"); + if (kStreamOpen == mStreamStatus) + mStreamStatus = NPRES_DONE; (void) mInstance->mPluginIface - ->destroystream(&mInstance->mData, &mStream, reason); - + ->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 = NULL; + } + if (DYING == mState && DESTROYED == mDestroyPending + && !mStreamNotify && !mInstanceDying) { SendStreamDestroyed(); mState = DELETING; } } +bool +BrowserStreamChild::DeliverPendingData() +{ + if (mState != ALIVE && mState != DYING) + NS_RUNTIMEABORT("Unexpected state"); + + NS_ASSERTION(mPendingData.Length(), "Called from Deliver with empty pending"); + + while (mPendingData[0].curpos < 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 + NPN_DestroyStream(NPRES_NETWORK_ERR); + return false; + } + mPendingData[0].curpos += r; + } + mPendingData.RemoveElementAt(0); + return false; +} void BrowserStreamChild::SetSuspendedTimer() @@ -292,8 +305,8 @@ BrowserStreamChild::SetSuspendedTimer() if (mSuspendedTimer.IsRunning()) return; mSuspendedTimer.Start( - base::TimeDelta::FromMilliseconds(100), - this, &BrowserStreamChild::DeliverData); + base::TimeDelta::FromMilliseconds(100), // 100ms copied from Mozilla plugin host + this, &BrowserStreamChild::Deliver); } void diff --git a/dom/plugins/BrowserStreamChild.h b/dom/plugins/BrowserStreamChild.h index 4cccab2ff25..73a225be556 100644 --- a/dom/plugins/BrowserStreamChild.h +++ b/dom/plugins/BrowserStreamChild.h @@ -45,7 +45,7 @@ namespace mozilla { namespace plugins { class PluginInstanceChild; -class PStreamNotifyChild; +class StreamNotifyChild; class BrowserStreamChild : public PBrowserStreamChild, public AStream { @@ -54,22 +54,17 @@ public: const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, - const PStreamNotifyChild* notifyData, + StreamNotifyChild* notifyData, const nsCString& headers, const nsCString& mimeType, const bool& seekable, NPError* rv, uint16_t* stype); - virtual ~BrowserStreamChild() { } + virtual ~BrowserStreamChild(); NS_OVERRIDE virtual bool IsBrowserStream() { return true; } NPError StreamConstructed( - const nsCString& url, - const uint32_t& length, - const uint32_t& lastmodified, - PStreamNotifyChild* notifyData, - const nsCString& headers, const nsCString& mimeType, const bool& seekable, uint16_t* stype); @@ -95,21 +90,87 @@ public: NPError NPN_RequestRead(NPByteRange* aRangeList); void NPN_DestroyStream(NPReason reason); + void NotifyPending() { + NS_ASSERTION(!mNotifyPending, "Pending twice?"); + mNotifyPending = true; + EnsureDeliveryPending(); + } + + /** + * During instance destruction, artificially cancel all outstanding streams. + * + * @return false if we are already in the DELETING state. + */ + bool InstanceDying() { + if (DELETING == mState) + return false; + + mInstanceDying = true; + return true; + } + + void FinishDelivery() { + NS_ASSERTION(mInstanceDying, "Should only be called after InstanceDying"); + NS_ASSERTION(DELETING != mState, "InstanceDying didn't work?"); + mStreamStatus = NPRES_NETWORK_ERR; + Deliver(); + NS_ASSERTION(!mStreamNotify, "Didn't deliver NPN_URLNotify?"); + } + private: + friend class StreamNotifyChild; using PBrowserStreamChild::SendNPN_DestroyStream; /** - * Deliver the data currently in mPending, scheduling + * Post an event to ensure delivery of pending data/destroy/urlnotify events + * outside of the current RPC stack. + */ + void EnsureDeliveryPending(); + + /** + * Deliver data, destruction, notify scheduling * or cancelling the suspended timer as needed. */ - void DeliverData(); - void MaybeDeliverNPP_DestroyStream(); + void Deliver(); + + /** + * Deliver one chunk of pending data. + * @return true if the plugin indicated a pause was necessary + */ + bool DeliverPendingData(); + void SetSuspendedTimer(); void ClearSuspendedTimer(); PluginInstanceChild* mInstance; NPStream mStream; - bool mClosed; + + static const NPReason kStreamOpen = -1; + + /** + * The plugin's notion of whether a stream has been "closed" (no more + * data delivery) differs from the plugin host due to asynchronous delivery + * of data and NPN_DestroyStream. While the plugin-visible stream is open, + * mStreamStatus should be kStreamOpen (-1). mStreamStatus will be a + * failure code if either the parent or child indicates stream failure. + */ + NPReason mStreamStatus; + + /** + * Delivery of NPP_DestroyStream and NPP_URLNotify must be postponed until + * all data has been delivered. + */ + enum { + NOT_DESTROYED, // NPP_DestroyStream not yet received + DESTROY_PENDING, // NPP_DestroyStream received, not yet delivered + DESTROYED // NPP_DestroyStream delivered, NPP_URLNotify may still be pending + } mDestroyPending; + bool mNotifyPending; + + // When NPP_Destroy is called for our instance (manager), this flag is set + // cancels the stream and avoids sending StreamDestroyed. + bool mInstanceDying; + enum { CONSTRUCTING, ALIVE, @@ -118,16 +179,7 @@ private: } mState; nsCString mURL; nsCString mHeaders; - - static const NPReason kDestroyNotPending = -1; - - /** - * When NPP_DestroyStream is delivered with pending data, we postpone - * delivering or acknowledging it until the pending data has been written. - * - * The default/initial value is kDestroyNotPending - */ - NPReason mDestroyPending; + StreamNotifyChild* mStreamNotify; struct PendingData { @@ -143,7 +195,7 @@ private: * stack frame. It instead posts a runnable using this tracker to cancel * in case we are destroyed. */ - ScopedRunnableMethodFactory mDeliverDataTracker; + ScopedRunnableMethodFactory mDeliveryTracker; base::RepeatingTimer mSuspendedTimer; }; diff --git a/dom/plugins/PPluginInstance.ipdl b/dom/plugins/PPluginInstance.ipdl index 7da28849329..fc215e1e5c6 100644 --- a/dom/plugins/PPluginInstance.ipdl +++ b/dom/plugins/PPluginInstance.ipdl @@ -113,7 +113,7 @@ parent: /** * Covers both NPN_GetURLNotify and NPN_PostURLNotify. * @TODO This would be more readable as an overloaded method, - * but IPDL doesn't allow that for constructors (or any method?). + * but IPDL doesn't allow that for constructors. */ rpc PStreamNotify(nsCString url, nsCString target, bool post, nsCString buffer, bool file) diff --git a/dom/plugins/PStreamNotify.ipdl b/dom/plugins/PStreamNotify.ipdl index 13e445f9507..c164631b6ed 100644 --- a/dom/plugins/PStreamNotify.ipdl +++ b/dom/plugins/PStreamNotify.ipdl @@ -20,7 +20,7 @@ child: /** * Represents NPP_URLNotify */ - rpc __delete__(NPReason reason); + async __delete__(NPReason reason); }; } // namespace plugins diff --git a/dom/plugins/PluginInstanceChild.cpp b/dom/plugins/PluginInstanceChild.cpp index bd0aba27ab0..4c08221fc04 100644 --- a/dom/plugins/PluginInstanceChild.cpp +++ b/dom/plugins/PluginInstanceChild.cpp @@ -75,11 +75,11 @@ using mozilla::gfx::SharedDIB; #endif // defined(OS_WIN) PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface, - const nsCString& aMimeType) : - mPluginIface(aPluginIface) + const nsCString& aMimeType) + : mPluginIface(aPluginIface) + , mQuirks(0) , mCachedWindowActor(nsnull) , mCachedElementActor(nsnull) - , mQuirks(0) #if defined(OS_WIN) , mPluginWindowHWND(0) , mPluginWndProc(0) @@ -1288,9 +1288,7 @@ PluginInstanceChild::AnswerPBrowserStreamConstructor( { AssertPluginThread(); *rv = static_cast(aActor) - ->StreamConstructed(url, length, lastmodified, - notifyData, headers, mimeType, seekable, - stype); + ->StreamConstructed(mimeType, seekable, stype); return true; } @@ -1306,7 +1304,8 @@ PluginInstanceChild::AllocPBrowserStream(const nsCString& url, uint16_t *stype) { AssertPluginThread(); - return new BrowserStreamChild(this, url, length, lastmodified, notifyData, + return new BrowserStreamChild(this, url, length, lastmodified, + static_cast(notifyData), headers, mimeType, seekable, rv, stype); } @@ -1348,29 +1347,58 @@ PluginInstanceChild::AllocPStreamNotify(const nsCString& url, return NULL; } -bool -StreamNotifyChild::Answer__delete__(const NPReason& reason) +void +StreamNotifyChild::ActorDestroy(ActorDestroyReason why) { - AssertPluginThread(); - return static_cast(Manager()) - ->NotifyStream(this, reason); + if (AncestorDeletion == why && mBrowserStream) { + NS_ERROR("Pending NPP_URLNotify not called when closing an instance."); + + // reclaim responsibility for deleting ourself + mBrowserStream = NULL; + mBrowserStream->mStreamNotify = NULL; + } +} + + +void +StreamNotifyChild::SetAssociatedStream(BrowserStreamChild* bs) +{ + NS_ASSERTION(bs, "Shouldn't be null"); + NS_ASSERTION(!mBrowserStream, "Two streams for one streamnotify?"); + + mBrowserStream = bs; } bool -PluginInstanceChild::NotifyStream(StreamNotifyChild* notifyData, - NPReason reason) +StreamNotifyChild::Recv__delete__(const NPReason& reason) { - if (notifyData->mClosure) - mPluginIface->urlnotify(&mData, notifyData->mURL.get(), reason, - notifyData->mClosure); + AssertPluginThread(); + + if (mBrowserStream) + mBrowserStream->NotifyPending(); + else + NPP_URLNotify(reason); + return true; } +void +StreamNotifyChild::NPP_URLNotify(NPReason reason) +{ + PluginInstanceChild* instance = static_cast(Manager()); + + if (mClosure) + instance->mPluginIface->urlnotify(instance->GetNPP(), mURL.get(), + reason, mClosure); +} + bool PluginInstanceChild::DeallocPStreamNotify(PStreamNotifyChild* notifyData) { AssertPluginThread(); - delete notifyData; + + if (!static_cast(notifyData)->mBrowserStream) + delete notifyData; return true; } @@ -1506,6 +1534,19 @@ PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult) PLUGIN_LOG_DEBUG_METHOD; AssertPluginThread(); + nsTArray streams; + ManagedPBrowserStreamChild(streams); + + // First make sure none of these streams become deleted + for (PRUint32 i = 0; i < streams.Length(); ) { + if (static_cast(streams[i])->InstanceDying()) + ++i; + else + streams.RemoveElementAt(i); + } + for (PRUint32 i = 0; i < streams.Length(); ++i) + static_cast(streams[i])->FinishDelivery(); + for (PRUint32 i = 0; i < mPendingAsyncCalls.Length(); ++i) mPendingAsyncCalls[i]->Cancel(); mPendingAsyncCalls.TruncateLength(0); diff --git a/dom/plugins/PluginInstanceChild.h b/dom/plugins/PluginInstanceChild.h index 733edad0e46..6b634242922 100644 --- a/dom/plugins/PluginInstanceChild.h +++ b/dom/plugins/PluginInstanceChild.h @@ -59,11 +59,13 @@ namespace plugins { class PBrowserStreamChild; class BrowserStreamChild; +class StreamNotifyChild; class PluginInstanceChild : public PPluginInstanceChild { friend class BrowserStreamChild; friend class PluginStreamChild; + friend class StreamNotifyChild; #ifdef OS_WIN friend LRESULT CALLBACK PluginWindowProc(HWND hWnd, @@ -183,8 +185,6 @@ public: void InvalidateRect(NPRect* aInvalidRect); - bool NotifyStream(StreamNotifyChild* notifyData, NPReason reason); - uint32_t ScheduleTimer(uint32_t interval, bool repeat, TimerFunc func); void UnscheduleTimer(uint32_t id); diff --git a/dom/plugins/StreamNotifyChild.h b/dom/plugins/StreamNotifyChild.h index 67686b22c97..6f29e3abffe 100644 --- a/dom/plugins/StreamNotifyChild.h +++ b/dom/plugins/StreamNotifyChild.h @@ -44,6 +44,8 @@ namespace mozilla { namespace plugins { +class BrowserStreamChild; + class StreamNotifyChild : public PStreamNotifyChild { friend class PluginInstanceChild; @@ -53,17 +55,37 @@ public: StreamNotifyChild(const nsCString& aURL) : mURL(aURL) , mClosure(NULL) + , mBrowserStream(NULL) { } + NS_OVERRIDE virtual void ActorDestroy(ActorDestroyReason why); + void SetValid(void* aClosure) { mClosure = aClosure; } - bool Answer__delete__(const NPReason& reason); + void NPP_URLNotify(NPReason reason); private: + NS_OVERRIDE virtual bool Recv__delete__(const NPReason& reason); + + /** + * If a stream is created for this this URLNotify, we associate the objects + * so that the NPP_URLNotify call is not fired before the stream data is + * completely delivered. The BrowserStreamChild takes responsibility for + * calling NPP_URLNotify and deleting this object. + */ + void SetAssociatedStream(BrowserStreamChild* bs); + nsCString mURL; void* mClosure; + + /** + * If mBrowserStream is true, it is responsible for deleting this C++ object + * and DeallocPStreamNotify is not, so that the delayed delivery of + * NPP_URLNotify is possible. + */ + BrowserStreamChild* mBrowserStream; }; } // namespace plugins