Bug 551049 part 3: delay the delivery of NPP_URLNotify until the related stream is completely delivered and destroyed, and propagate errors from NPP_Write and NPN_DestroyStream back to NPP_URLNotify r=bent

This commit is contained in:
Benjamin Smedberg 2010-03-06 16:03:05 -05:00
Родитель 372ebbcfb0
Коммит 60af52af60
7 изменённых файлов: 256 добавлений и 128 удалений

Просмотреть файл

@ -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<const StreamNotifyChild*>(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<char*>(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<char*>(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<char*>(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

Просмотреть файл

@ -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<BrowserStreamChild> mDeliverDataTracker;
ScopedRunnableMethodFactory<BrowserStreamChild> mDeliveryTracker;
base::RepeatingTimer<BrowserStreamChild> mSuspendedTimer;
};

Просмотреть файл

@ -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)

Просмотреть файл

@ -20,7 +20,7 @@ child:
/**
* Represents NPP_URLNotify
*/
rpc __delete__(NPReason reason);
async __delete__(NPReason reason);
};
} // namespace plugins

Просмотреть файл

@ -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<BrowserStreamChild*>(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<StreamNotifyChild*>(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<PluginInstanceChild*>(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<PluginInstanceChild*>(Manager());
if (mClosure)
instance->mPluginIface->urlnotify(instance->GetNPP(), mURL.get(),
reason, mClosure);
}
bool
PluginInstanceChild::DeallocPStreamNotify(PStreamNotifyChild* notifyData)
{
AssertPluginThread();
delete notifyData;
if (!static_cast<StreamNotifyChild*>(notifyData)->mBrowserStream)
delete notifyData;
return true;
}
@ -1506,6 +1534,19 @@ PluginInstanceChild::AnswerNPP_Destroy(NPError* aResult)
PLUGIN_LOG_DEBUG_METHOD;
AssertPluginThread();
nsTArray<PBrowserStreamChild*> streams;
ManagedPBrowserStreamChild(streams);
// First make sure none of these streams become deleted
for (PRUint32 i = 0; i < streams.Length(); ) {
if (static_cast<BrowserStreamChild*>(streams[i])->InstanceDying())
++i;
else
streams.RemoveElementAt(i);
}
for (PRUint32 i = 0; i < streams.Length(); ++i)
static_cast<BrowserStreamChild*>(streams[i])->FinishDelivery();
for (PRUint32 i = 0; i < mPendingAsyncCalls.Length(); ++i)
mPendingAsyncCalls[i]->Cancel();
mPendingAsyncCalls.TruncateLength(0);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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