diff options
author | wolfbeast <mcwerewolf@wolfbeast.com> | 2019-12-19 03:05:45 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@wolfbeast.com> | 2019-12-19 03:05:45 +0100 |
commit | 33cef3b9ce1c443ceaf326abac34feb828203502 (patch) | |
tree | a9c5fce75bb1a120925c27b1de4461403213794d | |
parent | 19ca30d1895f03e489972873a54417abf0476556 (diff) | |
download | uxp-33cef3b9ce1c443ceaf326abac34feb828203502.tar.gz |
Issue mcp-graveyard/UXP#1322 - Part 3: Remove DOM promise interface gunk
Based on work by Boris Zbarsky
-rw-r--r-- | dom/bindings/Bindings.conf | 4 | ||||
-rw-r--r-- | dom/bindings/Codegen.py | 448 | ||||
-rw-r--r-- | dom/bindings/Configuration.py | 7 | ||||
-rw-r--r-- | dom/bindings/Errors.msg | 5 | ||||
-rw-r--r-- | dom/bindings/ToJSValue.cpp | 2 | ||||
-rw-r--r-- | dom/bindings/TypedArray.h | 2 | ||||
-rw-r--r-- | dom/bindings/parser/WebIDL.py | 94 | ||||
-rw-r--r-- | dom/bindings/parser/tests/test_distinguishability.py | 1 | ||||
-rw-r--r-- | dom/bindings/parser/tests/test_promise.py | 4 | ||||
-rw-r--r-- | dom/promise/Promise.cpp | 11 | ||||
-rw-r--r-- | dom/promise/Promise.h | 4 | ||||
-rw-r--r-- | dom/webidl/Promise.webidl | 17 | ||||
-rw-r--r-- | dom/webidl/TestInterfaceJS.webidl | 2 |
13 files changed, 287 insertions, 314 deletions
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 6f9733c5fb..b00af20851 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -694,10 +694,6 @@ DOMInterfaces = { 'headerFile': 'nsGeolocation.h' }, -'Promise': { - 'implicitJSContext': [ 'then', 'catch' ], -}, - 'PromiseDebugging': { 'concrete': False, }, diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 9cde82df93..6b23e8225c 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1135,6 +1135,12 @@ class CGHeaders(CGWrapper): declareIncludes.add("mozilla/dom/Date.h") else: bindingHeaders.add("mozilla/dom/Date.h") + elif unrolled.isPromise(): + # See comment in the isInterface() case for why we add + # Promise.h to headerSet, not bindingHeaders. + headerSet.add("mozilla/dom/Promise.h") + # We need ToJSValue to do the Promise to JS conversion. + bindingHeaders.add("mozilla/dom/ToJSValue.h") elif unrolled.isInterface(): if unrolled.isSpiderMonkeyInterface(): bindingHeaders.add("jsfriendapi.h") @@ -1352,7 +1358,11 @@ def UnionTypes(unionTypes, config): headers.add("mozilla/dom/Nullable.h") isSequence = f.isSequence() f = f.unroll() - if f.isInterface(): + if f.isPromise(): + headers.add("mozilla/dom/Promise.h") + # We need ToJSValue to do the Promise to JS conversion. + headers.add("mozilla/dom/ToJSValue.h") + elif f.isInterface(): if f.isSpiderMonkeyInterface(): headers.add("jsfriendapi.h") headers.add("mozilla/dom/TypedArray.h") @@ -1434,7 +1444,11 @@ def UnionConversions(unionTypes, config): def addHeadersForType(f): f = f.unroll() - if f.isInterface(): + if f.isPromise(): + headers.add("mozilla/dom/Promise.h") + # We need ToJSValue to do the Promise to JS conversion. + headers.add("mozilla/dom/ToJSValue.h") + elif f.isInterface(): if f.isSpiderMonkeyInterface(): headers.add("jsfriendapi.h") headers.add("mozilla/dom/TypedArray.h") @@ -5227,6 +5241,139 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, holderArgs=holderArgs, dealWithOptional=isOptional and (not nullable or isOwningUnion)) + if type.isPromise(): + assert not type.nullable() + assert defaultValue is None + + # We always have to hold a strong ref to Promise here, because + # Promise::resolve returns an addrefed thing. + argIsPointer = isCallbackReturnValue + if argIsPointer: + declType = CGGeneric("RefPtr<Promise>") + else: + declType = CGGeneric("OwningNonNull<Promise>") + + # Per spec, what we're supposed to do is take the original + # Promise.resolve and call it with the original Promise as this + # value to make a Promise out of whatever value we actually have + # here. The question is which global we should use. There are + # several cases to consider: + # + # 1) Normal call to API with a Promise argument. This is a case the + # spec covers, and we should be using the current Realm's + # Promise. That means the current compartment. + # 2) Call to API with a Promise argument over Xrays. In practice, + # this sort of thing seems to be used for giving an API + # implementation a way to wait for conclusion of an asyc + # operation, _not_ to expose the Promise to content code. So we + # probably want to allow callers to use such an API in a + # "natural" way, by passing chrome-side promises; indeed, that + # may be all that the caller has to represent their async + # operation. That means we really need to do the + # Promise.resolve() in the caller (chrome) compartment: if we do + # it in the content compartment, we will try to call .then() on + # the chrome promise while in the content compartment, which will + # throw and we'll just get a rejected Promise. Note that this is + # also the reason why a caller who has a chrome Promise + # representing an async operation can't itself convert it to a + # content-side Promise (at least not without some serious + # gyrations). + # 3) Promise return value from a callback or callback interface. + # Per spec, this should use the Realm of the callback object. In + # our case, that's the compartment of the underlying callback, + # not the current compartment (which may be the compartment of + # some cross-compartment wrapper around said callback). + # 4) Return value from a JS-implemented interface. In this case we + # have a problem. Our current compartment is the compartment of + # the JS implementation. But if the JS implementation returned + # a page-side Promise (which is a totally sane thing to do, and + # in fact the right thing to do given that this return value is + # going right to content script) then we don't want to + # Promise.resolve with our current compartment Promise, because + # that will wrap it up in a chrome-side Promise, which is + # decidedly _not_ what's desired here. So in that case we + # should really unwrap the return value and use the global of + # the result. CheckedUnwrap should be good enough for that; if + # it fails, then we're failing unwrap while in a + # system-privileged compartment, so presumably we have a dead + # object wrapper. Just error out. Do NOT fall back to using + # the current compartment instead: that will return a + # system-privileged rejected (because getting .then inside + # resolve() failed) Promise to the caller, which they won't be + # able to touch. That's not helpful. If we error out, on the + # other hand, they will get a content-side rejected promise. + # Same thing if the value returned is not even an object. + if isCallbackReturnValue == "JSImpl": + # Case 4 above. Note that globalObj defaults to the current + # compartment global. Note that we don't use $*{exceptionCode} + # here because that will try to aRv.Throw(NS_ERROR_UNEXPECTED) + # which we don't really want here. + assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n" + getPromiseGlobal = fill( + """ + if (!$${val}.isObject()) { + aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}")); + return nullptr; + } + JSObject* unwrappedVal = js::CheckedUnwrap(&$${val}.toObject()); + if (!unwrappedVal) { + // A slight lie, but not much of one, for a dead object wrapper. + aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}")); + return nullptr; + } + globalObj = js::GetGlobalForObjectCrossCompartment(unwrappedVal); + """, + sourceDescription=sourceDescription) + elif isCallbackReturnValue == "Callback": + getPromiseGlobal = dedent( + """ + // We basically want our entry global here. Play it safe + // and use GetEntryGlobal() to get it, with whatever + // principal-clamping it ends up doing. + globalObj = GetEntryGlobal()->GetGlobalJSObject(); + """) + else: + getPromiseGlobal = "" + + templateBody = fill( + """ + { // Scope for our GlobalObject, FastErrorResult, JSAutoCompartment, + // etc. + + JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx)); + $*{getPromiseGlobal} + JSAutoCompartment ac(cx, globalObj); + GlobalObject promiseGlobal(cx, globalObj); + if (promiseGlobal.Failed()) { + $*{exceptionCode} + } + + JS::Rooted<JS::Value> valueToResolve(cx, $${val}); + if (!JS_WrapValue(cx, &valueToResolve)) { + $*{exceptionCode} + } + binding_detail::FastErrorResult promiseRv; + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(promiseGlobal.GetAsSupports()); + if (!global) { + promiseRv.Throw(NS_ERROR_UNEXPECTED); + promiseRv.MaybeSetPendingException(cx); + $*{exceptionCode} + } + $${declName} = Promise::Resolve(global, cx, valueToResolve, + promiseRv); + if (promiseRv.MaybeSetPendingException(cx)) { + $*{exceptionCode} + } + } + """, + getPromiseGlobal=getPromiseGlobal, + exceptionCode=exceptionCode) + + return JSToNativeConversionInfo(templateBody, + declType=declType, + dealWithOptional=isOptional) + if type.isGeckoInterface(): assert not isEnforceRange and not isClamp @@ -5265,12 +5412,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # Also, callback return values always end up addrefing anyway, so there # is no point trying to avoid it here and it makes other things simpler # since we can assume the return value is a strong ref. - # - # Finally, promises need to hold a strong ref because that's what - # Promise.resolve returns. assert not descriptor.interface.isCallback() - isPromise = descriptor.interface.identifier.name == "Promise" - forceOwningType = isMember or isCallbackReturnValue or isPromise + forceOwningType = isMember or isCallbackReturnValue typeName = descriptor.nativeType typePtr = typeName + "*" @@ -5298,122 +5441,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, if forceOwningType: templateBody += 'static_assert(IsRefcounted<%s>::value, "We can only store refcounted classes.");' % typeName - if isPromise: - # Per spec, what we're supposed to do is take the original - # Promise.resolve and call it with the original Promise as this - # value to make a Promise out of whatever value we actually have - # here. The question is which global we should use. There are - # several cases to consider: - # - # 1) Normal call to API with a Promise argument. This is a case the - # spec covers, and we should be using the current Realm's - # Promise. That means the current compartment. - # 2) Call to API with a Promise argument over Xrays. In practice, - # this sort of thing seems to be used for giving an API - # implementation a way to wait for conclusion of an asyc - # operation, _not_ to expose the Promise to content code. So we - # probably want to allow callers to use such an API in a - # "natural" way, by passing chrome-side promises; indeed, that - # may be all that the caller has to represent their async - # operation. That means we really need to do the - # Promise.resolve() in the caller (chrome) compartment: if we do - # it in the content compartment, we will try to call .then() on - # the chrome promise while in the content compartment, which will - # throw and we'll just get a rejected Promise. Note that this is - # also the reason why a caller who has a chrome Promise - # representing an async operation can't itself convert it to a - # content-side Promise (at least not without some serious - # gyrations). - # 3) Promise return value from a callback or callback interface. - # This is in theory a case the spec covers but in practice it - # really doesn't define behavior here because it doesn't define - # what Realm we're in after the callback returns, which is when - # the argument conversion happens. We will use the current - # compartment, which is the compartment of the callable (which - # may itself be a cross-compartment wrapper itself), which makes - # as much sense as anything else. In practice, such an API would - # once again be providing a Promise to signal completion of an - # operation, which would then not be exposed to anyone other than - # our own implementation code. - # 4) Return value from a JS-implemented interface. In this case we - # have a problem. Our current compartment is the compartment of - # the JS implementation. But if the JS implementation returned - # a page-side Promise (which is a totally sane thing to do, and - # in fact the right thing to do given that this return value is - # going right to content script) then we don't want to - # Promise.resolve with our current compartment Promise, because - # that will wrap it up in a chrome-side Promise, which is - # decidedly _not_ what's desired here. So in that case we - # should really unwrap the return value and use the global of - # the result. CheckedUnwrap should be good enough for that; if - # it fails, then we're failing unwrap while in a - # system-privileged compartment, so presumably we have a dead - # object wrapper. Just error out. Do NOT fall back to using - # the current compartment instead: that will return a - # system-privileged rejected (because getting .then inside - # resolve() failed) Promise to the caller, which they won't be - # able to touch. That's not helpful. If we error out, on the - # other hand, they will get a content-side rejected promise. - # Same thing if the value returned is not even an object. - if isCallbackReturnValue == "JSImpl": - # Case 4 above. Note that globalObj defaults to the current - # compartment global. Note that we don't use $*{exceptionCode} - # here because that will try to aRv.Throw(NS_ERROR_UNEXPECTED) - # which we don't really want here. - assert exceptionCode == "aRv.Throw(NS_ERROR_UNEXPECTED);\nreturn nullptr;\n" - getPromiseGlobal = fill( - """ - if (!$${val}.isObject()) { - aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}")); - return nullptr; - } - JSObject* unwrappedVal = js::CheckedUnwrap(&$${val}.toObject()); - if (!unwrappedVal) { - // A slight lie, but not much of one, for a dead object wrapper. - aRv.ThrowTypeError<MSG_NOT_OBJECT>(NS_LITERAL_STRING("${sourceDescription}")); - return nullptr; - } - globalObj = js::GetGlobalForObjectCrossCompartment(unwrappedVal); - """, - sourceDescription=sourceDescription) - else: - getPromiseGlobal = "" - - templateBody = fill( - """ - { // Scope for our GlobalObject, FastErrorResult, JSAutoCompartment, - // etc. - - JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx)); - $*{getPromiseGlobal} - JSAutoCompartment ac(cx, globalObj); - GlobalObject promiseGlobal(cx, globalObj); - if (promiseGlobal.Failed()) { - $*{exceptionCode} - } - - JS::Rooted<JS::Value> valueToResolve(cx, $${val}); - if (!JS_WrapValue(cx, &valueToResolve)) { - $*{exceptionCode} - } - binding_detail::FastErrorResult promiseRv; - nsCOMPtr<nsIGlobalObject> global = - do_QueryInterface(promiseGlobal.GetAsSupports()); - if (!global) { - promiseRv.Throw(NS_ERROR_UNEXPECTED); - promiseRv.MaybeSetPendingException(cx); - $*{exceptionCode} - } - $${declName} = Promise::Resolve(global, cx, valueToResolve, - promiseRv); - if (promiseRv.MaybeSetPendingException(cx)) { - $*{exceptionCode} - } - } - """, - getPromiseGlobal=getPromiseGlobal, - exceptionCode=exceptionCode) - elif not descriptor.interface.isConsequential() and not descriptor.interface.isExternal(): + if (not descriptor.interface.isConsequential() and + not descriptor.interface.isExternal()): if failureCode is not None: templateBody += str(CastableObjectUnwrapper( descriptor, @@ -5453,24 +5482,12 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, # And store our value in ${declName} templateBody += "${declName} = ${holderName};\n" - if isPromise: - if type.nullable(): - codeToSetNull = "${declName} = nullptr;\n" - templateBody = CGIfElseWrapper( - "${val}.isNullOrUndefined()", - CGGeneric(codeToSetNull), - CGGeneric(templateBody)).define() - if isinstance(defaultValue, IDLNullValue): - templateBody = handleDefault(templateBody, codeToSetNull) - else: - assert defaultValue is None - else: - # Just pass failureCode, not onFailureBadType, here, so we'll report - # the thing as not an object as opposed to not implementing whatever - # our interface is. - templateBody = wrapObjectTemplate(templateBody, type, - "${declName} = nullptr;\n", - failureCode) + # Just pass failureCode, not onFailureBadType, here, so we'll report + # the thing as not an object as opposed to not implementing whatever + # our interface is. + templateBody = wrapObjectTemplate(templateBody, type, + "${declName} = nullptr;\n", + failureCode) declType = CGGeneric(declType) if holderType is not None: @@ -6537,6 +6554,19 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode, return (code, False) + if type.isPromise(): + assert not type.nullable() + # The use of ToJSValue here is a bit annoying because the Promise + # version is not inlined, but we can't put an inline version in either + # ToJSValue.h or BindingUtils.h, because Promise.h includes ToJSValue.h + # and that includes BindingUtils.h, so we'd get an include loop if + # either of those headers included Promise.h. Trying to write the + # conversion by hand here is annoying because we'd have to handle + # the various RefPtr, rawptr, NonNull, etc. cases, which ToJSValue will + # already handle for us, so we just use the function call. + return (wrapAndSetPtr("ToJSValue(cx, %s, ${jsvalHandle})" % result), + False) + if type.isGeckoInterface() and not type.isCallbackInterface(): descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name) if type.nullable(): @@ -6551,14 +6581,6 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode, wrapMethod = "GetOrCreateDOMReflector" wrapArgs = "cx, %s, ${jsvalHandle}" % result else: - # Hack: the "Promise" interface is OK to return from - # non-newobject things even when it's not wrappercached; that - # happens when using SpiderMonkey promises, and the WrapObject() - # method will just return the existing reflector, which is just - # not stored in a wrappercache. - if (not returnsNewObject and - descriptor.interface.identifier.name != "Promise"): - raise MethodNotNewObjectError(descriptor.interface.identifier.name) wrapMethod = "WrapNewBindingNonWrapperCachedObject" wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result if isConstructorRetval: @@ -6874,14 +6896,17 @@ def getRetvalDeclarationForType(returnType, descriptorProvider, if returnType.nullable(): result = CGTemplatedType("Nullable", result) return result, None, None, None, None - if returnType.isGeckoInterface(): - result = CGGeneric(descriptorProvider.getDescriptor( - returnType.unroll().inner.identifier.name).nativeType) - conversion = None + if returnType.isGeckoInterface() or returnType.isPromise(): + if returnType.isGeckoInterface(): + typeName = descriptorProvider.getDescriptor( + returnType.unroll().inner.identifier.name).nativeType + else: + typeName = "Promise" if isMember: - result = CGGeneric("StrongPtrForMember<%s>::Type" % result.define()) + conversion = None + result = CGGeneric("StrongPtrForMember<%s>::Type" % typeName) else: - conversion = CGGeneric("StrongOrRawPtr<%s>" % result.define()) + conversion = CGGeneric("StrongOrRawPtr<%s>" % typeName) result = CGGeneric("auto") return result, None, None, None, conversion if returnType.isCallback(): @@ -7046,7 +7071,9 @@ class CGCallGenerator(CGThing): if needsConst(a): arg = CGWrapper(arg, pre="Constify(", post=")") # And convert NonNull<T> to T& - if (((a.type.isGeckoInterface() or a.type.isCallback()) and not a.type.nullable()) or + if (((a.type.isGeckoInterface() or a.type.isCallback() or + a.type.isPromise()) and + not a.type.nullable()) or a.type.isDOMString()): arg = CGWrapper(arg, pre="NonNullHelper(", post=")") args.append(arg) @@ -7170,6 +7197,9 @@ class CGCallGenerator(CGThing): def getUnionMemberName(type): + # Promises can't be in unions, because they're not distinguishable + # from anything else. + assert not type.isPromise() if type.isGeckoInterface(): return type.inner.identifier.name if type.isEnum(): @@ -7299,8 +7329,9 @@ def wrapTypeIntoCurrentCompartment(type, value, isMember=True): return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None if (type.isString() or type.isPrimitive() or type.isEnum() or - type.isGeckoInterface() or type.isCallback() or type.isDate()): - # All of these don't need wrapping + type.isGeckoInterface() or type.isCallback() or type.isDate() or + type.isPromise()): + # All of these don't need wrapping. return None raise TypeError("Unknown type; we don't know how to wrap it in constructor " @@ -7438,58 +7469,12 @@ class CGPerSignatureCall(CGThing): if needsCx: argsPre.append("cx") - # Hack for making Promise.prototype.then work well over Xrays. - if (not idlNode.isStatic() and - descriptor.name == "Promise" and - idlNode.isMethod() and - idlNode.identifier.name == "then"): - cgThings.append(CGGeneric(dedent( - """ - JS::Rooted<JSObject*> calleeGlobal(cx, xpc::XrayAwareCalleeGlobal(&args.callee())); - """))) - argsPre.append("calleeGlobal") - needsUnwrap = False argsPost = [] if isConstructor: - if descriptor.name == "Promise": - # Hack for Promise for now: pass in our desired proto so the - # implementation can create the reflector with the right proto. - argsPost.append("desiredProto") - # Also, we do not want to enter the content compartment when the - # Promise constructor is called via Xrays, because we want to - # create our callback functions that we will hand to our caller - # in the Xray compartment. The reason we want to do that is the - # following situation, over Xrays: - # - # contentWindow.Promise.race([Promise.resolve(5)]) - # - # Ideally this would work. Internally, race() does a - # contentWindow.Promise.resolve() on everything in the array. - # Per spec, to support subclassing, - # contentWindow.Promise.resolve has to do: - # - # var resolve, reject; - # var p = new contentWindow.Promise(function(a, b) { - # resolve = a; - # reject = b; - # }); - # resolve(arg); - # return p; - # - # where "arg" is, in this case, the chrome-side return value of - # Promise.resolve(5). But if the "resolve" function in that - # case were created in the content compartment, then calling it - # would wrap "arg" in an opaque wrapper, and that function tries - # to get .then off the argument, which would throw. So we need - # to create the "resolve" function in the chrome compartment, - # and hence want to be running the entire Promise constructor - # (which creates that function) in the chrome compartment in - # this case. So don't set needsUnwrap here. - else: - needsUnwrap = True - needsUnwrappedVar = False - unwrappedVar = "obj" + needsUnwrap = True + needsUnwrappedVar = False + unwrappedVar = "obj" elif descriptor.interface.isJSImplemented(): if not idlNode.isStatic(): needsUnwrap = True @@ -7502,11 +7487,6 @@ class CGPerSignatureCall(CGThing): needsUnwrappedVar = True argsPre.append("unwrappedObj ? *unwrappedObj : obj") - if idlNode.isStatic() and not isConstructor and descriptor.name == "Promise": - # Hack for Promise for now: pass in the "this" value to - # Promise static methods. - argsPre.append("args.thisv()") - if needsUnwrap and needsUnwrappedVar: # We cannot assign into obj because it's a Handle, not a # MutableHandle, so we need a separate Rooted. @@ -7649,7 +7629,8 @@ class CGPerSignatureCall(CGThing): returnsNewObject = memberReturnsNewObject(self.idlNode) if (returnsNewObject and - self.returnType.isGeckoInterface()): + (self.returnType.isGeckoInterface() or + self.returnType.isPromise())): wrapCode += dedent( """ static_assert(!IsPointer<decltype(result)>::value, @@ -9455,6 +9436,8 @@ class CGMemberJITInfo(CGThing): return "JSVAL_TYPE_OBJECT" if t.isRecord(): return "JSVAL_TYPE_OBJECT" + if t.isPromise(): + return "JSVAL_TYPE_OBJECT" if t.isGeckoInterface(): return "JSVAL_TYPE_OBJECT" if t.isString(): @@ -9528,6 +9511,8 @@ class CGMemberJITInfo(CGThing): return "JSJitInfo::ArgType(JSJitInfo::Null | %s)" % CGMemberJITInfo.getJSArgType(t.inner) if t.isSequence(): return "JSJitInfo::Object" + if t.isPromise(): + return "JSJitInfo::Object" if t.isGeckoInterface(): return "JSJitInfo::Object" if t.isString(): @@ -9714,6 +9699,10 @@ def getUnionAccessorSignatureType(type, descriptorProvider): # Flat member types have already unwrapped nullables. assert not type.nullable() + # Promise types can never appear in unions, because Promise is not + # distinguishable from anything. + assert not type.isPromise() + if type.isSequence() or type.isRecord(): if type.isSequence(): wrapperType = "Sequence" @@ -13622,6 +13611,8 @@ class ForwardDeclarationBuilder: # Note: Spidermonkey interfaces are typedefs, so can't be # forward-declared + elif t.isPromise(): + self.addInMozillaDom("Promise") elif t.isCallback(): self.addInMozillaDom(t.callback.identifier.name) elif t.isDictionary(): @@ -14076,10 +14067,13 @@ class CGNativeMember(ClassMethod): else: defaultValue = "%s(0)" % enumName return enumName, defaultValue, "return ${declName};\n" - if type.isGeckoInterface(): - iface = type.unroll().inner - result = CGGeneric(self.descriptorProvider.getDescriptor( - iface.identifier.name).prettyNativeType) + if type.isGeckoInterface() or type.isPromise(): + if type.isGeckoInterface(): + iface = type.unroll().inner + result = CGGeneric(self.descriptorProvider.getDescriptor( + iface.identifier.name).prettyNativeType) + else: + result = CGGeneric("Promise") if self.resultAlreadyAddRefed: if isMember: holder = "RefPtr" @@ -14282,11 +14276,18 @@ class CGNativeMember(ClassMethod): # auto-wrapping in Nullable return CGUnionStruct.unionTypeDecl(type, isMember), True, False + if type.isPromise(): + assert not type.nullable() + if optional or isMember: + typeDecl = "OwningNonNull<Promise>" + else: + typeDecl = "Promise&" + return (typeDecl, False, False) + if type.isGeckoInterface() and not type.isCallbackInterface(): iface = type.unroll().inner argIsPointer = type.nullable() or iface.isExternal() - forceOwningType = (iface.isCallback() or isMember or - iface.identifier.name == "Promise") + forceOwningType = (iface.isCallback() or isMember) if argIsPointer: if (optional or isMember) and forceOwningType: typeDecl = "RefPtr<%s>" @@ -16920,7 +16921,10 @@ class CGEventGetter(CGNativeMember): def getMethodBody(self): type = self.member.type memberName = CGDictionary.makeMemberName(self.member.identifier.name) - if (type.isPrimitive() and type.tag() in builtinNames) or type.isEnum() or type.isGeckoInterface(): + if ((type.isPrimitive() and type.tag() in builtinNames) or + type.isEnum() or + type.isPromise() or + type.isGeckoInterface()): return "return " + memberName + ";\n" if type.isDOMString() or type.isByteString() or type.isUSVString(): return "aRetVal = " + memberName + ";\n" @@ -17331,6 +17335,8 @@ class CGEventClass(CGBindingImplClass): nativeType = CGGeneric("nsString") elif type.isByteString(): nativeType = CGGeneric("nsCString") + elif type.isPromise(): + nativeType = CGGeneric("RefPtr<Promise>") elif type.isGeckoInterface(): iface = type.unroll().inner nativeType = self.descriptor.getDescriptor( @@ -17355,7 +17361,7 @@ class CGEventClass(CGBindingImplClass): innerType = type.inner if (not innerType.isPrimitive() and not innerType.isEnum() and not innerType.isDOMString() and not innerType.isByteString() and - not innerType.isGeckoInterface()): + not innerType.isPromise() and not innerType.isGeckoInterface()): raise TypeError("Don't know how to properly manage GC/CC for " "event member of type %s" % type) diff --git a/dom/bindings/Configuration.py b/dom/bindings/Configuration.py index f80c19c33a..a56f2f2fd6 100644 --- a/dom/bindings/Configuration.py +++ b/dom/bindings/Configuration.py @@ -468,13 +468,6 @@ class Descriptor(DescriptorProvider): self.wrapperCache = (not self.interface.isCallback() and not self.interface.isIteratorInterface() and desc.get('wrapperCache', True)) - # Nasty temporary hack for supporting both DOM and SpiderMonkey promises - # without too much pain - if self.interface.identifier.name == "Promise": - assert self.wrapperCache - # But really, we're only wrappercached if we have an interface - # object (that is, when we're not using SpiderMonkey promises). - self.wrapperCache = self.interface.hasInterfaceObject() self.name = interface.identifier.name diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg index 142ccfdd66..c47f758750 100644 --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -84,11 +84,6 @@ MSG_DEF(MSG_NOTIFICATION_PERMISSION_DENIED, 0, JSEXN_TYPEERR, "Permission to sho MSG_DEF(MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER, 0, JSEXN_TYPEERR, "Notification constructor cannot be used in ServiceWorkerGlobalScope. Use registration.showNotification() instead.") MSG_DEF(MSG_INVALID_SCOPE, 2, JSEXN_TYPEERR, "Invalid scope trying to resolve {0} with base URL {1}.") MSG_DEF(MSG_INVALID_KEYFRAME_OFFSETS, 0, JSEXN_TYPEERR, "Keyframes with specified offsets must be in order and all be in the range [0, 1].") -MSG_DEF(MSG_ILLEGAL_PROMISE_CONSTRUCTOR, 0, JSEXN_TYPEERR, "Non-constructor value passed to NewPromiseCapability.") -MSG_DEF(MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCapabilitiesExecutor function already invoked with non-undefined values.") -MSG_DEF(MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.") -MSG_DEF(MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.") -MSG_DEF(MSG_PROMISE_ARG_NOT_ITERABLE, 1, JSEXN_TYPEERR, "{0} is not iterable") MSG_DEF(MSG_IS_NOT_PROMISE, 1, JSEXN_TYPEERR, "{0} is not a Promise") MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.") MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.") diff --git a/dom/bindings/ToJSValue.cpp b/dom/bindings/ToJSValue.cpp index 19219f4321..7afff41e2a 100644 --- a/dom/bindings/ToJSValue.cpp +++ b/dom/bindings/ToJSValue.cpp @@ -69,7 +69,7 @@ ToJSValue(JSContext* aCx, Promise& aArgument, JS::MutableHandle<JS::Value> aValue) { aValue.setObject(*aArgument.PromiseObj()); - return true; + return MaybeWrapObjectValue(aCx, aValue); } } // namespace dom diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h index a86abcd9dc..75313e2552 100644 --- a/dom/bindings/TypedArray.h +++ b/dom/bindings/TypedArray.h @@ -344,7 +344,7 @@ typedef TypedArray<uint8_t, js::UnwrapSharedArrayBuffer, JS_GetSharedArrayBuffer // A class for converting an nsTArray to a TypedArray // Note: A TypedArrayCreator must not outlive the nsTArray it was created from. // So this is best used to pass from things that understand nsTArray to -// things that understand TypedArray, as with Promise::ArgumentToJSValue. +// things that understand TypedArray, as with ToJSValue. template<typename TypedArrayType> class TypedArrayCreator { diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 8c32a87388..fafb3c9b6f 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -1597,11 +1597,7 @@ class IDLInterface(IDLInterfaceOrNamespace): args = attr.args() if attr.hasArgs() else [] - if self.identifier.name == "Promise": - promiseType = BuiltinTypes[IDLBuiltinType.Types.any] - else: - promiseType = None - retType = IDLWrapperType(self.location, self, promiseType) + retType = IDLWrapperType(self.location, self) if identifier == "Constructor" or identifier == "ChromeConstructor": name = "constructor" @@ -1988,7 +1984,8 @@ class IDLType(IDLObject): 'callback', 'union', 'sequence', - 'record' + 'record', + 'promise' ) def __init__(self, location, name): @@ -2142,9 +2139,8 @@ class IDLUnresolvedType(IDLType): Unresolved types are interface types """ - def __init__(self, location, name, promiseInnerType=None): + def __init__(self, location, name): IDLType.__init__(self, location, name) - self._promiseInnerType = promiseInnerType def isComplete(self): return False @@ -2171,11 +2167,8 @@ class IDLUnresolvedType(IDLType): assert self.name.name == obj.identifier.name return IDLCallbackType(self.location, obj) - if self._promiseInnerType and not self._promiseInnerType.isComplete(): - self._promiseInnerType = self._promiseInnerType.complete(scope) - name = self.name.resolve(scope, None) - return IDLWrapperType(self.location, obj, self._promiseInnerType) + return IDLWrapperType(self.location, obj) def isDistinguishableFrom(self, other): raise TypeError("Can't tell whether an unresolved type is or is not " @@ -2285,7 +2278,9 @@ class IDLNullableType(IDLParameterizedType): return self.inner.isInterface() def isPromise(self): - return self.inner.isPromise() + # There is no such thing as a nullable Promise. + assert not self.inner.isPromise() + return False def isCallbackInterface(self): return self.inner.isCallbackInterface() @@ -2698,13 +2693,11 @@ class IDLTypedef(IDLObjectWithIdentifier): class IDLWrapperType(IDLType): - def __init__(self, location, inner, promiseInnerType=None): + def __init__(self, location, inner): IDLType.__init__(self, location, inner.identifier.name) self.inner = inner self._identifier = inner.identifier self.builtin = False - assert not promiseInnerType or inner.identifier.name == "Promise" - self._promiseInnerType = promiseInnerType def __eq__(self, other): return (isinstance(other, IDLWrapperType) and @@ -2754,14 +2747,6 @@ class IDLWrapperType(IDLType): def isEnum(self): return isinstance(self.inner, IDLEnum) - def isPromise(self): - return (isinstance(self.inner, IDLInterface) and - self.inner.identifier.name == "Promise") - - def promiseInnerType(self): - assert self.isPromise() - return self._promiseInnerType - def isSerializable(self): if self.isInterface(): if self.inner.isExternal(): @@ -2793,8 +2778,6 @@ class IDLWrapperType(IDLType): assert False def isDistinguishableFrom(self, other): - if self.isPromise(): - return False if other.isPromise(): return False if other.isUnion(): @@ -2841,10 +2824,6 @@ class IDLWrapperType(IDLType): # Let's say true, though ideally we'd only do this when # exposureSet contains the primary global's name. return True - if (self.isPromise() and - # Check the internal type - not self.promiseInnerType().unroll().isExposedInAllOf(exposureSet)): - return False return iface.exposureSet.issuperset(exposureSet) def _getDependentObjects(self): @@ -2871,6 +2850,45 @@ class IDLWrapperType(IDLType): return set([self.inner]) return set() +
+class IDLPromiseType(IDLParametrizedType):
+ def __init__(self, location, innerType):
+ IDLParametrizedType.__init__(self, location, "Promise", innerType)
+
+ def __eq__(self, other):
+ return (isinstance(other, IDLPromiseType) and
+ self.promiseInnerType() == other.promiseInnerType())
+
+ def __str__(self):
+ return self.inner.__str__() + "Promise"
+
+ def isPromise(self):
+ return True
+
+ def promiseInnerType(self):
+ return self.inner
+
+ def tag(self):
+ return IDLType.Tags.promise
+
+ def complete(self, scope):
+ self.inner = self.promiseInnerType().complete(scope)
+ return self
+
+ def unroll(self):
+ # We do not unroll our inner. Just stop at ourselves. That
+ # lets us add headers for both ourselves and our inner as
+ # needed.
+ return self
+
+ def isDistinguishableFrom(self, other):
+ # Promises are not distinguishable from anything.
+ return False
+
+ def isExposedInAllOf(self, exposureSet):
+ # Check the internal type
+ return self.promiseInnerType().unroll().isExposedInAllOf(exposureSet)
+
class IDLBuiltinType(IDLType): @@ -3990,7 +4008,9 @@ class IDLAttribute(IDLInterfaceMember): raise WebIDLError("An attribute with [PutForwards] must have an " "interface type as its type", [self.location]) - if not self.type.isInterface() and self.getExtendedAttribute("SameObject"): + if (not self.type.isInterface() and + not self.type.isPromise() and + self.getExtendedAttribute("SameObject")): raise WebIDLError("An attribute with [SameObject] must have an " "interface type as its type", [self.location]) @@ -6394,17 +6414,13 @@ class Parser(Tokenizer): type = IDLSequenceType(self.getLocation(p, 1), innerType) p[0] = self.handleNullable(type, p[5]) - # Note: Promise<void> is allowed, so we want to parametrize on - # ReturnType, not Type. Also, we want this to end up picking up - # the Promise interface for now, hence the games with IDLUnresolvedType. + # Note: Promise<void> is allowed, so we want to parameterize on + # ReturnType, not Type. Promise types can't be null, hence no "Null" in there. def p_NonAnyTypePromiseType(self, p): """ - NonAnyType : PROMISE LT ReturnType GT Null + NonAnyType : PROMISE LT ReturnType GT """ - innerType = p[3] - promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise") - type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3]) - p[0] = self.handleNullable(type, p[5]) + p[0] = IDLPromiseType(self.getLocation(p, 1), p[3]) def p_NonAnyTypeRecordType(self, p): """ diff --git a/dom/bindings/parser/tests/test_distinguishability.py b/dom/bindings/parser/tests/test_distinguishability.py index d7780c1ffa..ac515a01df 100644 --- a/dom/bindings/parser/tests/test_distinguishability.py +++ b/dom/bindings/parser/tests/test_distinguishability.py @@ -263,7 +263,6 @@ def WebIDLTest(parser, harness): callback Callback2 = long(short arg); dictionary Dict {}; dictionary Dict2 {}; - interface _Promise {}; interface TestInterface {%s }; """ diff --git a/dom/bindings/parser/tests/test_promise.py b/dom/bindings/parser/tests/test_promise.py index 55bc076809..091381fab0 100644 --- a/dom/bindings/parser/tests/test_promise.py +++ b/dom/bindings/parser/tests/test_promise.py @@ -2,7 +2,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface _Promise {}; interface A { legacycaller Promise<any> foo(); }; @@ -18,7 +17,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface _Promise {}; interface A { Promise<any> foo(); long foo(long arg); @@ -35,7 +33,6 @@ def WebIDLTest(parser, harness): threw = False try: parser.parse(""" - interface _Promise {}; interface A { long foo(long arg); Promise<any> foo(); @@ -50,7 +47,6 @@ def WebIDLTest(parser, harness): parser = parser.reset() parser.parse(""" - interface _Promise {}; interface A { Promise<any> foo(); Promise<any> foo(long arg); diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index 429b685291..e5279345dc 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -87,17 +87,6 @@ Promise::~Promise() mozilla::DropJSObjects(this); } -bool -Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, - JS::MutableHandle<JSObject*> aWrapper) -{ -#ifdef DEBUG - binding_detail::AssertReflectorHasGivenProto(aCx, mPromiseObj, aGivenProto); -#endif // DEBUG - aWrapper.set(mPromiseObj); - return true; -} - // static already_AddRefed<Promise> Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv) diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index 77914079a8..2fe365c465 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -120,10 +120,6 @@ public: return mGlobal; } - bool - WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, - JS::MutableHandle<JSObject*> aWrapper); - // Do the equivalent of Promise.resolve in the compartment of aGlobal. The // compartment of aCx is ignored. Errors are reported on the ErrorResult; if // aRv comes back !Failed(), this function MUST return a non-null value. diff --git a/dom/webidl/Promise.webidl b/dom/webidl/Promise.webidl index 65b406b6fb..a296553dfa 100644 --- a/dom/webidl/Promise.webidl +++ b/dom/webidl/Promise.webidl @@ -3,28 +3,15 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. * - * The origin of this IDL file is - * http://dom.spec.whatwg.org/#promises + * This IDL file contains utilities to help connect JS promises to our + * Web IDL infrastructure. */ -// TODO We use object instead Function. There is an open issue on WebIDL to -// have different types for "platform-provided function" and "user-provided -// function"; for now, we just use "object". -callback PromiseInit = void (object resolve, object reject); - callback PromiseJobCallback = void(); [TreatNonCallableAsNull] callback AnyCallback = any (any value); -// Promises are implemented in SpiderMonkey; just define a tiny interface to make -// codegen of Promise arguments and return values work. -[NoInterfaceObject, - Exposed=(Window,Worker,WorkerDebugger,System)] -// Need to escape "Promise" so it's treated as an identifier. -interface _Promise { -}; - // Hack to allow us to have JS owning and properly tracing/CCing/etc a // PromiseNativeHandler. [NoInterfaceObject, diff --git a/dom/webidl/TestInterfaceJS.webidl b/dom/webidl/TestInterfaceJS.webidl index 2cf8d701af..2757745f0a 100644 --- a/dom/webidl/TestInterfaceJS.webidl +++ b/dom/webidl/TestInterfaceJS.webidl @@ -70,7 +70,7 @@ interface TestInterfaceJS : EventTarget { // Tests for promise-rejection behavior Promise<void> testPromiseWithThrowingChromePromiseInit(); - Promise<void> testPromiseWithThrowingContentPromiseInit(PromiseInit func); + Promise<void> testPromiseWithThrowingContentPromiseInit(Function func); Promise<void> testPromiseWithDOMExceptionThrowingPromiseInit(); Promise<void> testPromiseWithThrowingChromeThenFunction(); Promise<void> testPromiseWithThrowingContentThenFunction(AnyCallback func); |