Create a mechanism to allow GC participants to be marked as externally reachable due to network loads, make XMLHttpRequest a GC participant and use nsMarkedJSFunctionHolder to manage its event listeners just like DOM event listeners to avoid leaks due to cycles. b=206520 r=mrbkap, darin, bzbarsky sr=jst

This commit is contained in:
dbaron%dbaron.org 2006-05-26 01:00:21 +00:00
parent be7035febd
commit 5c13c92f24
6 changed files with 399 additions and 103 deletions

View File

@ -79,6 +79,8 @@
#include "nsICachingChannel.h"
#include "nsContentUtils.h"
#include "nsEventDispatcher.h"
#include "nsCOMArray.h"
#include "nsDOMClassInfo.h"
static const char* kLoadAsData = "loadAsData";
@ -100,7 +102,7 @@ static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CI
#define XML_HTTP_REQUEST_COMPLETED (1 << 4) // 4
#define XML_HTTP_REQUEST_SENT (1 << 5) // Internal, LOADING in IE and external view
#define XML_HTTP_REQUEST_STOPPED (1 << 6) // Internal, INTERACTIVE in IE and external view
// The above states are mutually exclusing, change with ChangeState() only.
// The above states are mutually exclusive, change with ChangeState() only.
// The states below can be combined.
#define XML_HTTP_REQUEST_ABORTED (1 << 7) // Internal
#define XML_HTTP_REQUEST_ASYNC (1 << 8) // Internal
@ -108,6 +110,7 @@ static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CI
#define XML_HTTP_REQUEST_XSITEENABLED (1 << 10) // Internal
#define XML_HTTP_REQUEST_SYNCLOOPING (1 << 11) // Internal
#define XML_HTTP_REQUEST_MULTIPART (1 << 12) // Internal
#define XML_HTTP_REQUEST_ROOTED (1 << 13) // Internal
#define XML_HTTP_REQUEST_LOADSTATES \
(XML_HTTP_REQUEST_UNINITIALIZED | \
@ -286,6 +289,9 @@ nsXMLHttpRequest::~nsXMLHttpRequest()
NS_ABORT_IF_FALSE(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), "we rather crash than hang");
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
// Needed to free the listener arrays.
ClearEventListeners();
}
@ -301,6 +307,7 @@ NS_INTERFACE_MAP_BEGIN(nsXMLHttpRequest)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIDOMGCParticipant)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(XMLHttpRequest)
NS_INTERFACE_MAP_END
@ -319,11 +326,11 @@ nsXMLHttpRequest::AddEventListener(const nsAString& type,
{
NS_ENSURE_ARG(listener);
nsTArray<ListenerHolder*> *array;
#define IMPL_ADD_LISTENER(_type, _member) \
if (type.EqualsLiteral(_type)) { \
if (!(_member).AppendObject(listener)) { \
return NS_ERROR_OUT_OF_MEMORY; \
} \
array = &(_member); \
} else
IMPL_ADD_LISTENER(LOAD_STR, mLoadEventListeners)
@ -334,6 +341,12 @@ nsXMLHttpRequest::AddEventListener(const nsAString& type,
{
return NS_ERROR_INVALID_ARG;
}
ListenerHolder *holder = new ListenerHolder;
NS_ENSURE_TRUE(holder, NS_ERROR_OUT_OF_MEMORY);
holder->Set(listener, this);
array->AppendElement(holder);
mScriptContext = GetCurrentContext();
#undef IMPL_ADD_LISTENER
@ -350,9 +363,10 @@ nsXMLHttpRequest::RemoveEventListener(const nsAString & type,
{
NS_ENSURE_ARG(listener);
nsTArray<ListenerHolder*> *array;
#define IMPL_REMOVE_LISTENER(_type, _member) \
if (type.EqualsLiteral(_type)) { \
(_member).RemoveObject(listener); \
array = &(_member); \
} else
IMPL_REMOVE_LISTENER(LOAD_STR, mLoadEventListeners)
@ -364,6 +378,14 @@ nsXMLHttpRequest::RemoveEventListener(const nsAString & type,
return NS_ERROR_INVALID_ARG;
}
// Allow a caller to remove O(N^2) behavior by removing end-to-start.
for (PRUint32 i = array->Length() - 1; i != PRUint32(-1); --i) {
if (nsCOMPtr<nsIDOMEventListener>(array->ElementAt(i)->Get()) == listener) {
array->RemoveElementAt(i);
break;
}
}
return NS_OK;
}
@ -382,8 +404,7 @@ nsXMLHttpRequest::GetOnreadystatechange(nsIDOMEventListener * *aOnreadystatechan
{
NS_ENSURE_ARG_POINTER(aOnreadystatechange);
*aOnreadystatechange = mOnReadystatechangeListener;
NS_IF_ADDREF(*aOnreadystatechange);
mOnReadystatechangeListener.Get(aOnreadystatechange);
return NS_OK;
}
@ -391,7 +412,7 @@ nsXMLHttpRequest::GetOnreadystatechange(nsIDOMEventListener * *aOnreadystatechan
NS_IMETHODIMP
nsXMLHttpRequest::SetOnreadystatechange(nsIDOMEventListener * aOnreadystatechange)
{
mOnReadystatechangeListener = aOnreadystatechange;
mOnReadystatechangeListener.Set(aOnreadystatechange, this);
mScriptContext = GetCurrentContext();
@ -405,8 +426,7 @@ nsXMLHttpRequest::GetOnload(nsIDOMEventListener * *aOnLoad)
{
NS_ENSURE_ARG_POINTER(aOnLoad);
*aOnLoad = mOnLoadListener;
NS_IF_ADDREF(*aOnLoad);
mOnLoadListener.Get(aOnLoad);
return NS_OK;
}
@ -414,7 +434,7 @@ nsXMLHttpRequest::GetOnload(nsIDOMEventListener * *aOnLoad)
NS_IMETHODIMP
nsXMLHttpRequest::SetOnload(nsIDOMEventListener * aOnLoad)
{
mOnLoadListener = aOnLoad;
mOnLoadListener.Set(aOnLoad, this);
mScriptContext = GetCurrentContext();
@ -427,8 +447,7 @@ nsXMLHttpRequest::GetOnerror(nsIDOMEventListener * *aOnerror)
{
NS_ENSURE_ARG_POINTER(aOnerror);
*aOnerror = mOnErrorListener;
NS_IF_ADDREF(*aOnerror);
mOnErrorListener.Get(aOnerror);
return NS_OK;
}
@ -436,7 +455,7 @@ nsXMLHttpRequest::GetOnerror(nsIDOMEventListener * *aOnerror)
NS_IMETHODIMP
nsXMLHttpRequest::SetOnerror(nsIDOMEventListener * aOnerror)
{
mOnErrorListener = aOnerror;
mOnErrorListener.Set(aOnerror, this);
mScriptContext = GetCurrentContext();
@ -449,8 +468,7 @@ nsXMLHttpRequest::GetOnprogress(nsIDOMEventListener * *aOnprogress)
{
NS_ENSURE_ARG_POINTER(aOnprogress);
*aOnprogress = mOnProgressListener;
NS_IF_ADDREF(*aOnprogress);
mOnProgressListener.Get(aOnprogress);
return NS_OK;
}
@ -458,7 +476,7 @@ nsXMLHttpRequest::GetOnprogress(nsIDOMEventListener * *aOnprogress)
NS_IMETHODIMP
nsXMLHttpRequest::SetOnprogress(nsIDOMEventListener * aOnprogress)
{
mOnProgressListener = aOnprogress;
mOnProgressListener.Set(aOnprogress, this);
mScriptContext = GetCurrentContext();
@ -471,8 +489,7 @@ nsXMLHttpRequest::GetOnuploadprogress(nsIDOMEventListener * *aOnuploadprogress)
{
NS_ENSURE_ARG_POINTER(aOnuploadprogress);
*aOnuploadprogress = mOnUploadProgressListener;
NS_IF_ADDREF(*aOnuploadprogress);
mOnUploadProgressListener.Get(aOnuploadprogress);
return NS_OK;
}
@ -480,7 +497,7 @@ nsXMLHttpRequest::GetOnuploadprogress(nsIDOMEventListener * *aOnuploadprogress)
NS_IMETHODIMP
nsXMLHttpRequest::SetOnuploadprogress(nsIDOMEventListener * aOnuploadprogress)
{
mOnUploadProgressListener = aOnuploadprogress;
mOnUploadProgressListener.Set(aOnuploadprogress, this);
mScriptContext = GetCurrentContext();
@ -832,8 +849,22 @@ nsXMLHttpRequest::CreateEvent(nsEvent* aEvent, const nsAString& aType,
}
void
nsXMLHttpRequest::NotifyEventListeners(nsIDOMEventListener* aHandler,
nsCOMArray<nsIDOMEventListener>& aListeners,
nsXMLHttpRequest::CopyEventListeners(ListenerHolder& aListener,
const nsTArray<ListenerHolder*>& aListenerArray,
nsCOMArray<nsIDOMEventListener>& aCopy)
{
NS_PRECONDITION(aCopy.Count() == 0, "aCopy should start off empty");
nsCOMPtr<nsIDOMEventListener> listener = aListener.Get();
if (listener)
aCopy.AppendObject(listener);
PRUint32 count = aListenerArray.Length();
for (PRUint32 i = 0; i < count; ++i)
aCopy.AppendObject(nsCOMPtr<nsIDOMEventListener>(aListenerArray[i]->Get()));
}
void
nsXMLHttpRequest::NotifyEventListeners(const nsCOMArray<nsIDOMEventListener>& aListeners,
nsIDOMEvent* aEvent)
{
// XXXbz wouldn't it be easier to just have an actual nsEventListenerManager
@ -856,10 +887,6 @@ nsXMLHttpRequest::NotifyEventListeners(nsIDOMEventListener* aHandler,
}
}
if (aHandler) {
aHandler->HandleEvent(aEvent);
}
PRInt32 count = aListeners.Count();
for (PRInt32 index = 0; index < count; ++index) {
nsIDOMEventListener* listener = aListeners[index];
@ -877,16 +904,34 @@ nsXMLHttpRequest::NotifyEventListeners(nsIDOMEventListener* aHandler,
void
nsXMLHttpRequest::ClearEventListeners()
{
mLoadEventListeners.Clear();
mErrorEventListeners.Clear();
mProgressEventListeners.Clear();
mUploadProgressEventListeners.Clear();
mReadystatechangeEventListeners.Clear();
mOnLoadListener = nsnull;
mOnErrorListener = nsnull;
mOnProgressListener = nsnull;
mOnUploadProgressListener = nsnull;
mOnReadystatechangeListener = nsnull;
if (mState & XML_HTTP_REQUEST_ROOTED) {
nsDOMClassInfo::UnsetExternallyReferenced(this);
mState &= ~XML_HTTP_REQUEST_ROOTED;
}
// This isn't *really* needed anymore now that we use
// nsMarkedJSFunctionHolder, but we may as well keep it for safety
// (against leaks) and compatibility, and also for the code to clear
// the first listener arrays (called from the destructor).
PRUint32 i, i_end;
#define CLEAR_ARRAY(member_) \
for (i = 0, i_end = (member_).Length(); i < i_end; ++i) \
delete (member_)[i]; \
(member_).Clear();
CLEAR_ARRAY(mLoadEventListeners)
CLEAR_ARRAY(mErrorEventListeners)
CLEAR_ARRAY(mProgressEventListeners)
CLEAR_ARRAY(mUploadProgressEventListeners)
CLEAR_ARRAY(mReadystatechangeEventListeners)
#undef CLEAR_ARRAY
mOnLoadListener.Set(nsnull, this);
mOnErrorListener.Set(nsnull, this);
mOnProgressListener.Set(nsnull, this);
mOnUploadProgressListener.Set(nsnull, this);
mOnReadystatechangeListener.Set(nsnull, this);
}
already_AddRefed<nsIHttpChannel>
@ -981,9 +1026,10 @@ nsXMLHttpRequest::OpenRequest(const nsACString& method,
// a progress event handler we must load with nsIRequest::LOAD_NORMAL or
// necko won't generate any progress notifications
nsLoadFlags loadFlags;
if (mOnProgressListener || mOnUploadProgressListener ||
mProgressEventListeners.Count() != 0 ||
mUploadProgressEventListeners.Count() != 0) {
if (nsCOMPtr<nsIDOMEventListener>(mOnProgressListener.Get()) ||
nsCOMPtr<nsIDOMEventListener>(mOnUploadProgressListener.Get()) ||
mProgressEventListeners.Length() != 0 ||
mUploadProgressEventListeners.Length() != 0) {
loadFlags = nsIRequest::LOAD_NORMAL;
} else {
loadFlags = nsIRequest::LOAD_BACKGROUND;
@ -1435,10 +1481,15 @@ nsXMLHttpRequest::RequestCompleted()
return NS_OK;
}
// Grab hold of the event listeners we will need before we possibly clear
// them.
nsCOMArray<nsIDOMEventListener> loadEventListeners;
CopyEventListeners(mOnLoadListener, mLoadEventListeners, loadEventListeners);
// We need to create the event before nulling out mDocument
nsEvent evt(PR_TRUE, NS_PAGE_LOAD);
nsCOMPtr<nsIDOMEvent> domevent;
if (mOnLoadListener || mLoadEventListeners.Count()) {
if (loadEventListeners.Count()) {
rv = CreateEvent(&evt, EmptyString(), getter_AddRefs(domevent));
}
@ -1454,17 +1505,12 @@ nsXMLHttpRequest::RequestCompleted()
}
}
// Grab hold of the event listeners we will need before we possibly clear
// them.
nsCOMPtr<nsIDOMEventListener> onLoadListener = mOnLoadListener;
nsCOMArray<nsIDOMEventListener> loadEventListeners(mLoadEventListeners);
// Clear listeners here unless we're multipart
ChangeState(XML_HTTP_REQUEST_COMPLETED, PR_TRUE,
!(mState & XML_HTTP_REQUEST_MULTIPART));
if (NS_SUCCEEDED(rv) && domevent) {
NotifyEventListeners(onLoadListener, loadEventListeners, domevent);
NotifyEventListeners(loadEventListeners, domevent);
}
if (mState & XML_HTTP_REQUEST_MULTIPART) {
@ -1667,6 +1713,14 @@ nsXMLHttpRequest::Send(nsIVariant *aBody)
break;
}
}
} else {
// If we're asynchronous, we need to prevent our event listeners
// from being garbage collected even if this object becomes
// unreachable from script, since they can fire as a result of our
// reachability from the network stack.
rv = nsDOMClassInfo::SetExternallyReferenced(this);
if (NS_SUCCEEDED(rv))
mState |= XML_HTTP_REQUEST_ROOTED;
}
if (!mChannel) {
@ -1837,12 +1891,15 @@ nsXMLHttpRequest::Abort(nsIDOMEvent* aEvent)
nsresult
nsXMLHttpRequest::Error(nsIDOMEvent* aEvent)
{
nsCOMArray<nsIDOMEventListener> errorEventListeners;
CopyEventListeners(mOnErrorListener, mErrorEventListeners,
errorEventListeners);
// We need to create the event before nulling out mDocument
nsCOMPtr<nsIDOMEvent> event = aEvent;
// There is no NS_PAGE_ERROR event but NS_SCRIPT_ERROR should be ok.
nsEvent evt(PR_TRUE, NS_SCRIPT_ERROR);
if (!event &&
(mOnErrorListener || mErrorEventListeners.Count())) {
if (!event && errorEventListeners.Count()) {
CreateEvent(&evt, EmptyString(), getter_AddRefs(event));
}
@ -1851,13 +1908,10 @@ nsXMLHttpRequest::Error(nsIDOMEvent* aEvent)
mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
nsCOMPtr<nsIDOMEventListener> onErrorListener = mOnErrorListener;
nsCOMArray<nsIDOMEventListener> errorEventListeners(mErrorEventListeners);
ClearEventListeners();
if (event) {
NotifyEventListeners(onErrorListener, errorEventListeners, event);
NotifyEventListeners(errorEventListeners, event);
}
return NS_OK;
@ -1867,7 +1921,7 @@ nsresult
nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast,
PRBool aClearEventListeners)
{
// If we are setting one of the mutually exclusing states,
// If we are setting one of the mutually exclusive states,
// unset those state bits first.
if (aState & XML_HTTP_REQUEST_LOADSTATES) {
mState &= ~XML_HTTP_REQUEST_LOADSTATES;
@ -1876,10 +1930,10 @@ nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast,
nsresult rv = NS_OK;
// Grab private copies of the listeners we need
nsCOMPtr<nsIDOMEventListener> onReadyStateChangeListener =
mOnReadystatechangeListener;
nsCOMArray<nsIDOMEventListener> readystatechangeEventListeners(
mReadystatechangeEventListeners);
nsCOMArray<nsIDOMEventListener> readystatechangeEventListeners;
CopyEventListeners(mOnReadystatechangeListener,
mReadystatechangeEventListeners,
readystatechangeEventListeners);
if (aClearEventListeners) {
ClearEventListeners();
@ -1888,15 +1942,13 @@ nsXMLHttpRequest::ChangeState(PRUint32 aState, PRBool aBroadcast,
if ((mState & XML_HTTP_REQUEST_ASYNC) &&
(aState & XML_HTTP_REQUEST_LOADSTATES) && // Broadcast load states only
aBroadcast &&
(onReadyStateChangeListener || readystatechangeEventListeners.Count())) {
readystatechangeEventListeners.Count()) {
nsCOMPtr<nsIDOMEvent> event;
rv = CreateEvent(nsnull, NS_LITERAL_STRING(READYSTATE_STR),
getter_AddRefs(event));
NS_ENSURE_SUCCESS(rv, rv);
NotifyEventListeners(onReadyStateChangeListener,
readystatechangeEventListeners,
event);
NotifyEventListeners(readystatechangeEventListeners, event);
}
return rv;
@ -1967,12 +2019,16 @@ nsXMLHttpRequest::OnProgress(nsIRequest *aRequest, nsISupports *aContext, PRUint
// XML_HTTP_REQUEST_SENT
PRBool downloading =
!((XML_HTTP_REQUEST_OPENED | XML_HTTP_REQUEST_SENT) & mState);
nsIDOMEventListener* progressListener =
downloading ? mOnProgressListener : mOnUploadProgressListener;
nsCOMArray<nsIDOMEventListener> & progressListenerArray =
downloading ? mProgressEventListeners : mUploadProgressEventListeners;
nsCOMArray<nsIDOMEventListener> progressListeners;
if (downloading) {
CopyEventListeners(mOnProgressListener,
mProgressEventListeners, progressListeners);
} else {
CopyEventListeners(mOnUploadProgressListener,
mUploadProgressEventListeners, progressListeners);
}
if (progressListener || progressListenerArray.Count()) {
if (progressListeners.Count()) {
nsCOMPtr<nsIDOMEvent> event;
nsresult rv = CreateEvent(nsnull, NS_LITERAL_STRING(PROGRESS_STR),
getter_AddRefs(event));
@ -1984,9 +2040,7 @@ nsXMLHttpRequest::OnProgress(nsIRequest *aRequest, nsISupports *aContext, PRUint
return NS_ERROR_OUT_OF_MEMORY;
event = progressEvent;
nsCOMPtr<nsIDOMEventListener> onProgressListener = progressListener;
nsCOMArray<nsIDOMEventListener> progressListeners(progressListenerArray);
NotifyEventListeners(onProgressListener, progressListeners, event);
NotifyEventListeners(progressListeners, event);
}
if (mProgressEventSink) {
@ -2062,6 +2116,24 @@ nsXMLHttpRequest::GetInterface(const nsIID & aIID, void **aResult)
return QueryInterface(aIID, aResult);
}
/////////////////////////////////////////////////////
// nsIDOMGCParticipant methods:
//
/* virtual */ nsIDOMGCParticipant*
nsXMLHttpRequest::GetSCCIndex()
{
return this;
}
/* virtual */ void
nsXMLHttpRequest::AppendReachableList(nsCOMArray<nsIDOMGCParticipant>& aArray)
{
nsCOMPtr<nsIDOMGCParticipant> gcp = do_QueryInterface(mDocument);
if (gcp)
aArray.AppendObject(gcp);
}
NS_IMPL_ISUPPORTS1(nsXMLHttpRequest::nsHeaderVisitor, nsIHttpHeaderVisitor)
NS_IMETHODIMP nsXMLHttpRequest::

View File

@ -57,6 +57,8 @@
#include "nsIHttpHeaderVisitor.h"
#include "nsIProgressEventSink.h"
#include "nsCOMArray.h"
#include "nsJSUtils.h"
#include "nsTArray.h"
#include "nsIDOMLSProgressEvent.h"
@ -68,8 +70,9 @@ class nsXMLHttpRequest : public nsIXMLHttpRequest,
public nsIDOMEventTarget,
public nsIStreamListener,
public nsIChannelEventSink,
public nsIInterfaceRequestor,
public nsIProgressEventSink,
public nsIInterfaceRequestor,
public nsIDOMGCParticipant,
public nsSupportsWeakReference
{
public:
@ -111,7 +114,14 @@ public:
// nsIInterfaceRequestor
NS_DECL_NSIINTERFACEREQUESTOR
// nsIDOMGCParticipant
virtual nsIDOMGCParticipant* GetSCCIndex();
virtual void AppendReachableList(nsCOMArray<nsIDOMGCParticipant>& aArray);
protected:
typedef nsMarkedJSFunctionHolder<nsIDOMEventListener> ListenerHolder;
nsresult GetStreamForWString(const PRUnichar* aStr,
PRInt32 aLength,
nsIInputStream** aStream);
@ -142,10 +152,15 @@ protected:
nsresult CreateEvent(nsEvent* event, const nsAString& aType,
nsIDOMEvent** domevent);
// Make a copy of a pair of members to be passed to NotifyEventListeners.
void CopyEventListeners(ListenerHolder& aListener,
const nsTArray<ListenerHolder*>& aListenerArray,
nsCOMArray<nsIDOMEventListener>& aCopy);
// aListeners must be a "non-live" list (i.e., addEventListener and
// removeEventListener should not affect it).
void NotifyEventListeners(nsIDOMEventListener* aHandler,
nsCOMArray<nsIDOMEventListener>& aListeners,
// removeEventListener should not affect it). It should be built from
// member variables by calling CopyEventListeners.
void NotifyEventListeners(const nsCOMArray<nsIDOMEventListener>& aListeners,
nsIDOMEvent* aEvent);
void ClearEventListeners();
already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
@ -155,19 +170,19 @@ protected:
nsCOMPtr<nsIRequest> mReadRequest;
nsCOMPtr<nsIDOMDocument> mDocument;
nsCOMArray<nsIDOMEventListener> mLoadEventListeners;
nsCOMArray<nsIDOMEventListener> mErrorEventListeners;
nsCOMArray<nsIDOMEventListener> mProgressEventListeners;
nsCOMArray<nsIDOMEventListener> mUploadProgressEventListeners;
nsCOMArray<nsIDOMEventListener> mReadystatechangeEventListeners;
nsTArray<ListenerHolder*> mLoadEventListeners;
nsTArray<ListenerHolder*> mErrorEventListeners;
nsTArray<ListenerHolder*> mProgressEventListeners;
nsTArray<ListenerHolder*> mUploadProgressEventListeners;
nsTArray<ListenerHolder*> mReadystatechangeEventListeners;
nsCOMPtr<nsIScriptContext> mScriptContext;
nsCOMPtr<nsIDOMEventListener> mOnLoadListener;
nsCOMPtr<nsIDOMEventListener> mOnErrorListener;
nsCOMPtr<nsIDOMEventListener> mOnProgressListener;
nsCOMPtr<nsIDOMEventListener> mOnUploadProgressListener;
nsCOMPtr<nsIDOMEventListener> mOnReadystatechangeListener;
nsMarkedJSFunctionHolder<nsIDOMEventListener> mOnLoadListener;
nsMarkedJSFunctionHolder<nsIDOMEventListener> mOnErrorListener;
nsMarkedJSFunctionHolder<nsIDOMEventListener> mOnProgressListener;
nsMarkedJSFunctionHolder<nsIDOMEventListener> mOnUploadProgressListener;
nsMarkedJSFunctionHolder<nsIDOMEventListener> mOnReadystatechangeListener;
nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;

View File

@ -1130,8 +1130,8 @@ static nsDOMClassInfoData sClassInfoData[] = {
NS_DEFINE_CLASSINFO_DATA(XMLHttpProgressEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(XMLHttpRequest, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(XMLHttpRequest, nsDOMGCParticipantSH,
GCPARTICIPANT_SCRIPTABLE_FLAGS)
// Define MOZ_SVG_FOREIGNOBJECT here so that when it gets switched on,
// we preserve binary compatibility. New classes should be added
@ -3588,6 +3588,23 @@ nsDOMClassInfo::InnerObject(nsIXPConnectWrappedNative *wrapper, JSContext * cx,
return NS_ERROR_UNEXPECTED;
}
// These are declared here so we can assert that they're empty in ShutDown.
/**
* Every XPConnect wrapper that needs to be preserved (a wrapped native
* with JS properties set on it or used by XBL or a wrapped JS event
* handler function) as long as the element it wraps is reachable from
* script (via JS or via DOM APIs accessible from JS) gets an entry in
* this table.
*
* See PreservedWrapperEntry.
*/
static PLDHashTable sPreservedWrapperTable;
// See ExternallyReferencedEntry.
static PLDHashTable sExternallyReferencedTable;
// See RootWhenExternallyReferencedEntry
static PLDHashTable sRootWhenExternallyReferencedTable;
// static
nsIClassInfo *
@ -3648,6 +3665,20 @@ nsDOMClassInfo::GetClassInfoInstance(nsDOMClassInfoData* aData)
void
nsDOMClassInfo::ShutDown()
{
NS_ASSERTION(sPreservedWrapperTable.ops == 0,
"preserved wrapper table not empty at shutdown");
NS_ASSERTION(sExternallyReferencedTable.ops == 0 ||
sExternallyReferencedTable.entryCount == 0,
"rooted participant table not empty at shutdown");
NS_ASSERTION(sRootWhenExternallyReferencedTable.ops == 0,
"root when externally referenced table not empty at shutdown");
if (sExternallyReferencedTable.ops &&
sExternallyReferencedTable.entryCount == 0) {
PL_DHashTableFinish(&sExternallyReferencedTable);
sExternallyReferencedTable.ops = nsnull;
}
if (sClassInfoData[0].u.mConstructorFptr) {
PRUint32 i;
@ -4919,24 +4950,60 @@ nsDOMConstructor::ToString(nsAString &aResult)
#define DEBUG_PRESERVE_WRAPPERS
#endif
/**
* Every XPConnect wrapper that needs to be preserved (a wrapped native
* with JS properties set on it or used by XBL or a wrapped JS event
* handler function) as long as the element it wraps is reachable from
* script (via JS or via DOM APIs accessible from JS) gets an entry in
* this table.
*/
static PLDHashTable sPreservedWrapperTable;
struct RootWhenExternallyReferencedEntry : public PLDHashEntryHdr {
// must be first to line up with PLDHashEntryStub
nsIDOMGCParticipant *participant;
PRUint32 refcnt;
};
struct PreservedWrapperEntry : public PLDHashEntryHdr {
void *key; // must be first to line up with PLDHashEntryStub
nsIXPConnectJSObjectHolder* (*keyToWrapperFunc)(void* aKey);
nsIDOMGCParticipant *participant;
PRBool rootWhenExternallyReferenced;
// See |WrapperSCCEntry::first|. Valid only during mark phase of GC.
PreservedWrapperEntry *next;
};
PR_STATIC_CALLBACK(void)
PreservedWrapperClearEntry(PLDHashTable *table, PLDHashEntryHdr *hdr)
{
PreservedWrapperEntry *entry = NS_STATIC_CAST(PreservedWrapperEntry*, hdr);
if (entry->rootWhenExternallyReferenced) {
NS_ASSERTION(sRootWhenExternallyReferencedTable.ops,
"must have been added to rwer table");
RootWhenExternallyReferencedEntry *rwerEntry =
NS_STATIC_CAST(RootWhenExternallyReferencedEntry*,
PL_DHashTableOperate(&sRootWhenExternallyReferencedTable,
entry->participant, PL_DHASH_LOOKUP));
NS_ASSERTION(PL_DHASH_ENTRY_IS_BUSY(rwerEntry),
"boolean vs. table mismatch");
if (PL_DHASH_ENTRY_IS_BUSY(rwerEntry) && --rwerEntry->refcnt == 0) {
PL_DHashTableRawRemove(&sRootWhenExternallyReferencedTable, rwerEntry);
if (sRootWhenExternallyReferencedTable.entryCount == 0) {
PL_DHashTableFinish(&sRootWhenExternallyReferencedTable);
sRootWhenExternallyReferencedTable.ops = nsnull;
}
}
}
memset(hdr, 0, table->entrySize);
}
static const PLDHashTableOps sPreservedWrapperTableOps = {
PL_DHashAllocTable,
PL_DHashFreeTable,
PL_DHashGetKeyStub,
PL_DHashVoidPtrKeyStub,
PL_DHashMatchEntryStub,
PL_DHashMoveEntryStub,
PreservedWrapperClearEntry,
PL_DHashFinalizeStub,
nsnull
};
/**
* At the beginning of the mark phase of the GC, we sort all the
* wrappers into their strongly connected components. We maintain this
@ -5005,7 +5072,8 @@ static const PLDHashTableOps sWrapperSCCTableOps = {
nsresult
nsDOMClassInfo::PreserveWrapper(void *aKey,
nsIXPConnectJSObjectHolder* (*aKeyToWrapperFunc)(void* aKey),
nsIDOMGCParticipant *aParticipant)
nsIDOMGCParticipant *aParticipant,
PRBool aRootWhenExternallyReferenced)
{
NS_PRECONDITION(aKey, "unexpected null pointer");
NS_PRECONDITION(aKeyToWrapperFunc, "unexpected null pointer");
@ -5014,8 +5082,8 @@ nsDOMClassInfo::PreserveWrapper(void *aKey,
"cannot change preserved wrapper table during mark phase");
if (!sPreservedWrapperTable.ops &&
!PL_DHashTableInit(&sPreservedWrapperTable, PL_DHashGetStubOps(), nsnull,
sizeof(PreservedWrapperEntry), 16)) {
!PL_DHashTableInit(&sPreservedWrapperTable, &sPreservedWrapperTableOps,
nsnull, sizeof(PreservedWrapperEntry), 16)) {
sPreservedWrapperTable.ops = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
@ -5024,10 +5092,42 @@ nsDOMClassInfo::PreserveWrapper(void *aKey,
PL_DHashTableOperate(&sPreservedWrapperTable, aKey, PL_DHASH_ADD));
if (!entry)
return NS_ERROR_OUT_OF_MEMORY;
NS_ASSERTION(!entry->key ||
(entry->key == aKey &&
entry->keyToWrapperFunc == aKeyToWrapperFunc &&
entry->participant == aParticipant &&
!entry->rootWhenExternallyReferenced &&
!aRootWhenExternallyReferenced),
"preservation key already used");
entry->key = aKey;
entry->keyToWrapperFunc = aKeyToWrapperFunc;
entry->participant = aParticipant;
entry->rootWhenExternallyReferenced = aRootWhenExternallyReferenced;
if (aRootWhenExternallyReferenced) {
if (!sRootWhenExternallyReferencedTable.ops &&
!PL_DHashTableInit(&sRootWhenExternallyReferencedTable,
PL_DHashGetStubOps(), nsnull,
sizeof(RootWhenExternallyReferencedEntry), 16)) {
PL_DHashTableRawRemove(&sPreservedWrapperTable, entry);
return NS_ERROR_OUT_OF_MEMORY;
}
RootWhenExternallyReferencedEntry *rwerEntry =
NS_STATIC_CAST(RootWhenExternallyReferencedEntry*,
PL_DHashTableOperate(&sRootWhenExternallyReferencedTable,
aParticipant, PL_DHASH_ADD));
if (!entry) {
PL_DHashTableRawRemove(&sPreservedWrapperTable, entry);
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ASSERTION(rwerEntry->refcnt == 0 ||
rwerEntry->participant == aParticipant,
"entry mismatch");
rwerEntry->participant = aParticipant;
++rwerEntry->refcnt;
}
return NS_OK;
}
@ -5048,7 +5148,7 @@ nsDOMClassInfo::PreserveNodeWrapper(nsIXPConnectWrappedNative *aWrapper)
return NS_OK;
return nsDOMClassInfo::PreserveWrapper(aWrapper, IdentityKeyToWrapperFunc,
participant);
participant, PR_FALSE);
}
// static
@ -5103,7 +5203,7 @@ nsDOMClassInfo::MarkReachablePreservedWrappers(nsIDOMGCParticipant *aParticipant
// the mark phase. However, we can determine that the mark phase
// has begun by detecting the first mark and lazily call
// BeginGCMark.
if (!sWrapperSCCTable.ops && !nsDOMClassInfo::BeginGCMark()) {
if (!sWrapperSCCTable.ops && !nsDOMClassInfo::BeginGCMark(cx)) {
// We didn't have enough memory to create the temporary data
// structures we needed.
sWrapperSCCTable.ops = WRAPPER_SCC_OPS_OOM_MARKER;
@ -5159,6 +5259,45 @@ nsDOMClassInfo::MarkReachablePreservedWrappers(nsIDOMGCParticipant *aParticipant
}
}
struct ExternallyReferencedEntry : public PLDHashEntryHdr {
nsIDOMGCParticipant* key; // must be first to line up with PLDHashEntryStub
};
/* static */ nsresult
nsDOMClassInfo::SetExternallyReferenced(nsIDOMGCParticipant *aParticipant)
{
NS_PRECONDITION(aParticipant, "unexpected null pointer");
if (!sExternallyReferencedTable.ops &&
!PL_DHashTableInit(&sExternallyReferencedTable, PL_DHashGetStubOps(),
nsnull, sizeof(ExternallyReferencedEntry), 16)) {
sExternallyReferencedTable.ops = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
ExternallyReferencedEntry *entry = NS_STATIC_CAST(ExternallyReferencedEntry*,
PL_DHashTableOperate(&sExternallyReferencedTable, aParticipant, PL_DHASH_ADD));
if (!entry)
return NS_ERROR_OUT_OF_MEMORY;
NS_ASSERTION(!entry->key, "participant already rooted");
entry->key = aParticipant;
return NS_OK;
}
/* static */ void
nsDOMClassInfo::UnsetExternallyReferenced(nsIDOMGCParticipant *aParticipant)
{
NS_PRECONDITION(aParticipant, "unexpected null pointer");
PL_DHashTableOperate(&sExternallyReferencedTable, aParticipant,
PL_DHASH_REMOVE);
// Don't destroy the table when the entryCount hits zero, since that
// is expected to happen often. Instead, destroy it at shutdown.
}
PR_STATIC_CALLBACK(PLDHashOperator)
ClassifyWrapper(PLDHashTable *table, PLDHashEntryHdr *hdr,
PRUint32 number, void *arg)
@ -5193,9 +5332,34 @@ ClassifyWrapper(PLDHashTable *table, PLDHashEntryHdr *hdr,
return PL_DHASH_NEXT;
}
PR_STATIC_CALLBACK(PLDHashOperator)
MarkExternallyReferenced(PLDHashTable *table, PLDHashEntryHdr *hdr,
PRUint32 number, void *arg)
{
ExternallyReferencedEntry *entry =
NS_STATIC_CAST(ExternallyReferencedEntry*, hdr);
JSContext *cx = NS_STATIC_CAST(JSContext*, arg);
nsIDOMGCParticipant *erParticipant = entry->key;
if (sRootWhenExternallyReferencedTable.ops) {
RootWhenExternallyReferencedEntry *rwerEntry =
NS_STATIC_CAST(RootWhenExternallyReferencedEntry*,
PL_DHashTableOperate(&sRootWhenExternallyReferencedTable,
erParticipant, PL_DHASH_LOOKUP));
if (PL_DHASH_ENTRY_IS_BUSY(rwerEntry)) {
NS_ASSERTION(rwerEntry->refcnt > 0, "bad reference count");
// XXX Construct something to say where the mark is coming from?
nsDOMClassInfo::MarkReachablePreservedWrappers(entry->key, cx, nsnull);
}
}
return PL_DHASH_NEXT;
}
// static
PRBool
nsDOMClassInfo::BeginGCMark()
nsDOMClassInfo::BeginGCMark(JSContext *cx)
{
NS_PRECONDITION(!sWrapperSCCTable.ops, "table already initialized");
@ -5209,8 +5373,9 @@ nsDOMClassInfo::BeginGCMark()
}
PRBool failure = PR_FALSE;
if (sPreservedWrapperTable.ops)
if (sPreservedWrapperTable.ops) {
PL_DHashTableEnumerate(&sPreservedWrapperTable, ClassifyWrapper, &failure);
}
if (failure) {
PL_DHashTableFinish(&sWrapperSCCTable);
// caller will reset table ops
@ -5221,6 +5386,11 @@ nsDOMClassInfo::BeginGCMark()
printf("Marking:\n");
#endif
if (sExternallyReferencedTable.ops) {
PL_DHashTableEnumerate(&sExternallyReferencedTable,
MarkExternallyReferenced, cx);
}
return PR_TRUE;
}

View File

@ -184,10 +184,15 @@ public:
* No strong references are held as a result of this function call, so
* the caller is responsible for calling |ReleaseWrapper| sometime
* before |aParticipant|'s destructor runs.
*
* aRootWhenExternallyReferenced should be true if the preservation is
* for an event handler that could be triggered by the participant
* being externally referenced by a network load.
*/
static nsresult PreserveWrapper(void* aKey,
nsIXPConnectJSObjectHolder* (*aKeyToWrapperFunc)(void* aKey),
nsIDOMGCParticipant *aParticipant);
nsIDOMGCParticipant *aParticipant,
PRBool aRootWhenExternallyReferenced);
/**
@ -212,11 +217,36 @@ public:
static void MarkReachablePreservedWrappers(nsIDOMGCParticipant *aParticipant,
JSContext *cx, void *arg);
/**
* Add/remove |aParticipant| from the table of externally referenced
* participants. Does not maintain a count, so an object should not
* be added when it is already in the table.
*
* The table of externally referenced participants is a list of
* participants that should be marked at GC-time **if they are a
* participant in the preserved wrapper table added with
* aRootWhenExternallyReferenced**, whether or not they are reachable
* from marked participants. This should be used for participants
* that hold onto scripts (typically onload or onerror handlers) that
* can be triggered at the end of a currently-ongoing operation
* (typically a network request) and that could thus expose the
* participant to script again in the future even though it is not
* otherwise reachable.
*
* The caller is responsible for ensuring that the GC participant is
* alive while it is in this table; the table does not own a reference.
*
* UnsetExternallyReferenced must be called exactly once for every
* successful SetExternallyReferenced call, and in no other cases.
*/
static nsresult SetExternallyReferenced(nsIDOMGCParticipant *aParticipant);
static void UnsetExternallyReferenced(nsIDOMGCParticipant *aParticipant);
/**
* Classify the wrappers for use by |MarkReachablePreservedWrappers|
* during the GC. Returns false to indicate failure (out-of-memory).
*/
static PRBool BeginGCMark();
static PRBool BeginGCMark(JSContext *cx);
/**
* Clean up data structures (and strong references) created by

View File

@ -268,8 +268,13 @@ nsMarkedJSFunctionHolder_base::TryMarkedSet(nsISupports *aPotentialFunction,
if (!wrappedJS) // a non-JS implementation
return PR_FALSE;
// XXX We really only need to pass PR_TRUE for
// root-if-externally-referenced if this is an onload, onerror,
// onreadystatechange, etc., so we could pass the responsibility for
// choosing that to the caller.
nsresult rv =
nsDOMClassInfo::PreserveWrapper(this, HolderToWrappedJS, aParticipant);
nsDOMClassInfo::PreserveWrapper(this, HolderToWrappedJS, aParticipant,
PR_TRUE);
NS_ENSURE_SUCCESS(rv, PR_FALSE);
nsIWeakReference* weakRef; // [STRONG]

View File

@ -143,6 +143,10 @@ public:
already_AddRefed<T> Get() {
return already_AddRefed<T>(NS_STATIC_CAST(T*, nsMarkedJSFunctionHolder_base::Get(NS_GET_TEMPLATE_IID(T)).get()));
}
// An overloaded version that's more useful for XPCOM getters
void Get(T** aResult) {
*aResult = Get().get();
}
};
#endif /* nsJSUtils_h__ */