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:
Родитель
5f1306128f
Коммит
bb92b3038c
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче