Bug 1070842 - Introduce an API on AutoJSAPI to allow Gecko to handle exceptions without meddling from the JS engine. r=bz

This commit is contained in:
Bobby Holley 2014-09-29 15:34:21 +02:00
parent a56064b5df
commit a16cec5899
2 changed files with 127 additions and 0 deletions

View File

@ -234,9 +234,53 @@ FindJSContext(nsIGlobalObject* aGlobalObject)
AutoJSAPI::AutoJSAPI()
: mCx(nullptr)
, mOwnErrorReporting(false)
, mOldDontReportUncaught(false)
{
}
AutoJSAPI::~AutoJSAPI()
{
if (mOwnErrorReporting) {
MOZ_ASSERT(NS_IsMainThread(), "See corresponding assertion in TakeOwnershipOfErrorReporting()");
JS::ContextOptionsRef(cx()).setDontReportUncaught(mOldDontReportUncaught);
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);
const char *category = nsContentUtils::IsCallerChrome() ? "chrome javascript"
: "content javascript";
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(), category,
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");
}
}
}
if (mOldErrorReporter.isSome()) {
JS_SetErrorReporter(JS_GetRuntime(cx()), mOldErrorReporter.value());
}
}
void
AutoJSAPI::InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread)
{
@ -253,11 +297,19 @@ AutoJSAPI::InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread)
} else {
mAutoNullableCompartment.emplace(mCx, aGlobal);
}
if (aIsMainThread) {
JSRuntime* rt = JS_GetRuntime(aCx);
mOldErrorReporter.emplace(JS_GetErrorReporter(rt));
JS_SetErrorReporter(rt, xpc::SystemErrorReporter);
}
}
AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
bool aIsMainThread,
JSContext* aCx)
: mOwnErrorReporting(false)
, mOldDontReportUncaught(false)
{
MOZ_ASSERT(aGlobalObject);
MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
@ -352,6 +404,49 @@ AutoJSAPI::InitWithLegacyErrorReporting(nsGlobalWindow* aWindow)
return InitWithLegacyErrorReporting(static_cast<nsIGlobalObject*>(aWindow));
}
// Even with dontReportUncaught, the JS engine still sends warning reports
// to the JSErrorReporter as soon as they are generated. These go directly to
// the console, so we can handle them easily here.
//
// Eventually, SpiderMonkey will have a special-purpose callback for warnings only.
void
WarningOnlyErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aRep)
{
MOZ_ASSERT(JSREPORT_IS_WARNING(aRep->flags));
nsRefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
const char* category = nsContentUtils::IsCallerChrome() ? "chrome javascript"
: "content javascript";
nsPIDOMWindow* win = xpc::WindowGlobalOrNull(JS::CurrentGlobalOrNull(aCx));
xpcReport->Init(aRep, aMessage, category, win ? win->WindowID() : 0);
xpcReport->LogToConsole();
}
void
AutoJSAPI::TakeOwnershipOfErrorReporting()
{
MOZ_ASSERT(NS_IsMainThread(), "Can't own error reporting off-main-thread yet");
MOZ_ASSERT(!mOwnErrorReporting);
mOwnErrorReporting = true;
JSRuntime *rt = JS_GetRuntime(cx());
mOldDontReportUncaught = JS::ContextOptionsRef(cx()).dontReportUncaught();
JS::ContextOptionsRef(cx()).setDontReportUncaught(true);
JS_SetErrorReporter(rt, WarningOnlyErrorReporter);
}
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;
}
AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
bool aIsMainThread,
JSContext* aCx)

View File

@ -203,6 +203,8 @@ public:
// accessing the JSContext through cx().
AutoJSAPI();
~AutoJSAPI();
// This uses the SafeJSContext (or worker equivalent), and enters a null
// compartment, so that the consumer is forced to select a compartment to
// enter before manipulating objects.
@ -253,6 +255,31 @@ public:
bool CxPusherIsStackTop() const { return mCxPusher->IsStackTop(); }
// We're moving towards a world where the AutoJSAPI always handles
// exceptions that bubble up from the JS engine. In order to make this
// process incremental, we allow consumers to opt-in to the new behavior
// while keeping the old behavior as the default.
void TakeOwnershipOfErrorReporting();
bool OwnsErrorReporting() { return mOwnErrorReporting; }
bool HasException() const {
MOZ_ASSERT(CxPusherIsStackTop());
return JS_IsExceptionPending(cx());
};
// Transfers ownership of the current exception from the JS engine to the
// caller. 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 StealException(JS::MutableHandle<JS::Value> aVal);
void ClearException() {
MOZ_ASSERT(CxPusherIsStackTop());
JS_ClearPendingException(cx());
}
protected:
// Protected constructor, allowing subclasses to specify a particular cx to
// be used. This constructor initialises the AutoJSAPI, so Init must NOT be
@ -266,6 +293,11 @@ private:
mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
JSContext *mCx;
// Track state between the old and new error reporting modes.
bool mOwnErrorReporting;
bool mOldDontReportUncaught;
Maybe<JSErrorReporter> mOldErrorReporter;
void InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread);
};