diff options
-rw-r--r-- | dom/base/DOMException.cpp | 45 | ||||
-rw-r--r-- | dom/base/DOMException.h | 3 | ||||
-rw-r--r-- | dom/base/ScriptSettings.cpp | 97 | ||||
-rw-r--r-- | dom/base/ScriptSettings.h | 11 | ||||
-rw-r--r-- | dom/base/nsGlobalWindow.cpp | 1 | ||||
-rw-r--r-- | dom/base/nsJSUtils.cpp | 65 | ||||
-rw-r--r-- | dom/base/nsJSUtils.h | 18 | ||||
-rw-r--r-- | dom/base/nsScriptLoader.cpp | 1 | ||||
-rw-r--r-- | dom/bindings/CallbackObject.cpp | 29 | ||||
-rw-r--r-- | dom/bindings/CallbackObject.h | 1 | ||||
-rw-r--r-- | dom/bindings/test/mochitest.ini | 1 | ||||
-rw-r--r-- | dom/bindings/test/test_callback_exceptions.html | 17 | ||||
-rw-r--r-- | dom/jsurl/nsJSProtocolHandler.cpp | 15 | ||||
-rw-r--r-- | dom/plugins/base/nsNPAPIPlugin.cpp | 1 | ||||
-rw-r--r-- | dom/webidl/DOMException.webidl | 2 | ||||
-rw-r--r-- | dom/xbl/nsXBLProtoImplField.cpp | 7 | ||||
-rw-r--r-- | js/src/jscntxt.cpp | 8 | ||||
-rw-r--r-- | js/src/jsexn.cpp | 6 | ||||
-rw-r--r-- | js/src/jsexn.h | 14 | ||||
-rw-r--r-- | js/xpconnect/idl/xpcexception.idl | 5 | ||||
-rw-r--r-- | xpcom/base/ErrorList.h | 6 | ||||
-rw-r--r-- | xpcom/base/nsIException.idl | 4 |
22 files changed, 168 insertions, 189 deletions
diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp index d84d198df..a9caf8968 100644 --- a/dom/base/DOMException.cpp +++ b/dom/base/DOMException.cpp @@ -173,7 +173,6 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(Exception) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Exception) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -184,7 +183,6 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Exception) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mInner) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER tmp->mThrownJSVal.setNull(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -244,7 +242,7 @@ Exception::Exception(const nsACString& aMessage, } } - Initialize(aMessage, aResult, aName, location, aData, nullptr); + Initialize(aMessage, aResult, aName, location, aData); } Exception::Exception() @@ -403,18 +401,6 @@ Exception::GetData(nsISupports** aData) return NS_OK; } -/* readonly attribute nsIException inner; */ -NS_IMETHODIMP -Exception::GetInner(nsIException** aException) -{ - NS_ENSURE_ARG_POINTER(aException); - NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); - - nsCOMPtr<nsIException> inner = mInner; - inner.forget(aException); - return NS_OK; -} - /* AUTF8String toString (); */ NS_IMETHODIMP Exception::ToString(nsACString& _retval) @@ -463,7 +449,7 @@ Exception::ToString(nsACString& _retval) NS_IMETHODIMP Exception::Initialize(const nsACString& aMessage, nsresult aResult, const nsACString& aName, nsIStackFrame *aLocation, - nsISupports *aData, nsIException *aInner) + nsISupports *aData) { NS_ENSURE_FALSE(mInitialized, NS_ERROR_ALREADY_INITIALIZED); @@ -483,7 +469,6 @@ Exception::Initialize(const nsACString& aMessage, nsresult aResult, } mData = aData; - mInner = aInner; mInitialized = true; return NS_OK; @@ -553,13 +538,6 @@ Exception::GetLocation() const } already_AddRefed<nsISupports> -Exception::GetInner() const -{ - nsCOMPtr<nsIException> inner = mInner; - return inner.forget(); -} - -already_AddRefed<nsISupports> Exception::GetData() const { nsCOMPtr<nsISupports> data = mData; @@ -632,25 +610,6 @@ DOMException::ToString(nsACString& aReturn) nsAutoCString location; - if (mInner) { - nsString filename; - mInner->GetFilename(filename); - - if (!filename.IsEmpty()) { - uint32_t line_nr = 0; - - mInner->GetLineNumber(&line_nr); - - char *temp = PR_smprintf("%s Line: %d", - NS_ConvertUTF16toUTF8(filename).get(), - line_nr); - if (temp) { - location.Assign(temp); - PR_smprintf_free(temp); - } - } - } - if (location.IsEmpty()) { location = defaultLocation; } diff --git a/dom/base/DOMException.h b/dom/base/DOMException.h index f2b2dadea..97f2d272c 100644 --- a/dom/base/DOMException.h +++ b/dom/base/DOMException.h @@ -83,8 +83,6 @@ public: already_AddRefed<nsIStackFrame> GetLocation() const; - already_AddRefed<nsISupports> GetInner() const; - already_AddRefed<nsISupports> GetData() const; void GetStack(nsAString& aStack, ErrorResult& aRv) const; @@ -110,7 +108,6 @@ protected: nsCOMPtr<nsISupports> mData; nsString mFilename; int mLineNumber; - nsCOMPtr<nsIException> mInner; bool mInitialized; bool mHoldingJSVal; diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp index 3fbfb9043..daf8ebb33 100644 --- a/dom/base/ScriptSettings.cpp +++ b/dom/base/ScriptSettings.cpp @@ -312,36 +312,15 @@ AutoJSAPI::~AutoJSAPI() { if (mOwnErrorReporting) { MOZ_ASSERT(NS_IsMainThread(), "See corresponding assertion in TakeOwnershipOfErrorReporting()"); - JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting); - if (HasException()) { - - // AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null - // compartment when the destructor is called. However, the JS engine - // requires us to be in a compartment when we fetch the pending exception. - // In this case, we enter the privileged junk scope and don't dispatch any - // error events. - JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx())); - if (!errorGlobal) - errorGlobal = xpc::PrivilegedJunkScope(); - JSAutoCompartment ac(cx(), errorGlobal); - nsCOMPtr<nsPIDOMWindow> win = xpc::WindowGlobalOrNull(errorGlobal); - JS::Rooted<JS::Value> exn(cx()); - js::ErrorReport jsReport(cx()); - if (StealException(&exn) && jsReport.init(cx(), exn)) { - nsRefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport(); - xpcReport->Init(jsReport.report(), jsReport.message(), - nsContentUtils::IsCallerChrome(), - win ? win->WindowID() : 0); - if (win) { - DispatchScriptErrorEvent(win, JS_GetRuntime(cx()), xpcReport, exn); - } else { - xpcReport->LogToConsole(); - } - } else { - NS_WARNING("OOMed while acquiring uncaught exception from JSAPI"); - } - } + ReportException(); + + // We need to do this _after_ processing the existing exception, because the + // JS engine can throw while doing that, and uses this bit to determine what + // to do in that case: squelch the exception if the bit is set, otherwise + // call the error reporter. Calling WarningOnlyErrorReporter with a + // non-warning will assert, so we need to make sure we do the former. + JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting); } if (mOldErrorReporter.isSome()) { @@ -502,17 +481,61 @@ AutoJSAPI::TakeOwnershipOfErrorReporting() JS_SetErrorReporter(rt, WarningOnlyErrorReporter); } +void +AutoJSAPI::ReportException() +{ + MOZ_ASSERT(OwnsErrorReporting(), "This is not our exception to report!"); + if (!HasException()) { + return; + } + + // AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null + // compartment when the destructor is called. However, the JS engine + // requires us to be in a compartment when we fetch the pending exception. + // In this case, we enter the privileged junk scope and don't dispatch any + // error events. + JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx())); + if (!errorGlobal) + errorGlobal = xpc::PrivilegedJunkScope(); + JSAutoCompartment ac(cx(), errorGlobal); + nsCOMPtr<nsPIDOMWindow> win = xpc::WindowGlobalOrNull(errorGlobal); + JS::Rooted<JS::Value> exn(cx()); + js::ErrorReport jsReport(cx()); + if (StealException(&exn) && jsReport.init(cx(), exn)) { + nsRefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport(); + xpcReport->Init(jsReport.report(), jsReport.message(), + nsContentUtils::IsCallerChrome(), + win ? win->WindowID() : 0); + if (win) { + DispatchScriptErrorEvent(win, JS_GetRuntime(cx()), xpcReport, exn); + } else { + xpcReport->LogToConsole(); + } + } else { + NS_WARNING("OOMed while acquiring uncaught exception from JSAPI"); + } +} + +bool +AutoJSAPI::PeekException(JS::MutableHandle<JS::Value> aVal) +{ + MOZ_ASSERT_IF(mIsMainThread, CxPusherIsStackTop()); + MOZ_ASSERT(HasException()); + MOZ_ASSERT(js::GetContextCompartment(cx())); + if (!JS_GetPendingException(cx(), aVal)) { + return false; + } + return true; +} + bool AutoJSAPI::StealException(JS::MutableHandle<JS::Value> aVal) { - MOZ_ASSERT(CxPusherIsStackTop()); - MOZ_ASSERT(HasException()); - MOZ_ASSERT(js::GetContextCompartment(cx())); - if (!JS_GetPendingException(cx(), aVal)) { - return false; - } - JS_ClearPendingException(cx()); - return true; + if (!PeekException(aVal)) { + return false; + } + JS_ClearPendingException(cx()); + return true; } AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h index f74d434c1..a9c1084cc 100644 --- a/dom/base/ScriptSettings.h +++ b/dom/base/ScriptSettings.h @@ -273,6 +273,9 @@ public: // while keeping the old behavior as the default. void TakeOwnershipOfErrorReporting(); bool OwnsErrorReporting() { return mOwnErrorReporting; } + // If HasException, report it. Otherwise, a no-op. This must be + // called only if OwnsErrorReporting(). + void ReportException(); bool HasException() const { MOZ_ASSERT(CxPusherIsStackTop()); @@ -287,6 +290,14 @@ public: // into the current compartment. bool StealException(JS::MutableHandle<JS::Value> aVal); + // Peek the current exception from the JS engine, without stealing it. + // Callers must ensure that HasException() is true, and that cx() is in a + // non-null compartment. + // + // Note that this fails if and only if we OOM while wrapping the exception + // into the current compartment. + bool PeekException(JS::MutableHandle<JS::Value> aVal); + void ClearException() { MOZ_ASSERT(CxPusherIsStackTop()); JS_ClearPendingException(cx()); diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 400b2109f..01266e899 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -12413,6 +12413,7 @@ nsGlobalWindow::RunTimeoutHandler(nsTimeout* aTimeout, // New script entry point required, due to the "Create a script" sub-step of // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialization-steps AutoEntryScript entryScript(this, true, aScx->GetNativeContext()); + entryScript.TakeOwnershipOfErrorReporting(); JS::CompileOptions options(entryScript.cx()); options.setFileAndLine(filename, lineNo) .setVersion(JSVERSION_DEFAULT); diff --git a/dom/base/nsJSUtils.cpp b/dom/base/nsJSUtils.cpp index 6441d1f33..d9c68b4d2 100644 --- a/dom/base/nsJSUtils.cpp +++ b/dom/base/nsJSUtils.cpp @@ -98,41 +98,6 @@ nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext) return innerWindowID; } -void -nsJSUtils::ReportPendingException(JSContext *aContext) -{ - if (JS_IsExceptionPending(aContext)) { - bool saved = JS_SaveFrameChain(aContext); - { - // JS_SaveFrameChain set the compartment of aContext to null, so we need - // to enter a compartment. The question is, which one? We don't want to - // enter the original compartment of aContext (or the compartment of the - // current exception on aContext, for that matter) because when we - // JS_ReportPendingException the JS engine can try to duck-type the - // exception and produce a JSErrorReport. It will then pass that - // JSErrorReport to the error reporter on aContext, which might expose - // information from it to script via onerror handlers. So it's very - // important that the duck typing happen in the same compartment as the - // onerror handler. In practice, that's the compartment of the window (or - // otherwise default global) of aContext, so use that here. - nsIScriptContext* scx = GetScriptContextFromJSContext(aContext); - JS::Rooted<JSObject*> scope(aContext); - scope = scx ? scx->GetWindowProxy() : nullptr; - if (!scope) { - // The SafeJSContext has no default object associated with it. - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aContext == nsContentUtils::GetSafeJSContext()); - scope = xpc::UnprivilegedJunkScope(); // Usage approved by bholley - } - JSAutoCompartment ac(aContext, scope); - JS_ReportPendingException(aContext); - } - if (saved) { - JS_RestoreFrameChain(aContext); - } - } -} - nsresult nsJSUtils::CompileFunction(AutoJSAPI& jsapi, JS::AutoObjectVector& aScopeChain, @@ -199,12 +164,11 @@ nsJSUtils::EvaluateString(JSContext* aCx, PROFILER_LABEL("nsJSUtils", "EvaluateString", js::ProfileEntry::Category::JS); + MOZ_ASSERT(JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting(), + "Caller must own error reporting"); MOZ_ASSERT_IF(aCompileOptions.versionSet, aCompileOptions.version != JSVERSION_UNKNOWN); MOZ_ASSERT_IF(aEvaluateOptions.coerceToString, !aCompileOptions.noScriptRval); - MOZ_ASSERT_IF(!aEvaluateOptions.reportUncaught, !aCompileOptions.noScriptRval); - // Note that the above assert means that if aCompileOptions.noScriptRval then - // also aEvaluateOptions.reportUncaught. MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext()); MOZ_ASSERT(aSrcBuf.get()); MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(aEvaluationGlobal) == @@ -225,13 +189,6 @@ nsJSUtils::EvaluateString(JSContext* aCx, nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); NS_ENSURE_TRUE(ssm->ScriptAllowed(aEvaluationGlobal), NS_OK); - mozilla::Maybe<AutoDontReportUncaught> dontReport; - if (!aEvaluateOptions.reportUncaught) { - // We need to prevent AutoLastFrameCheck from reporting and clearing - // any pending exceptions. - dontReport.emplace(aCx); - } - bool ok = true; // Scope the JSAutoCompartment so that we can later wrap the return value // into the caller's cx. @@ -275,24 +232,14 @@ nsJSUtils::EvaluateString(JSContext* aCx, } if (!ok) { - if (aEvaluateOptions.reportUncaught) { - ReportPendingException(aCx); - if (!aCompileOptions.noScriptRval) { - aRetValue.setUndefined(); - } - } else { - rv = JS_IsExceptionPending(aCx) ? NS_ERROR_FAILURE - : NS_ERROR_OUT_OF_MEMORY; - JS::Rooted<JS::Value> exn(aCx); - JS_GetPendingException(aCx, &exn); - MOZ_ASSERT(!aCompileOptions.noScriptRval); // we asserted this on entry - aRetValue.set(exn); - JS_ClearPendingException(aCx); + rv = NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW; + if (!aCompileOptions.noScriptRval) { + aRetValue.setUndefined(); } } // Wrap the return value into whatever compartment aCx was in. - if (!aCompileOptions.noScriptRval) { + if (ok && !aCompileOptions.noScriptRval) { if (!JS_WrapValue(aCx, aRetValue)) { return NS_ERROR_OUT_OF_MEMORY; } diff --git a/dom/base/nsJSUtils.h b/dom/base/nsJSUtils.h index d7ac67767..4679151c0 100644 --- a/dom/base/nsJSUtils.h +++ b/dom/base/nsJSUtils.h @@ -52,13 +52,6 @@ public: */ static uint64_t GetCurrentlyRunningCodeInnerWindowID(JSContext *aContext); - /** - * Report a pending exception on aContext, if any. Note that this - * can be called when the context has a JS stack. If that's the - * case, the stack will be set aside before reporting the exception. - */ - static void ReportPendingException(JSContext *aContext); - static nsresult CompileFunction(mozilla::dom::AutoJSAPI& jsapi, JS::AutoObjectVector& aScopeChain, JS::CompileOptions& aOptions, @@ -70,12 +63,10 @@ public: struct MOZ_STACK_CLASS EvaluateOptions { bool coerceToString; - bool reportUncaught; JS::AutoObjectVector scopeChain; explicit EvaluateOptions(JSContext* cx) : coerceToString(false) - , reportUncaught(true) , scopeChain(cx) {} @@ -83,16 +74,13 @@ public: coerceToString = aCoerce; return *this; } - - EvaluateOptions& setReportUncaught(bool aReport) { - reportUncaught = aReport; - return *this; - } }; // aEvaluationGlobal is the global to evaluate in. The return value // will then be wrapped back into the compartment aCx is in when - // this function is called. + // this function is called. For all the EvaluateString overloads, + // the JSContext must come from an AutoJSAPI that has had + // TakeOwnershipOfErrorReporting() called on it. static nsresult EvaluateString(JSContext* aCx, const nsAString& aScript, JS::Handle<JSObject*> aEvaluationGlobal, diff --git a/dom/base/nsScriptLoader.cpp b/dom/base/nsScriptLoader.cpp index 6f6adc6d0..739ad1430 100644 --- a/dom/base/nsScriptLoader.cpp +++ b/dom/base/nsScriptLoader.cpp @@ -1138,6 +1138,7 @@ nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest, // New script entry point required, due to the "Create a script" sub-step of // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block AutoEntryScript entryScript(globalObject, true, context->GetNativeContext()); + entryScript.TakeOwnershipOfErrorReporting(); JS::Rooted<JSObject*> global(entryScript.cx(), globalObject->GetGlobalJSObject()); diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index 2c0aca913..fee6b2e84 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -180,12 +180,8 @@ CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, // And now we're ready to go. mCx = cx; - // Make sure the JS engine doesn't report exceptions we want to re-throw - if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) || - mExceptionHandling == eRethrowExceptions) { - mSavedJSContextOptions = JS::ContextOptionsRef(cx); - JS::ContextOptionsRef(cx).setDontReportUncaught(true); - } + // Make sure the JS engine doesn't report exceptions we want to re-throw. + mAutoEntryScript->TakeOwnershipOfErrorReporting(); } bool @@ -251,18 +247,18 @@ CallbackObject::CallSetup::~CallSetup() // Now, if we have a JSContext, report any pending errors on it, unless we // were told to re-throw them. if (mCx) { - bool needToDealWithException = JS_IsExceptionPending(mCx); + bool needToDealWithException = mAutoEntryScript->HasException(); if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) || mExceptionHandling == eRethrowExceptions) { - // Restore the old context options - JS::ContextOptionsRef(mCx) = mSavedJSContextOptions; mErrorResult.MightThrowJSException(); + MOZ_ASSERT(mAutoEntryScript->OwnsErrorReporting()); if (needToDealWithException) { JS::Rooted<JS::Value> exn(mCx); - if (JS_GetPendingException(mCx, &exn) && + if (mAutoEntryScript->PeekException(&exn) && ShouldRethrowException(exn)) { + mAutoEntryScript->ClearException(); + MOZ_ASSERT(!mAutoEntryScript->HasException()); mErrorResult.ThrowJSException(mCx, exn); - JS_ClearPendingException(mCx); needToDealWithException = false; } } @@ -270,7 +266,7 @@ CallbackObject::CallSetup::~CallSetup() if (needToDealWithException) { // Either we're supposed to report our exceptions, or we're supposed to - // re-throw them but we failed to JS_GetPendingException. Either way, + // re-throw them but we failed to get the exception value. Either way, // just report the pending exception, if any. // // We don't use nsJSUtils::ReportPendingException here because all it @@ -281,7 +277,9 @@ CallbackObject::CallSetup::~CallSetup() // screw up our compartment, which is exactly what we do not want. // // XXXbz FIXME: bug 979525 means we don't always JS_SaveFrameChain here, - // so we need to go ahead and do that. + // so we need to go ahead and do that. This is also the reason we don't + // just rely on ~AutoJSAPI reporting the exception for us. I think if we + // didn't need to JS_SaveFrameChain here, we could just rely on that. JS::Rooted<JSObject*> oldGlobal(mCx, JS::CurrentGlobalOrNull(mCx)); MOZ_ASSERT(oldGlobal, "How can we not have a global here??"); bool saved = JS_SaveFrameChain(mCx); @@ -292,7 +290,10 @@ CallbackObject::CallSetup::~CallSetup() MOZ_ASSERT(!JS::DescribeScriptedCaller(mCx), "Our comment above about JS_SaveFrameChain having been " "called is a lie?"); - JS_ReportPendingException(mCx); + // Note that we don't JS_ReportPendingException here because we want to + // go through our AutoEntryScript's reporting mechanism instead, since + // it currently owns error reporting. + mAutoEntryScript->ReportException(); } if (saved) { JS_RestoreFrameChain(mCx); diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h index f21e890e8..a5c144acf 100644 --- a/dom/bindings/CallbackObject.h +++ b/dom/bindings/CallbackObject.h @@ -218,7 +218,6 @@ protected: // we should re-throw them. ErrorResult& mErrorResult; const ExceptionHandling mExceptionHandling; - JS::ContextOptions mSavedJSContextOptions; const bool mIsMainThread; }; }; diff --git a/dom/bindings/test/mochitest.ini b/dom/bindings/test/mochitest.ini index 4ea9b0789..0b1118f8c 100644 --- a/dom/bindings/test/mochitest.ini +++ b/dom/bindings/test/mochitest.ini @@ -59,3 +59,4 @@ skip-if = debug == false skip-if = debug == false [test_worker_UnwrapArg.html] [test_crossOriginWindowSymbolAccess.html] +[test_callback_exceptions.html] diff --git a/dom/bindings/test/test_callback_exceptions.html b/dom/bindings/test/test_callback_exceptions.html new file mode 100644 index 000000000..fad6319f7 --- /dev/null +++ b/dom/bindings/test/test_callback_exceptions.html @@ -0,0 +1,17 @@ +<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for ...</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+promise_test(function(t) {
+ var iterator = document.createNodeIterator(document, NodeFilter.SHOW_ALL, JSON.parse);
+ return promise_rejects(t, new SyntaxError,
+ Promise.resolve().then(iterator.nextNode.bind(iterator)));
+}, "Trying to use JSON.parse as filter should throw a catchable SyntaxError exception even when the filter is invoked async");
+
+promise_test(function(t) {
+ return promise_rejects(t, new SyntaxError, Promise.resolve('{').then(JSON.parse));
+}, "Trying to use JSON.parse as a promise callback should allow the next promise to handle the resulting exception.");
+</script>
diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp index 83a32a05b..1be74c627 100644 --- a/dom/jsurl/nsJSProtocolHandler.cpp +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -249,6 +249,10 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel, // http://www.whatwg.org/specs/web-apps/current-work/#javascript-protocol AutoEntryScript entryScript(innerGlobal, true, scriptContext->GetNativeContext()); + // We want to make sure we report any exceptions that happen before we + // return, since whatever happens inside our execution shouldn't affect any + // other scripts that might happen to be running. + entryScript.TakeOwnershipOfErrorReporting(); JSContext* cx = entryScript.cx(); JS::Rooted<JSObject*> globalJSObject(cx, innerGlobal->GetGlobalJSObject()); NS_ENSURE_TRUE(globalJSObject, NS_ERROR_UNEXPECTED); @@ -276,20 +280,13 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel, rv = nsJSUtils::EvaluateString(cx, NS_ConvertUTF8toUTF16(script), globalJSObject, options, evalOptions, &v); - // If there's an error on cx as a result of that call, report - // it now -- either we're just running under the event loop, - // so we shouldn't propagate JS exceptions out of here, or we - // can't be sure that our caller is JS (and if it's not we'll - // lose the error), or it might be JS that then proceeds to - // cause an error of its own (which will also make us lose - // this error). - ::JS_ReportPendingException(cx); - if (NS_FAILED(rv) || !(v.isString() || v.isUndefined())) { return NS_ERROR_MALFORMED_URI; } else if (v.isUndefined()) { return NS_ERROR_DOM_RETVAL_UNDEFINED; } else { + MOZ_ASSERT(rv != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW, + "How did we get a non-undefined return value?"); nsAutoJSString result; if (!result.init(cx, v)) { return NS_ERROR_OUT_OF_MEMORY; diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index 935044697..b04e4632f 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -1508,6 +1508,7 @@ _evaluate(NPP npp, NPObject* npobj, NPString *script, NPVariant *result) } dom::AutoEntryScript aes(win); + aes.TakeOwnershipOfErrorReporting(); JSContext* cx = aes.cx(); JS::Rooted<JSObject*> obj(cx, nsNPObjWrapper::GetNewOrUsed(npp, cx, npobj)); diff --git a/dom/webidl/DOMException.webidl b/dom/webidl/DOMException.webidl index 95a6250d8..f83f05c69 100644 --- a/dom/webidl/DOMException.webidl +++ b/dom/webidl/DOMException.webidl @@ -52,8 +52,6 @@ interface ExceptionMembers // this was only ever usefully available to chrome JS. [ChromeOnly] readonly attribute StackFrame? location; - // An inner exception that triggered this, if available. - readonly attribute nsISupports? inner; // Arbitary data for the implementation. readonly attribute nsISupports? data; diff --git a/dom/xbl/nsXBLProtoImplField.cpp b/dom/xbl/nsXBLProtoImplField.cpp index 8c2295bbc..a76fd0b16 100644 --- a/dom/xbl/nsXBLProtoImplField.cpp +++ b/dom/xbl/nsXBLProtoImplField.cpp @@ -405,6 +405,7 @@ nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode, // We are going to run script via EvaluateString, so we need a script entry // point, but as this is XBL related it does not appear in the HTML spec. AutoEntryScript entryScript(globalObject, true); + entryScript.TakeOwnershipOfErrorReporting(); JSContext* cx = entryScript.cx(); NS_ASSERTION(!::JS_IsExceptionPending(cx), @@ -440,6 +441,12 @@ nsXBLProtoImplField::InstallField(JS::Handle<JSObject*> aBoundNode, return rv; } + if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW) { + // Report the exception now, before we try using the JSContext for + // the JS_DefineUCProperty call. + entryScript.ReportException(); + } + // Now, enter the node's compartment, wrap the eval result, and define it on // the bound node. JSAutoCompartment ac2(cx, aBoundNode); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 9b1a3d709..e703b4c13 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -229,6 +229,14 @@ ReportError(JSContext* cx, const char* message, JSErrorReport* reportp, if (js_ErrorToException(cx, message, reportp, callback, userRef)) { return; } + + /* + * The AutoJSAPI error reporter only allows warnings to be reported so + * just ignore this error rather than try to report it. + */ + if (cx->options().autoJSAPIOwnsErrorReporting() && !JSREPORT_IS_WARNING(reportp->flags)) + return; + } /* diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp index f49140e8f..06b20042b 100644 --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -607,6 +607,12 @@ js_ErrorToException(JSContext* cx, const char* message, JSErrorReport* reportp, static bool IsDuckTypedErrorObject(JSContext* cx, HandleObject exnObject, const char** filename_strp) { + /* + * This function is called from ErrorReport::init and so should not generate + * any new exceptions. + */ + AutoClearPendingException acpe(cx); + bool found; if (!JS_HasProperty(cx, exnObject, js_message_str, &found) || !found) return false; diff --git a/js/src/jsexn.h b/js/src/jsexn.h index 24010960f..376a191de 100644 --- a/js/src/jsexn.h +++ b/js/src/jsexn.h @@ -116,4 +116,18 @@ ExnTypeFromProtoKey(JSProtoKey key) return type; } +class AutoClearPendingException +{ + JSContext *cx; + + public: + explicit AutoClearPendingException(JSContext *cxArg) + : cx(cxArg) + { } + + ~AutoClearPendingException() { + cx->clearPendingException(); + } +}; + #endif /* jsexn_h */ diff --git a/js/xpconnect/idl/xpcexception.idl b/js/xpconnect/idl/xpcexception.idl index 7587ce990..beaeb1bc5 100644 --- a/js/xpconnect/idl/xpcexception.idl +++ b/js/xpconnect/idl/xpcexception.idl @@ -7,7 +7,7 @@ #include "nsISupports.idl" #include "nsIException.idl" -[scriptable, uuid(dd250248-2586-4ec1-a68f-8d14ef452517)] +[scriptable, uuid(875e6645-e762-4da6-9ec8-bf19ab0050df)] interface nsIXPCException : nsIException { // inherits methods from nsIException @@ -16,8 +16,7 @@ interface nsIXPCException : nsIException in nsresult aResult, in AUTF8String aName, in nsIStackFrame aLocation, - in nsISupports aData, - in nsIException aInner); + in nsISupports aData); }; /* this goes into the C++ header verbatim. */ diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 7a7270fa1..d8c073923 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -529,6 +529,12 @@ * the second assignment throws NS_SUCCESS_DOM_NO_OPERATION. */ ERROR(NS_SUCCESS_DOM_NO_OPERATION, SUCCESS(1)), + + /* + * A success code that indicates that evaluating a string of JS went + * just fine except it threw an exception. + */ + ERROR(NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW, SUCCESS(2)), #undef MODULE diff --git a/xpcom/base/nsIException.idl b/xpcom/base/nsIException.idl index d770382a6..1f9702d2e 100644 --- a/xpcom/base/nsIException.idl +++ b/xpcom/base/nsIException.idl @@ -12,7 +12,7 @@ [ptr] native JSContext(JSContext); -[scriptable, uuid(4ed5cd87-401a-425a-8d8d-c28fbc1e88b2)] +[scriptable, uuid(4371b5bf-6845-487f-8d9d-3f1e4a9badd2)] interface nsIStackFrame : nsISupports { // see nsIProgrammingLanguage for list of language consts @@ -69,8 +69,6 @@ interface nsIException : nsISupports // A stack trace, if available. readonly attribute nsIStackFrame location; - // An inner exception that triggered this, if available. - readonly attribute nsIException inner; // Arbitary data for the implementation. readonly attribute nsISupports data; |