Bug 1441333: Part 3 - Add helper to create a JS error with a saved stack. r=bz

There's no standard way to create a JS error with full stack and location
information from a saved stack. Since we need this functionality in order to
reject promises with useful Error objects, this patch adds a simple helper to
make that possible.

MozReview-Commit-ID: FyGuo4UjfsQ

--HG--
extra : rebase_source : 65ef11c56f23e04ea5eeb87b972bfc8e4867fdd2
This commit is contained in:
Kris Maglione 2018-03-01 14:00:00 -08:00
Родитель 597e2b745d
Коммит 69844a6fbb
4 изменённых файлов: 100 добавлений и 0 удалений

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

@ -679,5 +679,68 @@ ChromeUtils::GetCallerLocation(const GlobalObject& aGlobal, nsIPrincipal* aPrinc
aRetval.set(js::GetFirstSubsumedSavedFrame(cx, principals, frame, kSkipSelfHosted));
}
/* static */ void
ChromeUtils::CreateError(const GlobalObject& aGlobal, const nsAString& aMessage,
JS::Handle<JSObject*> aStack,
JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv)
{
if (aStack && !JS::IsSavedFrame(aStack)) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
JSContext* cx = aGlobal.Context();
auto cleanup = MakeScopeExit([&]() {
aRv.NoteJSContextException(cx);
});
JS::RootedObject retVal(cx);
{
JS::RootedString fileName(cx, JS_GetEmptyString(cx));
uint32_t line = 0;
uint32_t column = 0;
Maybe<JSAutoCompartment> ac;
JS::RootedObject stack(cx);
if (aStack) {
stack = UncheckedUnwrap(aStack);
ac.emplace(cx, stack);
if (JS::GetSavedFrameLine(cx, stack, &line) != JS::SavedFrameResult::Ok ||
JS::GetSavedFrameColumn(cx, stack, &column) != JS::SavedFrameResult::Ok ||
JS::GetSavedFrameSource(cx, stack, &fileName) != JS::SavedFrameResult::Ok) {
return;
}
}
JS::RootedString message(cx);
{
JS::RootedValue msgVal(cx);
if (!xpc::NonVoidStringToJsval(cx, aMessage, &msgVal)) {
return;
}
message = msgVal.toString();
}
JS::Rooted<JS::Value> err(cx);
if (!JS::CreateError(cx, JSEXN_ERR, stack,
fileName, line, column,
nullptr, message, &err)) {
return;
}
MOZ_ASSERT(err.isObject());
retVal = &err.toObject();
}
if (aStack && !JS_WrapObject(cx, &retVal)) {
return;
}
cleanup.release();
aRetVal.set(retVal);
}
} // namespace dom
} // namespace mozilla

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

@ -170,6 +170,11 @@ public:
static void
GetCallerLocation(const GlobalObject& global, nsIPrincipal* principal,
JS::MutableHandle<JSObject*> aRetval);
static void
CreateError(const GlobalObject& global, const nsAString& message,
JS::Handle<JSObject*> stack,
JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv);
};
} // namespace dom

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

@ -301,6 +301,15 @@ partial namespace ChromeUtils {
* exists on the call stack, returns null.
*/
object? getCallerLocation(Principal principal);
/**
* Creates a JS Error object with the given message and stack.
*
* If a stack object is provided, the error object is created in the global
* that it belongs to.
*/
[Throws]
object createError(DOMString message, optional object? stack = null);
};
/**

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

@ -58,4 +58,27 @@ add_task(async function() {
Assert.throws(() => { Cu.reportError("Meh", {}); },
err => err.result == Cr.NS_ERROR_INVALID_ARG,
"reportError should throw when passed a non-SavedFrame object");
// createError
Assert.throws(() => { ChromeUtils.createError("Meh", {}); },
err => err.result == Cr.NS_ERROR_INVALID_ARG,
"createError should throw when passed a non-SavedFrame object");
let cloned = Cu.cloneInto(frame, sandbox);
let error = ChromeUtils.createError("Meh", cloned);
equal(String(cloned), String(frame),
"Cloning a SavedStack preserves its stringification");
equal(Cu.getGlobalForObject(error), sandbox,
"createError creates errors in the global of the SavedFrame");
equal(error.stack, String(cloned),
"createError creates errors with the correct stack");
equal(error.message, "Meh", "Error message");
equal(error.fileName, "thing.js", "Error filename");
equal(error.lineNumber, 5, "Error line");
equal(error.columnNumber, 14, "Error column");
});