diff --git a/mozilla/content/html/document/src/nsHTMLDocument.cpp b/mozilla/content/html/document/src/nsHTMLDocument.cpp index b4f69f30236..f8202fc914e 100644 --- a/mozilla/content/html/document/src/nsHTMLDocument.cpp +++ b/mozilla/content/html/document/src/nsHTMLDocument.cpp @@ -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; + } } } } diff --git a/mozilla/content/xbl/src/nsXBLProtoImpl.cpp b/mozilla/content/xbl/src/nsXBLProtoImpl.cpp index d67005bb810..31013416fa7 100644 --- a/mozilla/content/xbl/src/nsXBLProtoImpl.cpp +++ b/mozilla/content/xbl/src/nsXBLProtoImpl.cpp @@ -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 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; } diff --git a/mozilla/content/xbl/src/nsXBLProtoImpl.h b/mozilla/content/xbl/src/nsXBLProtoImpl.h index 10998ba342a..c861abcadb5 100644 --- a/mozilla/content/xbl/src/nsXBLProtoImpl.h +++ b/mozilla/content/xbl/src/nsXBLProtoImpl.h @@ -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; }; diff --git a/mozilla/dom/public/nsIScriptContext.h b/mozilla/dom/public/nsIScriptContext.h index ef247bd2e0e..3d4ec745145 100644 --- a/mozilla/dom/public/nsIScriptContext.h +++ b/mozilla/dom/public/nsIScriptContext.h @@ -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. diff --git a/mozilla/dom/src/base/nsGlobalWindow.cpp b/mozilla/dom/src/base/nsGlobalWindow.cpp index 801de2733ab..259e913dd84 100644 --- a/mozilla/dom/src/base/nsGlobalWindow.cpp +++ b/mozilla/dom/src/base/nsGlobalWindow.cpp @@ -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)); diff --git a/mozilla/dom/src/base/nsJSEnvironment.cpp b/mozilla/dom/src/base/nsJSEnvironment.cpp index 3aa99fea428..589bebc9b61 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.cpp +++ b/mozilla/dom/src/base/nsJSEnvironment.cpp @@ -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 diff --git a/mozilla/dom/src/base/nsJSEnvironment.h b/mozilla/dom/src/base/nsJSEnvironment.h index 91217b14a81..06c92d2f280 100644 --- a/mozilla/dom/src/base/nsJSEnvironment.h +++ b/mozilla/dom/src/base/nsJSEnvironment.h @@ -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 mTerminationFuncArg; +protected: + struct TerminationFuncClosure { + TerminationFuncClosure(nsScriptTerminationFunc aFunc, + nsISupports* aArg, + TerminationFuncClosure* aNext) : + mTerminationFunc(aFunc), + mTerminationFuncArg(aArg), + mNext(aNext) + {} + ~TerminationFuncClosure() + { + delete mNext; + } + + nsScriptTerminationFunc mTerminationFunc; + nsCOMPtr 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;