Make sure to call ScriptEvaluated when doing all evaluations, to call it

_after_ we've popped the JSContext from the stack, and to handle multiple
termination functions being posted from a single script evaluation.  Fixes
leaks when closing windows or tabs with still-loading documents.  Bug 295983,
r+sr=jst, a=asa
This commit is contained in:
bzbarsky%mit.edu 2005-06-09 15:42:19 +00:00
Родитель 379cc266ed
Коммит 3114a994a1
7 изменённых файлов: 152 добавлений и 44 удалений

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

@ -965,11 +965,16 @@ nsHTMLDocument::EndLoad()
// document.write("foo");
// location.href = "http://www.mozilla.org";
// document.write("bar");
scx->SetTerminationFunction(DocumentWriteTerminationFunc,
NS_STATIC_CAST(nsIDocument *, this));
return;
nsresult rv =
scx->SetTerminationFunction(DocumentWriteTerminationFunc,
NS_STATIC_CAST(nsIDocument *, this));
// If we fail to set the termination function, just go ahead
// and EndLoad now. The slight bugginess involved is better
// than leaking.
if (NS_SUCCEEDED(rv)) {
return;
}
}
}
}

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

@ -76,12 +76,15 @@ nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIConten
// class object in the bound document that represents the concrete version of this implementation.
// This function also has the side effect of building up the prototype implementation if it has
// not been built already.
void * targetScriptObject = nsnull;
nsCOMPtr<nsIXPConnectJSObjectHolder> holder;
void * targetClassObject = nsnull;
nsresult rv = InitTargetObjects(aBinding, context, aBoundElement,
&targetScriptObject, &targetClassObject);
getter_AddRefs(holder), &targetClassObject);
NS_ENSURE_SUCCESS(rv, rv); // kick out if we were unable to properly intialize our target objects
JSObject * targetScriptObject;
holder->GetJSObject(&targetScriptObject);
// Walk our member list and install each one in turn.
for (nsXBLProtoImplMember* curr = mMembers;
curr;
@ -95,10 +98,12 @@ nsresult
nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
nsIScriptContext* aContext,
nsIContent* aBoundElement,
void** aScriptObject,
nsIXPConnectJSObjectHolder** aScriptObjectHolder,
void** aTargetClassObject)
{
nsresult rv = NS_OK;
*aScriptObjectHolder = nsnull;
if (!mClassObject) {
rv = CompilePrototypeMembers(aBinding); // This is the first time we've ever installed this binding on an element.
// We need to go ahead and compile all methods and properties on a class
@ -128,7 +133,6 @@ nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
// concrete base class. We need to alter the object so that our concrete class is interposed
// between the object and its base class. We become the new base class of the object, and the
// object's old base class becomes the new class' base class.
*aScriptObject = object;
rv = aBinding->InitClass(mClassName, aContext, (void *) object, aTargetClassObject);
if (NS_FAILED(rv))
return rv;
@ -142,6 +146,8 @@ nsXBLProtoImpl::InitTargetObjects(nsXBLPrototypeBinding* aBinding,
NS_DOMClassInfo_PreserveWrapper(node, nativeWrapper);
}
}
wrapper.swap(*aScriptObjectHolder);
return rv;
}

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

@ -44,6 +44,8 @@
#include "nsXBLProtoImplMember.h"
#include "nsXBLPrototypeBinding.h"
class nsIXPConnectJSObjectHolder;
MOZ_DECL_CTOR_COUNTER(nsXBLProtoImpl)
class nsXBLProtoImpl
@ -70,7 +72,8 @@ public:
nsresult InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement);
nsresult InitTargetObjects(nsXBLPrototypeBinding* aBinding, nsIScriptContext* aContext,
nsIContent* aBoundElement,
void** aScriptObject, void** aTargetClassObject);
nsIXPConnectJSObjectHolder** aScriptObjectHolder,
void** aTargetClassObject);
nsresult CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding);
void SetMemberList(nsXBLProtoImplMember* aMemberList) { delete mMembers; mMembers = aMemberList; };

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

@ -310,11 +310,13 @@ public:
/**
* Called to specify a function that should be called when the current
* script (if there is one) terminates. Generally used if breakdown
* of script state needs to be happen, but should be deferred till
* of script state needs to happen, but should be deferred till
* the end of script evaluation.
*
* @throws NS_ERROR_OUT_OF_MEMORY if that happens
*/
virtual void SetTerminationFunction(nsScriptTerminationFunc aFunc,
nsISupports* aRef) = 0;
virtual nsresult SetTerminationFunction(nsScriptTerminationFunc aFunc,
nsISupports* aRef) = 0;
/**
* Called to disable/enable script execution in this context.

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

@ -3595,6 +3595,10 @@ nsGlobalWindow::Close()
nsIScriptContext *currentCX = nsJSUtils::GetDynamicScriptContext(cx);
if (currentCX && currentCX == mContext) {
// We ignore the return value here. If setting the termination function
// fails, it's better to fail to close the window than it is to crash
// (which is what would tend to happen if we did this synchronously
// here).
currentCX->SetTerminationFunction(CloseWindow,
NS_STATIC_CAST(nsIDOMWindow *,
this));

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

@ -713,7 +713,7 @@ nsJSContext::nsJSContext(JSRuntime *aRuntime) : mGCOnDestruction(PR_TRUE)
mIsInitialized = PR_FALSE;
mNumEvaluations = 0;
mOwner = nsnull;
mTerminationFunc = nsnull;
mTerminations = nsnull;
mScriptsEnabled = PR_TRUE;
mBranchCallbackCount = 0;
mBranchCallbackTime = LL_ZERO;
@ -724,6 +724,8 @@ nsJSContext::nsJSContext(JSRuntime *aRuntime) : mGCOnDestruction(PR_TRUE)
nsJSContext::~nsJSContext()
{
NS_PRECONDITION(!mTerminations, "Shouldn't have termination funcs by now");
// Cope with JS_NewContext failure in ctor (XXXbe move NewContext to Init?)
if (!mContext)
return;
@ -845,12 +847,12 @@ nsJSContext::EvaluateStringWithValue(const nsAString& aScript,
return NS_ERROR_FAILURE;
}
// The result of evaluation, used only if there were no errors. This need
// not be a GC root currently, provided we run the GC only from the branch
// callback or from ScriptEvaluated. TODO: use JS_Begin/EndRequest to keep
// the GC from racing with JS execution on any thread.
// The result of evaluation, used only if there were no errors. TODO: use
// JS_Begin/EndRequest to keep the GC from racing with JS execution on any
// thread.
jsval val;
nsJSContext::TerminationFuncHolder holder(this);
if (ok) {
JSVersion newVersion = JSVERSION_UNKNOWN;
@ -864,8 +866,6 @@ nsJSContext::EvaluateStringWithValue(const nsAString& aScript,
if (aVersion)
oldVersion = ::JS_SetVersion(mContext, newVersion);
mTerminationFuncArg = nsnull;
mTerminationFunc = nsnull;
ok = ::JS_EvaluateUCScriptForPrincipals(mContext,
(JSObject *)aScopeObject,
jsprin,
@ -910,6 +910,22 @@ nsJSContext::EvaluateStringWithValue(const nsAString& aScript,
if (NS_FAILED(stack->Pop(nsnull)))
rv = NS_ERROR_FAILURE;
// Need to lock, since ScriptEvaluated can GC.
PRBool locked = PR_FALSE;
if (ok && JSVAL_IS_GCTHING(val)) {
locked = ::JS_LockGCThing(mContext, JSVAL_TO_GCTHING(val));
if (!locked) {
rv = NS_ERROR_OUT_OF_MEMORY;
}
}
// ScriptEvaluated needs to come after we pop the stack
ScriptEvaluated(PR_TRUE);
if (locked) {
::JS_UnlockGCThing(mContext, JSVAL_TO_GCTHING(val));
}
return rv;
}
@ -1031,6 +1047,7 @@ nsJSContext::EvaluateString(const nsAString& aScript,
// the GC from racing with JS execution on any thread.
jsval val;
nsJSContext::TerminationFuncHolder holder(this);
if (ok) {
JSVersion newVersion = JSVERSION_UNKNOWN;
@ -1044,8 +1061,6 @@ nsJSContext::EvaluateString(const nsAString& aScript,
if (aVersion)
oldVersion = ::JS_SetVersion(mContext, newVersion);
mTerminationFuncArg = nsnull;
mTerminationFunc = nsnull;
ok = ::JS_EvaluateUCScriptForPrincipals(mContext,
(JSObject *)aScopeObject,
jsprin,
@ -1086,12 +1101,13 @@ nsJSContext::EvaluateString(const nsAString& aScript,
}
}
ScriptEvaluated(PR_TRUE);
// Pop here, after JS_ValueToString and any other possible evaluation.
if (NS_FAILED(stack->Pop(nsnull)))
rv = NS_ERROR_FAILURE;
// ScriptEvaluated needs to come after we pop the stack
ScriptEvaluated(PR_TRUE);
return rv;
}
@ -1202,8 +1218,7 @@ nsJSContext::ExecuteScript(void* aScriptObject,
jsval val;
JSBool ok;
mTerminationFuncArg = nsnull;
mTerminationFunc = nsnull;
nsJSContext::TerminationFuncHolder holder(this);
ok = ::JS_ExecuteScript(mContext,
(JSObject*) aScopeObject,
(JSScript*) ::JS_GetPrivate(mContext,
@ -1230,12 +1245,13 @@ nsJSContext::ExecuteScript(void* aScriptObject,
NotifyXPCIfExceptionPending(mContext);
}
ScriptEvaluated(PR_TRUE);
// Pop here, after JS_ValueToString and any other possible evaluation.
if (NS_FAILED(stack->Pop(nsnull)))
rv = NS_ERROR_FAILURE;
// ScriptEvaluated needs to come after we pop the stack
ScriptEvaluated(PR_TRUE);
return rv;
}
@ -1384,19 +1400,16 @@ nsJSContext::CallEventHandler(JSObject *aTarget, JSObject *aHandler,
if (NS_FAILED(rv) || NS_FAILED(stack->Push(mContext)))
return NS_ERROR_FAILURE;
mTerminationFuncArg = nsnull;
mTerminationFunc = nsnull;
// check if the event handler can be run on the object in question
rv = sSecurityManager->CheckFunctionAccess(mContext, aHandler, aTarget);
nsJSContext::TerminationFuncHolder holder(this);
if (NS_SUCCEEDED(rv)) {
jsval funval = OBJECT_TO_JSVAL(aHandler);
PRBool ok = ::JS_CallFunctionValue(mContext, aTarget, funval, argc, argv,
rval);
ScriptEvaluated(PR_TRUE);
if (!ok) {
// Tell XPConnect about any pending exceptions. This is needed
// to avoid dropping JS exceptions in case we got here through
@ -1415,6 +1428,22 @@ nsJSContext::CallEventHandler(JSObject *aTarget, JSObject *aHandler,
if (NS_FAILED(stack->Pop(nsnull)))
return NS_ERROR_FAILURE;
// Need to lock, since ScriptEvaluated can GC.
PRBool locked = PR_FALSE;
if (NS_SUCCEEDED(rv) && JSVAL_IS_GCTHING(*rval)) {
locked = ::JS_LockGCThing(mContext, JSVAL_TO_GCTHING(*rval));
if (!locked) {
rv = NS_ERROR_OUT_OF_MEMORY;
}
}
// ScriptEvaluated needs to come after we pop the stack
ScriptEvaluated(PR_TRUE);
if (locked) {
::JS_UnlockGCThing(mContext, JSVAL_TO_GCTHING(*rval));
}
return rv;
}
@ -1905,10 +1934,18 @@ nsJSContext::GC()
void
nsJSContext::ScriptEvaluated(PRBool aTerminated)
{
if (aTerminated && mTerminationFunc) {
(*mTerminationFunc)(mTerminationFuncArg);
mTerminationFuncArg = nsnull;
mTerminationFunc = nsnull;
if (aTerminated && mTerminations) {
// Make sure to null out mTerminations before doing anything that
// might cause new termination funcs to be added!
nsJSContext::TerminationFuncClosure* start = mTerminations;
mTerminations = nsnull;
for (nsJSContext::TerminationFuncClosure* cur = start;
cur;
cur = cur->mNext) {
(*(cur->mTerminationFunc))(cur->mTerminationFuncArg);
}
delete start;
}
mNumEvaluations++;
@ -1940,12 +1977,18 @@ nsJSContext::GetOwner()
return mOwner;
}
void
nsresult
nsJSContext::SetTerminationFunction(nsScriptTerminationFunc aFunc,
nsISupports* aRef)
{
mTerminationFunc = aFunc;
mTerminationFuncArg = aRef;
nsJSContext::TerminationFuncClosure* newClosure =
new nsJSContext::TerminationFuncClosure(aFunc, aRef, mTerminations);
if (!newClosure) {
return NS_ERROR_OUT_OF_MEMORY;
}
mTerminations = newClosure;
return NS_OK;
}
PRBool

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

@ -120,8 +120,8 @@ public:
virtual void ScriptEvaluated(PRBool aTerminated);
virtual void SetOwner(nsIScriptContextOwner* owner);
virtual nsIScriptContextOwner *GetOwner();
virtual void SetTerminationFunction(nsScriptTerminationFunc aFunc,
nsISupports* aRef);
virtual nsresult SetTerminationFunction(nsScriptTerminationFunc aFunc,
nsISupports* aRef);
virtual PRBool GetScriptsEnabled();
virtual void SetScriptsEnabled(PRBool aEnabled, PRBool aFireTimeouts);
@ -146,10 +146,55 @@ private:
PRUint32 mNumEvaluations;
nsIScriptContextOwner* mOwner; /* NB: weak reference, not ADDREF'd */
nsScriptTerminationFunc mTerminationFunc;
nsCOMPtr<nsISupports> mTerminationFuncArg;
protected:
struct TerminationFuncClosure {
TerminationFuncClosure(nsScriptTerminationFunc aFunc,
nsISupports* aArg,
TerminationFuncClosure* aNext) :
mTerminationFunc(aFunc),
mTerminationFuncArg(aArg),
mNext(aNext)
{}
~TerminationFuncClosure()
{
delete mNext;
}
nsScriptTerminationFunc mTerminationFunc;
nsCOMPtr<nsISupports> mTerminationFuncArg;
TerminationFuncClosure* mNext;
};
struct TerminationFuncHolder {
TerminationFuncHolder(nsJSContext* aContext) :
mContext(aContext),
mTerminations(aContext->mTerminations)
{
aContext->mTerminations = nsnull;
}
~TerminationFuncHolder() {
// Have to be careful here. mContext might have picked up new
// termination funcs while the script was evaluating. Prepend whatever
// we have to the current termination funcs on the context (since our
// termination funcs were posted first).
if (mTerminations) {
TerminationFuncClosure* cur = mTerminations;
while (cur->mNext) {
cur = cur->mNext;
}
cur->mNext = mContext->mTerminations;
mContext->mTerminations = mTerminations;
}
}
nsJSContext* mContext;
TerminationFuncClosure* mTerminations;
};
TerminationFuncClosure* mTerminations;
private:
PRPackedBool mIsInitialized;
PRPackedBool mScriptsEnabled;
PRPackedBool mGCOnDestruction;