Bug 905392 - Need way to throw web-console-visible exception-message from JS-implemented webidl object. r=bz.

This commit is contained in:
Peter Van der Beken 2013-09-03 14:01:53 +02:00
parent 0cc3bcb43d
commit b174788298
7 changed files with 220 additions and 59 deletions

View File

@ -26,6 +26,8 @@
#include "nsPrintfCString.h"
#include "prprf.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/DOMErrorBinding.h"
#include "mozilla/dom/HTMLObjectElement.h"
#include "mozilla/dom/HTMLObjectElementBinding.h"
#include "mozilla/dom/HTMLSharedObjectElement.h"
@ -185,6 +187,41 @@ ErrorResult::ReportJSException(JSContext* cx)
JS_RemoveValueRoot(cx, &mJSException);
}
void
ErrorResult::ReportJSExceptionFromJSImplementation(JSContext* aCx)
{
MOZ_ASSERT(!mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to handle JS exceptions?");
dom::DOMError* domError;
nsresult rv = UNWRAP_OBJECT(DOMError, aCx, &mJSException.toObject(),
domError);
if (NS_FAILED(rv)) {
// Unwrapping really shouldn't fail here, if mExceptionHandling is set to
// eRethrowContentExceptions then the CallSetup destructor only stores an
// exception if it unwraps to DOMError. If we reach this then either
// mExceptionHandling wasn't set to eRethrowContentExceptions and we
// shouldn't be calling ReportJSExceptionFromJSImplementation or something
// went really wrong.
NS_RUNTIMEABORT("We stored a non-DOMError exception!");
}
nsString message;
domError->GetMessage(message);
JSErrorReport errorReport;
memset(&errorReport, 0, sizeof(JSErrorReport));
errorReport.errorNumber = JSMSG_USER_DEFINED_ERROR;
errorReport.ucmessage = message.get();
errorReport.exnType = JSEXN_ERR;
JS_ThrowReportedError(aCx, nullptr, &errorReport);
JS_RemoveValueRoot(aCx, &mJSException);
// We no longer have a useful exception but we do want to signal that an error
// occured.
mResult = NS_ERROR_FAILURE;
}
void
ErrorResult::StealJSException(JSContext* cx,
JS::MutableHandle<JS::Value> value)

View File

@ -85,7 +85,7 @@ Throw(JSContext* cx, nsresult rv)
return false;
}
template<bool mainThread>
template<bool mainThread, bool reportJSContentExceptions = false>
inline bool
ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv,
const char* ifaceName,
@ -96,7 +96,11 @@ ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv,
return false;
}
if (rv.IsJSException()) {
rv.ReportJSException(cx);
if (reportJSContentExceptions) {
rv.ReportJSExceptionFromJSImplementation(cx);
} else {
rv.ReportJSException(cx);
}
return false;
}
if (rv.IsNotEnoughArgsError()) {
@ -183,8 +187,8 @@ IsDOMObject(JSObject* obj)
}
#define UNWRAP_OBJECT(Interface, cx, obj, value) \
UnwrapObject<prototypes::id::Interface, \
mozilla::dom::Interface##Binding::NativeType>(cx, obj, value)
mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface, \
mozilla::dom::Interface##Binding::NativeType>(cx, obj, value)
// Some callers don't want to set an exception when unwrapping fails
// (for example, overload resolution uses unwrapping to tell what sort

View File

@ -5,6 +5,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/CallbackObject.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/DOMErrorBinding.h"
#include "jsfriendapi.h"
#include "nsIScriptGlobalObject.h"
#include "nsIXPConnect.h"
@ -40,8 +43,10 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_END
CallbackObject::CallSetup::CallSetup(JS::Handle<JSObject*> aCallback,
ErrorResult& aRv,
ExceptionHandling aExceptionHandling)
ExceptionHandling aExceptionHandling,
JSCompartment* aCompartment)
: mCx(nullptr)
, mCompartment(aCompartment)
, mErrorResult(aRv)
, mExceptionHandling(aExceptionHandling)
{
@ -123,25 +128,55 @@ CallbackObject::CallSetup::CallSetup(JS::Handle<JSObject*> aCallback,
mCx = cx;
// Make sure the JS engine doesn't report exceptions we want to re-throw
if (mExceptionHandling == eRethrowExceptions) {
if (mExceptionHandling == eRethrowContentExceptions ||
mExceptionHandling == eRethrowExceptions) {
mSavedJSContextOptions = JS_GetOptions(cx);
JS_SetOptions(cx, mSavedJSContextOptions | JSOPTION_DONT_REPORT_UNCAUGHT);
}
}
bool
CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
{
if (mExceptionHandling == eRethrowExceptions) {
return true;
}
MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions);
// For eRethrowContentExceptions we only want to throw an exception if the
// object that was thrown is a DOMError object in the caller compartment
// (which we stored in mCompartment).
if (!aException.isObject()) {
return false;
}
JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false);
if (js::GetObjectCompartment(obj) != mCompartment) {
return false;
}
DOMError* domError;
return NS_SUCCEEDED(UNWRAP_OBJECT(DOMError, mCx, obj, domError));
}
CallbackObject::CallSetup::~CallSetup()
{
// First things first: if we have a JSContext, report any pending
// errors on it, unless we were told to re-throw them.
if (mCx) {
bool dealtWithPendingException = false;
if (mExceptionHandling == eRethrowExceptions) {
if (mExceptionHandling == eRethrowContentExceptions ||
mExceptionHandling == eRethrowExceptions) {
// Restore the old context options
JS_SetOptions(mCx, mSavedJSContextOptions);
mErrorResult.MightThrowJSException();
if (JS_IsExceptionPending(mCx)) {
JS::Rooted<JS::Value> exn(mCx);
if (JS_GetPendingException(mCx, exn.address())) {
if (JS_GetPendingException(mCx, exn.address()) &&
ShouldRethrowException(exn)) {
mErrorResult.ThrowJSException(mCx, exn);
JS_ClearPendingException(mCx);
dealtWithPendingException = true;

View File

@ -78,7 +78,13 @@ public:
}
enum ExceptionHandling {
// Report any exception and don't throw it to the caller code.
eReportExceptions,
// Throw an exception to the caller code if the thrown exception is a
// binding object for a DOMError from the caller's scope, otherwise report
// it.
eRethrowContentExceptions,
// Throw any exception to the caller code.
eRethrowExceptions
};
@ -118,8 +124,11 @@ protected:
* non-null.
*/
public:
// If aExceptionHandling == eRethrowContentExceptions then aCompartment
// needs to be set to the caller's compartment.
CallSetup(JS::Handle<JSObject*> aCallable, ErrorResult& aRv,
ExceptionHandling aExceptionHandling);
ExceptionHandling aExceptionHandling,
JSCompartment* aCompartment = nullptr);
~CallSetup();
JSContext* GetContext() const
@ -131,9 +140,15 @@ protected:
// We better not get copy-constructed
CallSetup(const CallSetup&) MOZ_DELETE;
bool ShouldRethrowException(JS::Handle<JS::Value> aException);
// Members which can go away whenever
JSContext* mCx;
// Caller's compartment. This will only have a sensible value if
// mExceptionHandling == eRethrowContentExceptions.
JSCompartment* mCompartment;
// And now members whose construction/destruction order we need to control.
// Put our nsAutoMicrotask first, so it gets destroyed after everything else

View File

@ -4807,15 +4807,26 @@ if (global.Failed()) {
if needsCx and not (static and descriptor.workers):
argsPre.append("cx")
needsUnwrap = isConstructor
if needScopeObject(returnType, arguments, self.extendedAttributes,
descriptor, descriptor.wrapperCache,
not descriptor.interface.isJSImplemented()):
needsUnwrap = False
if isConstructor:
needsUnwrap = True
needsUnwrappedVar = False
unwrappedVar = "obj"
elif descriptor.interface.isJSImplemented():
needsUnwrap = True
needsUnwrappedVar = True
argsPre.append("js::GetObjectCompartment(unwrappedObj.empty() ? obj : unwrappedObj.ref())")
elif needScopeObject(returnType, arguments, self.extendedAttributes,
descriptor, descriptor.wrapperCache, True):
needsUnwrap = True
needsUnwrappedVar = True
argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()")
if needsUnwrap and needsUnwrappedVar:
# We cannot assign into obj because it's a Handle, not a
# MutableHandle, so we need a separate Rooted.
cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;"))
argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()")
needsUnwrap = True
unwrappedVar = "unwrappedObj.ref()"
if idlNode.isMethod() and idlNode.isLegacycaller():
# If we can have legacycaller with identifier, we can't
@ -4844,7 +4855,7 @@ if (global.Failed()) {
if needsUnwrap:
# Something depends on having the unwrapped object, so unwrap it now.
xraySteps = []
if not isConstructor:
if needsUnwrappedVar:
xraySteps.append(
CGGeneric("unwrappedObj.construct(cx, obj);"))
@ -4854,7 +4865,7 @@ if (global.Failed()) {
CGGeneric(string.Template("""${obj} = js::CheckedUnwrap(${obj});
if (!${obj}) {
return false;
}""").substitute({ 'obj' : 'obj' if isConstructor else 'unwrappedObj.ref()' })))
}""").substitute({ 'obj' : unwrappedVar })))
if isConstructor:
# If we're called via an xray, we need to enter the underlying
# object's compartment and then wrap up all of our arguments into
@ -4915,8 +4926,12 @@ if (!${obj}) {
self.idlNode.identifier.name))
def getErrorReport(self):
return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");'
jsImplemented = ""
if self.descriptor.interface.isJSImplemented():
jsImplemented = ", true"
return CGGeneric('return ThrowMethodFailedWithDetails<%s%s>(cx, rv, "%s", "%s");'
% (toStringBool(not self.descriptor.workers),
jsImplemented,
self.descriptor.interface.identifier.name,
self.idlNode.identifier.name))
@ -9509,9 +9524,38 @@ class CGExampleRoot(CGThing):
def jsImplName(name):
return name + "JSImpl"
class CGJSImplMethod(CGNativeMember):
class CGJSImplMember(CGNativeMember):
"""
Base class for generating code for the members of the implementation class
for a JS-implemented WebIDL interface.
"""
def __init__(self, descriptorProvider, member, name, signature,
extendedAttrs, breakAfter=True, passJSBitsAsNeeded=True,
visibility="public", jsObjectsArePtr=False,
variadicIsSequence=False):
CGNativeMember.__init__(self, descriptorProvider, member, name,
signature, extendedAttrs, breakAfter=breakAfter,
passJSBitsAsNeeded=passJSBitsAsNeeded,
visibility=visibility,
jsObjectsArePtr=jsObjectsArePtr,
variadicIsSequence=variadicIsSequence)
self.body = self.getImpl()
def getArgs(self, returnType, argList):
args = CGNativeMember.getArgs(self, returnType, argList)
args.insert(0, Argument("JSCompartment*", "aCompartment"))
return args
class CGJSImplMethod(CGJSImplMember):
"""
Class for generating code for the methods for a JS-implemented WebIDL
interface.
"""
def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
CGNativeMember.__init__(self, descriptor, method,
self.signature = signature
self.descriptor = descriptor
self.isConstructor = isConstructor
CGJSImplMember.__init__(self, descriptor, method,
CGSpecializedMethod.makeNativeName(descriptor,
method),
signature,
@ -9519,29 +9563,29 @@ class CGJSImplMethod(CGNativeMember):
breakAfter=breakAfter,
variadicIsSequence=True,
passJSBitsAsNeeded=False)
self.signature = signature
self.descriptor = descriptor
if isConstructor:
self.body = self.getConstructorImpl()
else:
self.body = self.getImpl()
def getArgs(self, returnType, argList):
if self.isConstructor:
# Skip the JSCompartment bits for constructors; it's handled
# manually in getImpl.
return CGNativeMember.getArgs(self, returnType, argList)
return CGJSImplMember.getArgs(self, returnType, argList)
def getImpl(self):
callbackArgs = [arg.name for arg in self.getArgs(self.signature[0], self.signature[1])]
return 'return mImpl->%s(%s);' % (self.name, ", ".join(callbackArgs))
args = self.getArgs(self.signature[0], self.signature[1])
if not self.isConstructor:
return 'return mImpl->%s(%s);' % (self.name, ", ".join(arg.name for arg in args))
def getConstructorImpl(self):
assert self.descriptor.interface.isJSImplemented()
if self.name != 'Constructor':
raise TypeError("Named constructors are not supported for JS implemented WebIDL. See bug 851287.")
if len(self.signature[1]) != 0:
args = self.getArgs(self.signature[0], self.signature[1])
# The first two arguments to the constructor implementation are not
# arguments to the WebIDL constructor, so don't pass them to __Init()
assert args[0].argType == 'const GlobalObject&'
assert args[1].argType == 'JSContext*'
args = args[2:]
constructorArgs = [arg.name for arg in args]
constructorArgs = [arg.name for arg in args[2:]]
constructorArgs.insert(0, "js::GetObjectCompartment(scopeObj)")
initCall = """
// Wrap the object before calling __Init so that __DOM_IMPL__ is available.
nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(window);
@ -9588,24 +9632,31 @@ def callbackGetterName(attr):
def callbackSetterName(attr):
return "Set" + MakeNativeName(attr.identifier.name)
class CGJSImplGetter(CGNativeMember):
class CGJSImplGetter(CGJSImplMember):
"""
Class for generating code for the getters of attributes for a JS-implemented
WebIDL interface.
"""
def __init__(self, descriptor, attr):
CGNativeMember.__init__(self, descriptor, attr,
CGJSImplMember.__init__(self, descriptor, attr,
CGSpecializedGetter.makeNativeName(descriptor,
attr),
(attr.type, []),
descriptor.getExtendedAttributes(attr,
getter=True),
passJSBitsAsNeeded=False)
self.body = self.getImpl()
def getImpl(self):
callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])]
return 'return mImpl->%s(%s);' % (callbackGetterName(self.member), ", ".join(callbackArgs))
class CGJSImplSetter(CGNativeMember):
class CGJSImplSetter(CGJSImplMember):
"""
Class for generating code for the setters of attributes for a JS-implemented
WebIDL interface.
"""
def __init__(self, descriptor, attr):
CGNativeMember.__init__(self, descriptor, attr,
CGJSImplMember.__init__(self, descriptor, attr,
CGSpecializedSetter.makeNativeName(descriptor,
attr),
(BuiltinTypes[IDLBuiltinType.Types.void],
@ -9613,7 +9664,6 @@ class CGJSImplSetter(CGNativeMember):
descriptor.getExtendedAttributes(attr,
setter=True),
passJSBitsAsNeeded=False)
self.body = self.getImpl()
def getImpl(self):
callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void],
@ -9929,11 +9979,13 @@ class FakeMember():
return None
class CallbackMember(CGNativeMember):
def __init__(self, sig, name, descriptorProvider, needThisHandling):
def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
"""
needThisHandling is True if we need to be able to accept a specified
thisObj, False otherwise.
"""
assert not rethrowContentException or not needThisHandling
self.retvalType = sig[0]
self.originalSig = sig
args = sig[1]
@ -9951,6 +10003,7 @@ class CallbackMember(CGNativeMember):
# If needThisHandling, we generate ourselves as private and the caller
# will handle generating public versions that handle the "this" stuff.
visibility = "private" if needThisHandling else "public"
self.rethrowContentException = rethrowContentException
# We don't care, for callback codegen, whether our original member was
# a method or attribute or whatnot. Just always pass FakeMember()
# here.
@ -10109,8 +10162,12 @@ class CallbackMember(CGNativeMember):
if not self.needThisHandling:
# Since we don't need this handling, we're the actual method that
# will be called, so we need an aRethrowExceptions argument.
return args + [Argument("ExceptionHandling", "aExceptionHandling",
"eReportExceptions")]
if self.rethrowContentException:
args.insert(0, Argument("JSCompartment*", "aCompartment"))
else:
args.append(Argument("ExceptionHandling", "aExceptionHandling",
"eReportExceptions"))
return args
# We want to allow the caller to pass in a "this" object, as
# well as a JSContext.
return [Argument("JSContext*", "cx"),
@ -10120,13 +10177,22 @@ class CallbackMember(CGNativeMember):
if self.needThisHandling:
# It's been done for us already
return ""
callSetup = "CallSetup s(CallbackPreserveColor(), aRv"
if self.rethrowContentException:
# getArgs doesn't add the aExceptionHandling argument but does add
# aCompartment for us.
callSetup += ", eRethrowContentExceptions, aCompartment"
else:
callSetup += ", aExceptionHandling"
callSetup += ");"
return string.Template(
"CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n"
"${callSetup}\n"
"JSContext* cx = s.GetContext();\n"
"if (!cx) {\n"
" aRv.Throw(NS_ERROR_UNEXPECTED);\n"
" return${errorReturn};\n"
"}\n").substitute({
"callSetup": callSetup,
"errorReturn" : self.getDefaultRetval(),
})
@ -10149,9 +10215,9 @@ class CallbackMember(CGNativeMember):
idlObject.location))
class CallbackMethod(CallbackMember):
def __init__(self, sig, name, descriptorProvider, needThisHandling):
def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
CallbackMember.__init__(self, sig, name, descriptorProvider,
needThisHandling)
needThisHandling, rethrowContentException)
def getRvalDecl(self):
return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
@ -10189,10 +10255,10 @@ class CallbackOperationBase(CallbackMethod):
"""
Common class for implementing various callback operations.
"""
def __init__(self, signature, jsName, nativeName, descriptor, singleOperation):
def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False):
self.singleOperation = singleOperation
self.methodName = jsName
CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation)
CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException)
def getThisObj(self):
if not self.singleOperation:
@ -10232,7 +10298,8 @@ class CallbackOperation(CallbackOperationBase):
jsName = method.identifier.name
CallbackOperationBase.__init__(self, signature,
jsName, MakeNativeName(jsName),
descriptor, descriptor.interface.isSingleOperationInterface())
descriptor, descriptor.interface.isSingleOperationInterface(),
rethrowContentException=descriptor.interface.isJSImplemented())
class CallbackGetter(CallbackMember):
def __init__(self, attr, descriptor):
@ -10242,7 +10309,8 @@ class CallbackGetter(CallbackMember):
(attr.type, []),
callbackGetterName(attr),
descriptor,
needThisHandling=False)
needThisHandling=False,
rethrowContentException=descriptor.interface.isJSImplemented())
def getRvalDecl(self):
return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
@ -10267,7 +10335,8 @@ class CallbackSetter(CallbackMember):
[FakeArgument(attr.type, attr)]),
callbackSetterName(attr),
descriptor,
needThisHandling=False)
needThisHandling=False,
rethrowContentException=descriptor.interface.isJSImplemented())
def getRvalDecl(self):
# We don't need an rval
@ -10296,7 +10365,7 @@ class CGJSImplInitOperation(CallbackOperationBase):
def __init__(self, sig, descriptor):
assert sig in descriptor.interface.ctor().signatures()
CallbackOperationBase.__init__(self, (BuiltinTypes[IDLBuiltinType.Types.void], sig[1]),
"__init", "__Init", descriptor, False)
"__init", "__Init", descriptor, False, True)
class GlobalGenRoots():
"""

View File

@ -70,11 +70,14 @@ public:
// Facilities for throwing a preexisting JS exception value via this
// ErrorResult. The contract is that any code which might end up calling
// ThrowJSException() must call MightThrowJSException() even if no exception
// is being thrown. Code that would call ReportJSException or
// is being thrown. Code that would call ReportJSException* or
// StealJSException as needed must first call WouldReportJSException even if
// this ErrorResult has not failed.
void ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
void ReportJSException(JSContext* cx);
// Used to implement throwing exceptions from the JS implementation of
// bindings to callers of the binding.
void ReportJSExceptionFromJSImplementation(JSContext* aCx);
bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; }
void ThrowNotEnoughArgsError() { mResult = NS_ERROR_XPC_NOT_ENOUGH_ARGS; }

View File

@ -359,30 +359,28 @@ RTCPeerConnection.prototype = {
* ErrorMsg is passed in to detail which array-entry failed, if any.
*/
_mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
var errorCtor = this._win.DOMError;
function nicerNewURI(uriStr, errorMsg) {
let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
try {
return ios.newURI(uriStr, null, null);
} catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
throw new Components.Exception(errorMsg + " - malformed URI: " + uriStr,
Cr.NS_ERROR_MALFORMED_URI);
throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr);
}
}
function mustValidateServer(server) {
let url = nicerNewURI(server.url, errorMsg);
if (url.scheme in { turn:1, turns:1 }) {
if (!server.username) {
throw new Components.Exception(errorMsg + " - missing username: " +
server.url, Cr.NS_ERROR_MALFORMED_URI);
throw new errorCtor("", errorMsg + " - missing username: " + server.url);
}
if (!server.credential) {
throw new Components.Exception(errorMsg + " - missing credential: " +
server.url, Cr.NS_ERROR_MALFORMED_URI);
throw new errorCtor("", errorMsg + " - missing credential: " +
server.url);
}
}
else if (!(url.scheme in { stun:1, stuns:1 })) {
throw new Components.Exception(errorMsg + " - improper scheme: " + url.scheme,
Cr.NS_ERROR_MALFORMED_URI);
throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme);
}
}
if (rtcConfig.iceServers) {