--- a/browser/base/content/sync/aboutSyncTabs.js +++ b/browser/base/content/sync/aboutSyncTabs.js @@ -268,17 +268,17 @@ var RemoteTabViewer = { }; let tabEnt = this.createItem(tabAttrs); list.appendChild(tabEnt); } } }.bind(this); return CloudSync().tabs.getRemoteTabs() - .then(updateTabList, Promise.reject.bind(Promise)); + .then(updateTabList, Promise.reject); }, adjustContextMenu: function (event) { let mode = "all"; switch (this._tabsList.selectedItems.length) { case 0: break; case 1:
--- a/dom/base/DOMRequest.cpp +++ b/dom/base/DOMRequest.cpp @@ -8,17 +8,16 @@ #include "DOMError.h" #include "nsThreadUtils.h" #include "DOMCursor.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ScriptSettings.h" -#include "jsfriendapi.h" using mozilla::dom::AnyCallback; using mozilla::dom::DOMError; using mozilla::dom::DOMRequest; using mozilla::dom::DOMRequestService; using mozilla::dom::DOMCursor; using mozilla::dom::Promise; using mozilla::dom::AutoJSAPI; @@ -202,44 +201,39 @@ DOMRequest::FireEvent(const nsAString& a } void DOMRequest::RootResultVal() { mozilla::HoldJSObjects(this); } -void +already_AddRefed<Promise> DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback, - AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, - mozilla::ErrorResult& aRv) + AnyCallback* aRejectCallback, mozilla::ErrorResult& aRv) { if (!mPromise) { mPromise = Promise::Create(DOMEventTargetHelper::GetParentObject(), aRv); if (aRv.Failed()) { - return; + return nullptr; } if (mDone) { // Since we create mPromise lazily, it's possible that the DOMRequest object // has already fired its success/error event. In that case we should // manually resolve/reject mPromise here. mPromise will take care of // calling the callbacks on |promise| as needed. if (mError) { mPromise->MaybeRejectBrokenly(mError); } else { mPromise->MaybeResolve(mResult); } } } - // Just use the global of the Promise itself as the callee global. - JS::Rooted<JSObject*> global(aCx, mPromise->GetWrapper()); - global = js::GetGlobalForObjectCrossCompartment(global); - mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv); + return mPromise->Then(aCx, aResolveCallback, aRejectCallback, aRv); } NS_IMPL_ISUPPORTS(DOMRequestService, nsIDOMRequestService) NS_IMETHODIMP DOMRequestService::CreateRequest(nsIDOMWindow* aWindow, nsIDOMDOMRequest** aRequest) {
--- a/dom/base/DOMRequest.h +++ b/dom/base/DOMRequest.h @@ -69,21 +69,19 @@ public: NS_ASSERTION(mDone || !mError, "Error should be null when pending"); return mError; } IMPL_EVENT_HANDLER(success) IMPL_EVENT_HANDLER(error) - void + already_AddRefed<mozilla::dom::Promise> Then(JSContext* aCx, AnyCallback* aResolveCallback, - AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, - mozilla::ErrorResult& aRv); + AnyCallback* aRejectCallback, mozilla::ErrorResult& aRv); void FireSuccess(JS::Handle<JS::Value> aResult); void FireError(const nsAString& aError); void FireError(nsresult aError); void FireDetailedError(DOMError* aError); explicit DOMRequest(nsPIDOMWindow* aWindow); explicit DOMRequest(nsIGlobalObject* aGlobal);
--- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2756,27 +2756,18 @@ ConvertExceptionToPromise(JSContext* cx, if (!JS_GetPendingException(cx, &exn)) { // This is very important: if there is no pending exception here but we're // ending up in this code, that means the callee threw an uncatchable // exception. Just propagate that out as-is. return false; } JS_ClearPendingException(cx); - - nsCOMPtr<nsIGlobalObject> globalObj = - do_QueryInterface(global.GetAsSupports()); - if (!globalObj) { - ErrorResult rv; - rv.Throw(NS_ERROR_UNEXPECTED); - return !rv.MaybeSetPendingException(cx); - } - ErrorResult rv; - RefPtr<Promise> promise = Promise::Reject(globalObj, cx, exn, rv); + RefPtr<Promise> promise = Promise::Reject(global, exn, rv); if (rv.MaybeSetPendingException(cx)) { // We just give up. We put the exception from the ErrorResult on // the JSContext just to make sure to not leak memory on the // ErrorResult, but now just put the original exception back. JS_SetPendingException(cx, exn); return false; }
--- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -1944,23 +1944,17 @@ DOMInterfaces = { 'headerFile': 'TestExampleProxyInterface-example.h', 'register': False }, 'TestDeprecatedInterface' : { # Keep this in sync with TestExampleInterface 'headerFile': 'TestBindingHeader.h', 'register': False - }, - -'TestInterfaceWithPromiseConstructorArg' : { - 'headerFile': 'TestBindingHeader.h', - 'register': False, - }, - + } } # These are temporary, until they've been converted to use new DOM bindings def addExternalIface(iface, nativeType=None, headerFile=None, notflattened=False): if iface in DOMInterfaces: raise Exception('Interface declared both as WebIDL and External interface') domInterface = {
--- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2974,31 +2974,16 @@ class CGCreateInterfaceObjectsMethod(CGA } """)) unforgeableHolderSetup = CGList( [createUnforgeableHolder, installUnforgeableHolder], "\n") else: unforgeableHolderSetup = None - if self.descriptor.name == "Promise": - speciesSetup = CGGeneric(fill( - """ - JS::Rooted<JSObject*> promiseConstructor(aCx, *interfaceCache); - JS::Rooted<jsid> species(aCx, - SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species))); - if (!JS_DefinePropertyById(aCx, promiseConstructor, species, JS::UndefinedHandleValue, - JSPROP_SHARED, Promise::PromiseSpecies, nullptr)) { - $*{failureCode} - } - """, - failureCode=failureCode)) - else: - speciesSetup = None - if (self.descriptor.interface.isOnGlobalProtoChain() and needInterfacePrototypeObject): makeProtoPrototypeImmutable = CGGeneric(fill( """ if (*${protoCache}) { bool succeeded; JS::Handle<JSObject*> prot = GetProtoObjectHandle(aCx, aGlobal); if (!JS_SetImmutablePrototype(aCx, prot, &succeeded)) { @@ -3014,17 +2999,17 @@ class CGCreateInterfaceObjectsMethod(CGA protoCache=protoCache, failureCode=failureCode)) else: makeProtoPrototypeImmutable = None return CGList( [getParentProto, CGGeneric(getConstructorProto), initIds, prefCache, CGGeneric(call), defineAliases, unforgeableHolderSetup, - speciesSetup, makeProtoPrototypeImmutable], + makeProtoPrototypeImmutable], "\n").define() class CGGetPerInterfaceObject(CGAbstractMethod): """ A method for getting a per-interface object (a prototype object or interface constructor object). """ @@ -5088,134 +5073,34 @@ def getJSToNativeConversionInfo(type, de else: declType = "NonNull<" + typeName + ">" templateBody = "" 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, ErrorResult, JSAutoCompartment, - // etc. - - JS::Rooted<JSObject*> globalObj(cx, JS::CurrentGlobalOrNull(cx)); - $*{getPromiseGlobal} - JSAutoCompartment ac(cx, globalObj); - GlobalObject promiseGlobal(cx, globalObj); + { // Scope for our GlobalObject and ErrorResult + + // Might as well use CurrentGlobalOrNull here; that will at + // least give us the same behavior as if the caller just called + // Promise.resolve() themselves. + GlobalObject promiseGlobal(cx, JS::CurrentGlobalOrNull(cx)); if (promiseGlobal.Failed()) { $*{exceptionCode} } ErrorResult promiseRv; - JS::Handle<JSObject*> promiseCtor = - PromiseBinding::GetConstructorObjectHandle(cx, globalObj); - if (!promiseCtor) { - $*{exceptionCode} - } - JS::Rooted<JS::Value> resolveThisv(cx, JS::ObjectValue(*promiseCtor)); - JS::Rooted<JS::Value> resolveResult(cx); - JS::Rooted<JS::Value> valueToResolve(cx, $${val}); - if (!JS_WrapValue(cx, &valueToResolve)) { - $*{exceptionCode} - } - Promise::Resolve(promiseGlobal, resolveThisv, valueToResolve, - &resolveResult, promiseRv); + $${declName} = Promise::Resolve(promiseGlobal, $${val}, promiseRv); if (promiseRv.MaybeSetPendingException(cx)) { $*{exceptionCode} } - nsresult unwrapRv = UNWRAP_OBJECT(Promise, &resolveResult.toObject(), $${declName}); - if (NS_FAILED(unwrapRv)) { // Quite odd - promiseRv.Throw(unwrapRv); - promiseRv.MaybeSetPendingException(cx); - $*{exceptionCode} - } } """, - getPromiseGlobal=getPromiseGlobal, exceptionCode=exceptionCode) elif not descriptor.skipGen and not descriptor.interface.isConsequential() and not descriptor.interface.isExternal(): if failureCode is not None: templateBody += str(CastableObjectUnwrapper( descriptor, "&${val}.toObject()", "${declName}", failureCode)) @@ -7142,86 +7027,38 @@ class CGPerSignatureCall(CGThing): # needsCx decision on the types involved, just on our extended # attributes. Also, JSContext is not needed for the static case # since GlobalObject already contains the context. needsCx = needCx(returnType, arguments, self.extendedAttributes, not descriptor.interface.isJSImplemented(), static) if needsCx: argsPre.append("cx") - # Hack for making Promise.prototype.then work well over Xrays. - if (not static and - (descriptor.name == "Promise" or - descriptor.name == "MozAbortablePromise") 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: + needsUnwrap = True + needsUnwrappedVar = False + unwrappedVar = "obj" if descriptor.name == "Promise" or descriptor.name == "MozAbortablePromise": # 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" elif descriptor.interface.isJSImplemented(): if not idlNode.isStatic(): needsUnwrap = True needsUnwrappedVar = True argsPost.append("js::GetObjectCompartment(unwrappedObj ? *unwrappedObj : obj)") elif needScopeObject(returnType, arguments, self.extendedAttributes, descriptor.wrapperCache, True, idlNode.getExtendedAttribute("StoreInSlot")): needsUnwrap = True needsUnwrappedVar = True argsPre.append("unwrappedObj ? *unwrappedObj : obj") - if static 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. cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;\n")) unwrappedVar = "unwrappedObj.ref()" if idlNode.isMethod() and idlNode.isLegacycaller(): # If we can have legacycaller with identifier, we can't @@ -9156,16 +8993,36 @@ class CGStaticMethodJitinfo(CGGeneric): " JSJitInfo::AliasEverything, JSVAL_TYPE_MISSING, false, false,\n" " false, false, 0\n" "};\n" % (IDLToCIdentifier(method.identifier.name), CppKeywords.checkMethodName( IDLToCIdentifier(method.identifier.name)))) +class CGMethodIdentityTest(CGAbstractMethod): + """ + A class to generate a method-identity test for a given IDL operation. + """ + def __init__(self, descriptor, method): + self.method = method + name = "Is%sMethod" % MakeNativeName(method.identifier.name) + CGAbstractMethod.__init__(self, descriptor, name, 'bool', + [Argument('JS::Handle<JSObject*>', 'aObj')]) + + def definition_body(self): + return dedent( + """ + MOZ_ASSERT(aObj); + return js::IsFunctionObject(aObj) && + js::FunctionObjectIsNative(aObj) && + FUNCTION_VALUE_TO_JITINFO(JS::ObjectValue(*aObj)) == &%s_methodinfo; + """ % IDLToCIdentifier(self.method.identifier.name)) + + def getEnumValueName(value): # Some enum values can be empty strings. Others might have weird # characters in them. Deal with the former by returning "_empty", # deal with possible name collisions from that by throwing if the # enum value is actually "_empty", and throw on any value # containing non-ASCII chars for now. Replace all chars other than # [0-9A-Za-z_] with '_'. if re.match("[^\x20-\x7E]", value): @@ -11787,16 +11644,18 @@ class CGDescriptor(CGThing): elif descriptor.interface.hasInterfacePrototypeObject(): specializedMethod = CGSpecializedMethod(descriptor, m) cgThings.append(specializedMethod) if m.returnsPromise(): cgThings.append(CGMethodPromiseWrapper(descriptor, specializedMethod)) cgThings.append(CGMemberJITInfo(descriptor, m)) if props.isCrossOriginMethod: crossOriginMethods.add(m.identifier.name) + if m.getExtendedAttribute("MethodIdentityTestable"): + cgThings.append(CGMethodIdentityTest(descriptor, m)) # If we've hit the maplike/setlike member itself, go ahead and # generate its convenience functions. elif m.isMaplikeOrSetlike(): cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m)) elif m.isAttr(): if m.stringifier: raise TypeError("Stringifier attributes not supported yet. " "See bug 824857.\n"
--- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -78,16 +78,10 @@ MSG_DEF(MSG_INVALID_URL_SCHEME, 2, JSEXN MSG_DEF(MSG_RESPONSE_URL_IS_NULL, 0, JSEXN_TYPEERR, "Cannot set Response.finalURL when Response.url is null.") MSG_DEF(MSG_RESPONSE_HAS_VARY_STAR, 0, JSEXN_TYPEERR, "Invalid Response object with a 'Vary: *' header.") MSG_DEF(MSG_BAD_FORMDATA, 0, JSEXN_TYPEERR, "Could not parse content as FormData.") MSG_DEF(MSG_NO_ACTIVE_WORKER, 1, JSEXN_TYPEERR, "No active worker for scope {0}.") MSG_DEF(MSG_NOTIFICATION_PERMISSION_DENIED, 0, JSEXN_TYPEERR, "Permission to show Notification denied.") 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.")
--- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -4800,16 +4800,17 @@ class IDLMethod(IDLInterfaceMember, IDLS identifier == "UnsafeInPrerendering" or identifier == "Pref" or identifier == "Deprecated" or identifier == "Func" or identifier == "AvailableIn" or identifier == "CheckAnyPermissions" or identifier == "CheckAllPermissions" or identifier == "BinaryName" or + identifier == "MethodIdentityTestable" or identifier == "StaticClassOverride"): # Known attributes that we don't need to do anything with here pass else: raise WebIDLError("Unknown extended attribute %s on method" % identifier, [attr.location]) IDLInterfaceMember.handleExtendedAttribute(self, attr)
--- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -1366,24 +1366,12 @@ public: already_AddRefed<TestDeprecatedInterface> Constructor(const GlobalObject&, ErrorResult&); static void AlsoDeprecated(const GlobalObject&); virtual nsISupports* GetParentObject(); }; -class TestInterfaceWithPromiseConstructorArg : public nsISupports, public nsWrapperCache -{ -public: - NS_DECL_ISUPPORTS - - static - already_AddRefed<TestInterfaceWithPromiseConstructorArg> - Constructor(const GlobalObject&, Promise&, ErrorResult&); - - virtual nsISupports* GetParentObject(); -}; - } // namespace dom } // namespace mozilla #endif /* TestBindingHeader_h */
--- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -50,17 +50,16 @@ callback interface TestCallbackInterface void passFloat32Array(Float32Array arg); void passFloat64Array(Float64Array arg); void passSequenceOfArrayBuffers(sequence<ArrayBuffer> arg); void passSequenceOfNullableArrayBuffers(sequence<ArrayBuffer?> arg); void passVariadicTypedArray(Float32Array... arg); void passVariadicNullableTypedArray(Float32Array?... arg); Uint8Array receiveUint8Array(); attribute Uint8Array uint8ArrayAttr; - Promise<void> receivePromise(); }; callback interface TestSingleOperationCallbackInterface { TestInterface doSomething(short arg, sequence<double> anotherArg); }; enum TestEnum { "1", @@ -1030,19 +1029,16 @@ dictionary Dict : ParentDict { long dashed-name; required long requiredLong; required object requiredObject; CustomEventInit customEventInit; TestDictionaryTypedef dictionaryTypedef; - - Promise<void> promise; - sequence<Promise<void>> promiseSequence; }; dictionary ParentDict : GrandparentDict { long c = 5; TestInterface someInterface; TestInterface? someNullableInterface = null; TestExternalInterface someExternalInterface; any parentAny; @@ -1160,12 +1156,8 @@ interface TestCppKeywordNamedMethodsInte long volatile(); }; [Deprecated="GetAttributeNode", Constructor()] interface TestDeprecatedInterface { static void alsoDeprecated(); }; - -[Constructor(Promise<void> promise)] -interface TestInterfaceWithPromiseConstructorArg { -};
--- a/dom/bindings/test/TestInterfaceJS.js +++ b/dom/bindings/test/TestInterfaceJS.js @@ -119,41 +119,43 @@ TestInterfaceJS.prototype = { testPromiseWithDOMExceptionThrowingThenFunction: function() { return this._win.Promise.resolve(5).then(() => { throw new this._win.DOMException("We are a third DOMException", "NetworkError"); }); }, testPromiseWithThrowingChromeThenable: function() { - var thenable = { - then: function() { - noSuchMethodExistsYo3() - } - }; + // We need to produce a thing that has a "then" property in the page + // compartment, since we plan to call the page-provided resolve function. + var thenable = new this._win.Object(); + Cu.waiveXrays(thenable).then = function() { + noSuchMethodExistsYo3() + } return new this._win.Promise(function(resolve) { resolve(thenable) }); }, testPromiseWithThrowingContentThenable: function(thenable) { // Waive Xrays on the thenable, because we're calling resolve() in the // chrome compartment, so that's the compartment the "then" property get // will happen in, and if we leave the Xray in place the function-valued // property won't return the function. return this._win.Promise.resolve(Cu.waiveXrays(thenable)); }, testPromiseWithDOMExceptionThrowingThenable: function() { - var thenable = { - then: () => { - throw new this._win.DOMException("We are a fourth DOMException", - "TypeMismatchError"); - } - }; + // We need to produce a thing that has a "then" property in the page + // compartment, since we plan to call the page-provided resolve function. + var thenable = new this._win.Object(); + Cu.waiveXrays(thenable).then = () => { + throw new this._win.DOMException("We are a fourth DOMException", + "TypeMismatchError"); + } return new this._win.Promise(function(resolve) { resolve(thenable) }); }, get onsomething() { return this.__DOM_IMPL__.getEventHandler("onsomething"); },
--- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -29,17 +29,16 @@ #include "nsJSUtils.h" #include "nsPIDOMWindow.h" #include "PromiseCallback.h" #include "PromiseDebugging.h" #include "PromiseNativeHandler.h" #include "PromiseWorkerProxy.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" -#include "WrapperFactory.h" #include "xpcpublic.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif namespace mozilla { namespace dom { @@ -110,68 +109,68 @@ protected: private: RefPtr<Promise> mPromise; RefPtr<PromiseCallback> mCallback; JS::PersistentRooted<JS::Value> mValue; NS_DECL_OWNINGTHREAD; }; +enum { + SLOT_PROMISE = 0, + SLOT_DATA +}; + /* * Utilities for thenable callbacks. * * A thenable is a { then: function(resolve, reject) { } }. * `then` is called with a resolve and reject callback pair. * Since only one of these should be called at most once (first call wins), the * two keep a reference to each other in SLOT_DATA. When either of them is * called, the references are cleared. Further calls are ignored. */ namespace { void LinkThenableCallables(JSContext* aCx, JS::Handle<JSObject*> aResolveFunc, JS::Handle<JSObject*> aRejectFunc) { - js::SetFunctionNativeReserved(aResolveFunc, Promise::SLOT_DATA, + js::SetFunctionNativeReserved(aResolveFunc, SLOT_DATA, JS::ObjectValue(*aRejectFunc)); - js::SetFunctionNativeReserved(aRejectFunc, Promise::SLOT_DATA, + js::SetFunctionNativeReserved(aRejectFunc, SLOT_DATA, JS::ObjectValue(*aResolveFunc)); } /* * Returns false if callback was already called before, otherwise breaks the * links and returns true. */ bool MarkAsCalledIfNotCalledBefore(JSContext* aCx, JS::Handle<JSObject*> aFunc) { - JS::Value otherFuncVal = - js::GetFunctionNativeReserved(aFunc, Promise::SLOT_DATA); + JS::Value otherFuncVal = js::GetFunctionNativeReserved(aFunc, SLOT_DATA); if (!otherFuncVal.isObject()) { return false; } JSObject* otherFuncObj = &otherFuncVal.toObject(); - MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj, - Promise::SLOT_DATA).isObject()); + MOZ_ASSERT(js::GetFunctionNativeReserved(otherFuncObj, SLOT_DATA).isObject()); // Break both references. - js::SetFunctionNativeReserved(aFunc, Promise::SLOT_DATA, - JS::UndefinedValue()); - js::SetFunctionNativeReserved(otherFuncObj, Promise::SLOT_DATA, - JS::UndefinedValue()); + js::SetFunctionNativeReserved(aFunc, SLOT_DATA, JS::UndefinedValue()); + js::SetFunctionNativeReserved(otherFuncObj, SLOT_DATA, JS::UndefinedValue()); return true; } Promise* GetPromise(JSContext* aCx, JS::Handle<JSObject*> aFunc) { - JS::Value promiseVal = js::GetFunctionNativeReserved(aFunc, - Promise::SLOT_PROMISE); + JS::Value promiseVal = js::GetFunctionNativeReserved(aFunc, SLOT_PROMISE); MOZ_ASSERT(promiseVal.isObject()); Promise* promise; UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise); return promise; } } // namespace @@ -273,118 +272,58 @@ protected: private: RefPtr<Promise> mPromise; JS::PersistentRooted<JSObject*> mThenable; RefPtr<PromiseInit> mThen; NS_DECL_OWNINGTHREAD; }; -// A struct implementing -// <http://www.ecma-international.org/ecma-262/6.0/#sec-promisecapability-records>. -// While the spec holds on to these in some places, in practice those places -// don't actually need everything from this struct, so we explicitly grab -// members from it as needed in those situations. That allows us to make this a -// stack-only struct and keep the rooting simple. -// -// We also add an optimization for the (common) case when we discover that the -// Promise constructor we're supposed to use is in fact the canonical Promise -// constructor. In that case we will just set mNativePromise in our -// PromiseCapability and not set mPromise/mResolve/mReject; the correct -// callbacks will be the standard Promise ones, and we don't really want to -// synthesize JSFunctions for them in that situation. -struct MOZ_STACK_CLASS Promise::PromiseCapability +// Fast version of PromiseResolveThenableJob for use in the cases when we know we're +// calling the canonical Promise.prototype.then on an actual DOM Promise. In +// that case we can just bypass the jumping into and out of JS and call +// AppendCallbacks on that promise directly. +class FastPromiseResolveThenableJob final : public nsRunnable { - explicit PromiseCapability(JSContext* aCx) - : mPromise(aCx) - , mResolve(aCx) - , mReject(aCx) - {} +public: + FastPromiseResolveThenableJob(PromiseCallback* aResolveCallback, + PromiseCallback* aRejectCallback, + Promise* aNextPromise) + : mResolveCallback(aResolveCallback) + , mRejectCallback(aRejectCallback) + , mNextPromise(aNextPromise) + { + MOZ_ASSERT(aResolveCallback); + MOZ_ASSERT(aRejectCallback); + MOZ_ASSERT(aNextPromise); + MOZ_COUNT_CTOR(FastPromiseResolveThenableJob); + } - // Take an exception on aCx and try to convert it into a promise rejection. - // Note that this can result in a new exception being thrown on aCx, or an - // exception getting thrown on aRv. On entry to this method, aRv is assumed - // to not be a failure. This should only be called if NewPromiseCapability - // succeeded on this PromiseCapability. - void RejectWithException(JSContext* aCx, ErrorResult& aRv); + virtual + ~FastPromiseResolveThenableJob() + { + NS_ASSERT_OWNINGTHREAD(FastPromiseResolveThenableJob); + MOZ_COUNT_DTOR(FastPromiseResolveThenableJob); + } - // Return a JS::Value representing the promise. This should only be called if - // NewPromiseCapability succeeded on this PromiseCapability. It makes no - // guarantees about compartments (e.g. in the mNativePromise case it's in the - // compartment of the reflector, but in the mPromise case it might be in the - // compartment of some cross-compartment wrapper for a reflector). - JS::Value PromiseValue() const; - - // All the JS::Value fields of this struct are actually objects, but for our - // purposes it's simpler to store them as JS::Value. - - // [[Promise]]. - JS::Rooted<JS::Value> mPromise; - // [[Resolve]]. Value in the context compartment. - JS::Rooted<JS::Value> mResolve; - // [[Reject]]. Value in the context compartment. - JS::Rooted<JS::Value> mReject; - // If mNativePromise is non-null, we should use it, not mPromise. - RefPtr<Promise> mNativePromise; +protected: + NS_IMETHOD + Run() override + { + NS_ASSERT_OWNINGTHREAD(FastPromiseResolveThenableJob); + mNextPromise->AppendCallbacks(mResolveCallback, mRejectCallback); + return NS_OK; + } private: - // We don't want to allow creation of temporaries of this type, ever. - PromiseCapability(const PromiseCapability&) = delete; - PromiseCapability(PromiseCapability&&) = delete; + RefPtr<PromiseCallback> mResolveCallback; + RefPtr<PromiseCallback> mRejectCallback; + RefPtr<Promise> mNextPromise; }; -void -Promise::PromiseCapability::RejectWithException(JSContext* aCx, - ErrorResult& aRv) -{ - // This method basically implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-ifabruptrejectpromise - // or at least the parts of it that happen if we have an abrupt completion. - - MOZ_ASSERT(!aRv.Failed()); - MOZ_ASSERT(mNativePromise || !mPromise.isUndefined(), - "NewPromiseCapability didn't succeed"); - - JS::Rooted<JS::Value> exn(aCx); - if (!JS_GetPendingException(aCx, &exn)) { - // This is an uncatchable exception, so can't be converted into a rejection. - // Just rethrow that on aRv. - aRv.ThrowUncatchableException(); - return; - } - - JS_ClearPendingException(aCx); - - // If we have a native promise, just reject it without trying to call out into - // JS. - if (mNativePromise) { - mNativePromise->MaybeRejectInternal(aCx, exn); - return; - } - - JS::Rooted<JS::Value> ignored(aCx); - if (!JS::Call(aCx, JS::UndefinedHandleValue, mReject, JS::HandleValueArray(exn), - &ignored)) { - aRv.NoteJSContextException(); - } -} - -JS::Value -Promise::PromiseCapability::PromiseValue() const -{ - MOZ_ASSERT(mNativePromise || !mPromise.isUndefined(), - "NewPromiseCapability didn't succeed"); - - if (mNativePromise) { - return JS::ObjectValue(*mNativePromise->GetWrapper()); - } - - return mPromise; -} - // Promise NS_IMPL_CYCLE_COLLECTION_CLASS(Promise) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise) #if defined(DOM_PROMISE_DEPRECATED_REPORTING) tmp->MaybeReportRejectedOnce(); #else @@ -653,18 +592,16 @@ Promise::JSCallbackThenableRejecter(JSCo unsigned aArgc, JS::Value* aVp) { return ThenableResolverCommon(aCx, PromiseCallback::Reject, aArgc, aVp); } /* static */ JSObject* Promise::CreateFunction(JSContext* aCx, Promise* aPromise, int32_t aTask) { - // If this function ever changes, make sure to update - // WrapperPromiseCallback::GetDependentPromise. JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback, 1 /* nargs */, 0 /* flags */, nullptr); if (!func) { return nullptr; } JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func)); @@ -754,602 +691,133 @@ Promise::CallInitFunction(const GlobalOb return; } aInit.Call(resolveFunc, rejectFunc, aRv, "promise initializer", CallbackObject::eRethrowExceptions, Compartment()); aRv.WouldReportJSException(); if (aRv.Failed()) { - // There are two possibilities here. Either we've got a rethrown exception, - // or we reported that already and synthesized a generic NS_ERROR_FAILURE on - // the ErrorResult. In the former case, it doesn't much matter how we get - // the exception JS::Value from the ErrorResult to us, since we'll just end - // up wrapping it into the right compartment as needed if we hand it to - // someone. But in the latter case we have to ensure that the new exception - // object we create is created in our reflector compartment, not in our - // current compartment, because in the case when we're a Promise constructor - // called over Xrays creating it in the current compartment would mean - // rejecting with a value that can't be accessed by code that can call - // then() on this Promise. + // We want the same behavior as this JS implementation: + // + // function Promise(arg) { try { arg(a, b); } catch (e) { this.reject(e); }} // - // Luckily, MaybeReject(aRv) does exactly what we want here: it enters our - // reflector compartment before trying to produce a JS::Value from the - // ErrorResult. - MaybeReject(aRv); + // In particular, that means not using MaybeReject(aRv) here, since that + // would create the exception object in our reflector compartment, while we + // want to create it in whatever the current compartment on cx is. + JS::Rooted<JS::Value> value(cx); + DebugOnly<bool> conversionResult = ToJSValue(cx, aRv, &value); + MOZ_ASSERT(conversionResult); + MaybeRejectInternal(cx, value); } } -#define GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT 0 -#define GET_CAPABILITIES_EXECUTOR_REJECT_SLOT 1 - -namespace { -bool -GetCapabilitiesExecutor(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +/* static */ already_AddRefed<Promise> +Promise::Resolve(const GlobalObject& aGlobal, + JS::Handle<JS::Value> aValue, ErrorResult& aRv) { - // Implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-getcapabilitiesexecutor-functions - // except we store the [[Resolve]] and [[Reject]] in our own internal slots, - // not in a PromiseCapability. The PromiseCapability will then read them from - // us. - JS::CallArgs args = CallArgsFromVp(aArgc, aVp); - - // Step 1 is an assert. - - // Step 2 doesn't need to be done, because it's just giving a name to the - // PromiseCapability record which is supposed to be stored in an internal - // slot. But we don't store that at all, per the comment above; we just - // directly store its [[Resolve]] and [[Reject]] members. - - // Steps 3 and 4. - if (!js::GetFunctionNativeReserved(&args.callee(), - GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT).isUndefined() || - !js::GetFunctionNativeReserved(&args.callee(), - GET_CAPABILITIES_EXECUTOR_REJECT_SLOT).isUndefined()) { - ErrorResult rv; - rv.ThrowTypeError<MSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY>(); - return !rv.MaybeSetPendingException(aCx); - } - - // Step 5. - js::SetFunctionNativeReserved(&args.callee(), - GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT, - args.get(0)); - - // Step 6. - js::SetFunctionNativeReserved(&args.callee(), - GET_CAPABILITIES_EXECUTOR_REJECT_SLOT, - args.get(1)); - - // Step 7. - args.rval().setUndefined(); - return true; -} -} // anonymous namespace - -/* static */ void -Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal, - JS::Handle<JS::Value> aConstructor, - bool aForceCallbackCreation, - PromiseCapability& aCapability, - ErrorResult& aRv) -{ - // Implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability - - if (!aConstructor.isObject() || - !JS::IsConstructor(&aConstructor.toObject())) { - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); - return; - } + // If a Promise was passed, just return it. + if (aValue.isObject()) { + JS::Rooted<JSObject*> valueObj(aGlobal.Context(), &aValue.toObject()); + Promise* nextPromise; + nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise); - // Step 2 is a note. - // Step 3 is already done because we got the PromiseCapability passed in. - - // Optimization: Check whether constructor is in fact the canonical - // Promise constructor for aGlobal. - JS::Rooted<JSObject*> global(aCx, aGlobal->GetGlobalJSObject()); - { - // Scope for the JSAutoCompartment, since we need to enter the compartment - // of global to get constructors from it. Save the compartment we used to - // be in, though; we'll need it later. - JS::Rooted<JSObject*> callerGlobal(aCx, JS::CurrentGlobalOrNull(aCx)); - JSAutoCompartment ac(aCx, global); - - // Now wrap aConstructor into the compartment of aGlobal, so comparing it to - // the canonical Promise for that compartment actually makes sense. - JS::Rooted<JS::Value> constructorValue(aCx, aConstructor); - if (!MaybeWrapObjectValue(aCx, &constructorValue)) { - aRv.NoteJSContextException(); - return; - } - - JSObject* defaultCtor = PromiseBinding::GetConstructorObject(aCx, global); - if (!defaultCtor) { - aRv.NoteJSContextException(); - return; - } - if (defaultCtor == &constructorValue.toObject()) { - // This is the canonical Promise constructor. - aCapability.mNativePromise = Promise::Create(aGlobal, aRv); - if (aForceCallbackCreation) { - // We have to be a bit careful here. We want to create these functions - // in the compartment in which they would be created if we actually - // invoked the constructor via JS::Construct below. That means our - // callerGlobal compartment if aConstructor is an Xray and the reflector - // compartment of the promise we're creating otherwise. But note that - // our callerGlobal compartment is precisely the reflector compartment - // unless the call was done over Xrays, because the reflector - // compartment comes from xpc::XrayAwareCalleeGlobal. So we really just - // want to create these functions in the callerGlobal compartment. - MOZ_ASSERT(xpc::WrapperFactory::IsXrayWrapper(&aConstructor.toObject()) || - callerGlobal == global); - JSAutoCompartment ac2(aCx, callerGlobal); - - JSObject* resolveFuncObj = - CreateFunction(aCx, aCapability.mNativePromise, - PromiseCallback::Resolve); - if (!resolveFuncObj) { - aRv.NoteJSContextException(); - return; - } - aCapability.mResolve.setObject(*resolveFuncObj); - - JSObject* rejectFuncObj = - CreateFunction(aCx, aCapability.mNativePromise, - PromiseCallback::Reject); - if (!rejectFuncObj) { - aRv.NoteJSContextException(); - return; - } - aCapability.mReject.setObject(*rejectFuncObj); - } - return; + if (NS_SUCCEEDED(rv)) { + RefPtr<Promise> addRefed = nextPromise; + return addRefed.forget(); } } - // Step 4. - // We can create our get-capabilities function in the calling compartment. It - // will work just as if we did |new promiseConstructor(function(a,b){}). - // Notably, if we're called over Xrays that's all fine, because we will end up - // creating the callbacks in the caller compartment in that case. - JSFunction* getCapabilitiesFunc = - js::NewFunctionWithReserved(aCx, GetCapabilitiesExecutor, - 2 /* nargs */, - 0 /* flags */, - nullptr); - if (!getCapabilitiesFunc) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - - JS::Rooted<JSObject*> getCapabilitiesObj(aCx); - getCapabilitiesObj = JS_GetFunctionObject(getCapabilitiesFunc); - - // Step 5 doesn't need to be done, since we're not actually storing a - // PromiseCapability in the executor; see the comments in - // GetCapabilitiesExecutor above. - - // Step 6 and step 7. - JS::Rooted<JS::Value> getCapabilities(aCx, - JS::ObjectValue(*getCapabilitiesObj)); - JS::Rooted<JS::Value> promiseVal(aCx); - if (!JS::Construct(aCx, aConstructor, - JS::HandleValueArray(getCapabilities), - &promiseVal)) { - aRv.NoteJSContextException(); - return; - } - - // Step 8 plus copying over the value to the PromiseCapability. - JS::Rooted<JS::Value> v(aCx); - v = js::GetFunctionNativeReserved(getCapabilitiesObj, - GET_CAPABILITIES_EXECUTOR_RESOLVE_SLOT); - if (!v.isObject() || !JS::IsCallable(&v.toObject())) { - aRv.ThrowTypeError<MSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE>(); - return; - } - aCapability.mResolve = v; - - // Step 9 plus copying over the value to the PromiseCapability. - v = js::GetFunctionNativeReserved(getCapabilitiesObj, - GET_CAPABILITIES_EXECUTOR_REJECT_SLOT); - if (!v.isObject() || !JS::IsCallable(&v.toObject())) { - aRv.ThrowTypeError<MSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE>(); - return; - } - aCapability.mReject = v; - - // Step 10. - aCapability.mPromise = promiseVal; - - // Step 11 doesn't need anything, since the PromiseCapability was passed in. -} - -/* static */ void -Promise::Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aValue, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) -{ - // Implementation of - // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.resolve - - JSContext* cx = aGlobal.Context(); - nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { aRv.Throw(NS_ERROR_UNEXPECTED); - return; - } - - // Steps 1 and 2. - if (!aThisv.isObject()) { - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); - return; + return nullptr; } - // Step 3. If a Promise was passed and matches our constructor, just return it. - if (aValue.isObject()) { - JS::Rooted<JSObject*> valueObj(cx, &aValue.toObject()); - Promise* nextPromise; - nsresult rv = UNWRAP_OBJECT(Promise, valueObj, nextPromise); - - if (NS_SUCCEEDED(rv)) { - JS::Rooted<JS::Value> constructor(cx); - if (!JS_GetProperty(cx, valueObj, "constructor", &constructor)) { - aRv.NoteJSContextException(); - return; - } - - // Cheat instead of calling JS_SameValue, since we know one's an object. - if (aThisv == constructor) { - aRetval.setObject(*valueObj); - return; - } - } + RefPtr<Promise> p = Resolve(global, aGlobal.Context(), aValue, aRv); + if (p) { + p->mFullfillmentStack = p->mAllocationStack; } - - // Step 4. - PromiseCapability capability(cx); - NewPromiseCapability(cx, global, aThisv, false, capability, aRv); - // Step 5. - if (aRv.Failed()) { - return; - } - - // Step 6. - Promise* p = capability.mNativePromise; - if (p) { - p->MaybeResolveInternal(cx, aValue); - p->mFullfillmentStack = p->mAllocationStack; - } else { - JS::Rooted<JS::Value> value(cx, aValue); - JS::Rooted<JS::Value> ignored(cx); - if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */, - capability.mResolve, JS::HandleValueArray(value), - &ignored)) { - // Step 7. - aRv.NoteJSContextException(); - return; - } - } - - // Step 8. - aRetval.set(capability.PromiseValue()); + return p.forget(); } /* static */ already_AddRefed<Promise> Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { RefPtr<Promise> promise = Create(aGlobal, aRv); if (aRv.Failed()) { return nullptr; } promise->MaybeResolveInternal(aCx, aValue); return promise.forget(); } -/* static */ void -Promise::Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aValue, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) +/* static */ already_AddRefed<Promise> +Promise::Reject(const GlobalObject& aGlobal, + JS::Handle<JS::Value> aValue, ErrorResult& aRv) { - // Implementation of - // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.reject - - JSContext* cx = aGlobal.Context(); - nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { aRv.Throw(NS_ERROR_UNEXPECTED); - return; - } - - // Steps 1 and 2. - if (!aThisv.isObject()) { - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); - return; - } - - // Step 3. - PromiseCapability capability(cx); - NewPromiseCapability(cx, global, aThisv, false, capability, aRv); - // Step 4. - if (aRv.Failed()) { - return; + return nullptr; } - // Step 5. - Promise* p = capability.mNativePromise; + RefPtr<Promise> p = Reject(global, aGlobal.Context(), aValue, aRv); if (p) { - p->MaybeRejectInternal(cx, aValue); p->mRejectionStack = p->mAllocationStack; - } else { - JS::Rooted<JS::Value> value(cx, aValue); - JS::Rooted<JS::Value> ignored(cx); - if (!JS::Call(cx, JS::UndefinedHandleValue /* thisVal */, - capability.mReject, JS::HandleValueArray(value), - &ignored)) { - // Step 6. - aRv.NoteJSContextException(); - return; - } } - - // Step 7. - aRetval.set(capability.PromiseValue()); + return p.forget(); } /* static */ already_AddRefed<Promise> Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) { RefPtr<Promise> promise = Create(aGlobal, aRv); if (aRv.Failed()) { return nullptr; } promise->MaybeRejectInternal(aCx, aValue); return promise.forget(); } -namespace { -void -SpeciesConstructor(JSContext* aCx, - JS::Handle<JSObject*> promise, - JS::Handle<JS::Value> defaultCtor, - JS::MutableHandle<JS::Value> ctor, - ErrorResult& aRv) +already_AddRefed<Promise> +Promise::Then(JSContext* aCx, AnyCallback* aResolveCallback, + AnyCallback* aRejectCallback, ErrorResult& aRv) { - // Implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-speciesconstructor - - // Step 1. - MOZ_ASSERT(promise); - - // Step 2. - JS::Rooted<JS::Value> constructorVal(aCx); - if (!JS_GetProperty(aCx, promise, "constructor", &constructorVal)) { - // Step 3. - aRv.NoteJSContextException(); - return; - } - - // Step 4. - if (constructorVal.isUndefined()) { - ctor.set(defaultCtor); - return; - } - - // Step 5. - if (!constructorVal.isObject()) { - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); - return; - } - - // Step 6. - JS::Rooted<jsid> species(aCx, - SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species))); - JS::Rooted<JS::Value> speciesVal(aCx); - JS::Rooted<JSObject*> constructorObj(aCx, &constructorVal.toObject()); - if (!JS_GetPropertyById(aCx, constructorObj, species, &speciesVal)) { - // Step 7. - aRv.NoteJSContextException(); - return; - } - - // Step 8. - if (speciesVal.isNullOrUndefined()) { - ctor.set(defaultCtor); - return; - } - - // Step 9. - if (speciesVal.isObject() && JS::IsConstructor(&speciesVal.toObject())) { - ctor.set(speciesVal); - return; - } - - // Step 10. - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); -} -} // anonymous namespace - -void -Promise::Then(JSContext* aCx, JS::Handle<JSObject*> aCalleeGlobal, - AnyCallback* aResolveCallback, AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) -{ - // Implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.then - - // Step 1. - JS::Rooted<JS::Value> promiseVal(aCx, JS::ObjectValue(*GetWrapper())); - if (!MaybeWrapObjectValue(aCx, &promiseVal)) { - aRv.NoteJSContextException(); - return; - } - JS::Rooted<JSObject*> promiseObj(aCx, &promiseVal.toObject()); - MOZ_ASSERT(promiseObj); - - // Step 2 was done by the bindings. - - // Step 3. We want to use aCalleeGlobal here because it will do the - // right thing for us via Xrays (where we won't find @@species on - // our promise constructor for now). - JS::Rooted<JSObject*> calleeGlobal(aCx, aCalleeGlobal); - JS::Rooted<JS::Value> defaultCtorVal(aCx); - { // Scope for JSAutoCompartment - JSAutoCompartment ac(aCx, aCalleeGlobal); - JSObject* defaultCtor = - PromiseBinding::GetConstructorObject(aCx, calleeGlobal); - if (!defaultCtor) { - aRv.NoteJSContextException(); - return; - } - defaultCtorVal.setObject(*defaultCtor); - } - if (!MaybeWrapObjectValue(aCx, &defaultCtorVal)) { - aRv.NoteJSContextException(); - return; - } - - JS::Rooted<JS::Value> constructor(aCx); - SpeciesConstructor(aCx, promiseObj, defaultCtorVal, &constructor, aRv); + RefPtr<Promise> promise = Create(GetParentObject(), aRv); if (aRv.Failed()) { - // Step 4. - return; + return nullptr; } - // Step 5. - GlobalObject globalObj(aCx, GetWrapper()); - if (globalObj.Failed()) { - aRv.NoteJSContextException(); - return; - } - nsCOMPtr<nsIGlobalObject> globalObject = - do_QueryInterface(globalObj.GetAsSupports()); - if (!globalObject) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return; - } - PromiseCapability capability(aCx); - NewPromiseCapability(aCx, globalObject, constructor, false, capability, aRv); - if (aRv.Failed()) { - // Step 6. - return; - } + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); - // Now step 7: start - // http://www.ecma-international.org/ecma-262/6.0/#sec-performpromisethen - - // Step 1 and step 2 are just assertions. - - // Step 3 and step 4 are kinda handled for us already; we use null - // to represent "Identity" and "Thrower". - - // Steps 5 and 6. These branch based on whether we know we have a - // vanilla Promise or not. - JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); - if (capability.mNativePromise) { - Promise* promise = capability.mNativePromise; - - RefPtr<PromiseCallback> resolveCb = - PromiseCallback::Factory(promise, global, aResolveCallback, - PromiseCallback::Resolve); - - RefPtr<PromiseCallback> rejectCb = - PromiseCallback::Factory(promise, global, aRejectCallback, - PromiseCallback::Reject); + RefPtr<PromiseCallback> resolveCb = + PromiseCallback::Factory(promise, global, aResolveCallback, + PromiseCallback::Resolve); - AppendCallbacks(resolveCb, rejectCb); - } else { - JS::Rooted<JSObject*> resolveObj(aCx, &capability.mResolve.toObject()); - RefPtr<AnyCallback> resolveFunc = - new AnyCallback(aCx, resolveObj, GetIncumbentGlobal()); - - JS::Rooted<JSObject*> rejectObj(aCx, &capability.mReject.toObject()); - RefPtr<AnyCallback> rejectFunc = - new AnyCallback(aCx, rejectObj, GetIncumbentGlobal()); - - if (!capability.mPromise.isObject()) { - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); - return; - } - JS::Rooted<JSObject*> newPromiseObj(aCx, &capability.mPromise.toObject()); - // We want to store the reflector itself. - newPromiseObj = js::CheckedUnwrap(newPromiseObj); - if (!newPromiseObj) { - // Just throw something. - aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>(); - return; - } + RefPtr<PromiseCallback> rejectCb = + PromiseCallback::Factory(promise, global, aRejectCallback, + PromiseCallback::Reject); - RefPtr<PromiseCallback> resolveCb; - if (aResolveCallback) { - resolveCb = new WrapperPromiseCallback(global, aResolveCallback, - newPromiseObj, - resolveFunc, rejectFunc); - } else { - resolveCb = new InvokePromiseFuncCallback(global, newPromiseObj, - resolveFunc); - } + AppendCallbacks(resolveCb, rejectCb); - RefPtr<PromiseCallback> rejectCb; - if (aRejectCallback) { - rejectCb = new WrapperPromiseCallback(global, aRejectCallback, - newPromiseObj, - resolveFunc, rejectFunc); - } else { - rejectCb = new InvokePromiseFuncCallback(global, newPromiseObj, - rejectFunc); - } - - AppendCallbacks(resolveCb, rejectCb); - } - - aRetval.set(capability.PromiseValue()); + return promise.forget(); } -void -Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv) +already_AddRefed<Promise> +Promise::Catch(JSContext* aCx, AnyCallback* aRejectCallback, ErrorResult& aRv) { - // Implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.prototype.catch - - // We can't call Promise::Then directly, because someone might have - // overridden Promise.prototype.then. - JS::Rooted<JS::Value> promiseVal(aCx, JS::ObjectValue(*GetWrapper())); - if (!MaybeWrapObjectValue(aCx, &promiseVal)) { - aRv.NoteJSContextException(); - return; - } - JS::Rooted<JSObject*> promiseObj(aCx, &promiseVal.toObject()); - MOZ_ASSERT(promiseObj); - JS::AutoValueArray<2> callbacks(aCx); - callbacks[0].setUndefined(); - if (aRejectCallback) { - callbacks[1].setObject(*aRejectCallback->Callable()); - // It could be in any compartment, so put it in ours. - if (!MaybeWrapObjectValue(aCx, callbacks[1])) { - aRv.NoteJSContextException(); - return; - } - } else { - callbacks[1].setNull(); - } - if (!JS_CallFunctionName(aCx, promiseObj, "then", callbacks, aRetval)) { - aRv.NoteJSContextException(); - } + RefPtr<AnyCallback> resolveCb; + return Then(aCx, resolveCb, aRejectCallback, aRv); } /** * The CountdownHolder class encapsulates Promise.all countdown functions and * the countdown holder parts of the Promises spec. It maintains the result * array and AllResolveElementFunctions use SetValue() to set the array indices. */ class CountdownHolder final : public nsISupports @@ -1481,329 +949,34 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(AllResol NS_IMPL_CYCLE_COLLECTING_RELEASE(AllResolveElementFunction) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AllResolveElementFunction) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(AllResolveElementFunction, mCountdownHolder) -static const JSClass PromiseAllDataHolderClass = { - "PromiseAllDataHolder", JSCLASS_HAS_RESERVED_SLOTS(3) -}; - -// Slot indices for objects of class PromiseAllDataHolderClass. -#define DATA_HOLDER_REMAINING_ELEMENTS_SLOT 0 -#define DATA_HOLDER_VALUES_ARRAY_SLOT 1 -#define DATA_HOLDER_RESOLVE_FUNCTION_SLOT 2 - -// Slot indices for PromiseAllResolveElement. -// The RESOLVE_ELEMENT_INDEX_SLOT stores our index unless we've already been -// called. Then it stores INT32_MIN (which is never a valid index value). -#define RESOLVE_ELEMENT_INDEX_SLOT 0 -// The RESOLVE_ELEMENT_DATA_HOLDER_SLOT slot stores an object of class -// PromiseAllDataHolderClass. -#define RESOLVE_ELEMENT_DATA_HOLDER_SLOT 1 - -static bool -PromiseAllResolveElement(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +/* static */ already_AddRefed<Promise> +Promise::All(const GlobalObject& aGlobal, + const Sequence<JS::Value>& aIterable, ErrorResult& aRv) { - // Implements - // http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all-resolve-element-functions - // - // See the big comment about compartments in Promise::All "Substep 4" that - // explains what compartments the various stuff here lives in. - JS::CallArgs args = CallArgsFromVp(aArgc, aVp); - - // Step 1. - int32_t index = - js::GetFunctionNativeReserved(&args.callee(), - RESOLVE_ELEMENT_INDEX_SLOT).toInt32(); - // Step 2. - if (index == INT32_MIN) { - args.rval().setUndefined(); - return true; - } - - // Step 3. - js::SetFunctionNativeReserved(&args.callee(), - RESOLVE_ELEMENT_INDEX_SLOT, - JS::Int32Value(INT32_MIN)); - - // Step 4 already done. - - // Step 5. - JS::Rooted<JSObject*> dataHolder(aCx, - &js::GetFunctionNativeReserved(&args.callee(), - RESOLVE_ELEMENT_DATA_HOLDER_SLOT).toObject()); - - JS::Rooted<JS::Value> values(aCx, - js::GetReservedSlot(dataHolder, DATA_HOLDER_VALUES_ARRAY_SLOT)); - - // Step 6, effectively. - JS::Rooted<JS::Value> resolveFunc(aCx, - js::GetReservedSlot(dataHolder, DATA_HOLDER_RESOLVE_FUNCTION_SLOT)); - - // Step 7. - int32_t remainingElements = - js::GetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32(); - - // Step 8. - JS::Rooted<JSObject*> valuesObj(aCx, &values.toObject()); - if (!JS_DefineElement(aCx, valuesObj, index, args.get(0), JSPROP_ENUMERATE)) { - return false; - } - - // Step 9. - remainingElements -= 1; - js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT, - JS::Int32Value(remainingElements)); - - // Step 10. - if (remainingElements == 0) { - return JS::Call(aCx, JS::UndefinedHandleValue, resolveFunc, - JS::HandleValueArray(values), args.rval()); - } - - // Step 11. - args.rval().setUndefined(); - return true; -} - - -/* static */ void -Promise::All(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aIterable, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) -{ - // Implements http://www.ecma-international.org/ecma-262/6.0/#sec-promise.all - nsCOMPtr<nsIGlobalObject> global = - do_QueryInterface(aGlobal.GetAsSupports()); - if (!global) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return; - } - JSContext* cx = aGlobal.Context(); - // Steps 1-5: nothing to do. Note that the @@species bits got removed in - // https://github.com/tc39/ecma262/pull/211 - - // Step 6. - PromiseCapability capability(cx); - NewPromiseCapability(cx, global, aThisv, true, capability, aRv); - // Step 7. - if (aRv.Failed()) { - return; - } - - MOZ_ASSERT(aThisv.isObject(), "How did NewPromiseCapability succeed?"); - JS::Rooted<JSObject*> constructorObj(cx, &aThisv.toObject()); + nsTArray<RefPtr<Promise>> promiseList; - // After this point we have a useful promise value in "capability", so just go - // ahead and put it in our retval now. Every single return path below would - // want to do that anyway. - aRetval.set(capability.PromiseValue()); - if (!MaybeWrapValue(cx, aRetval)) { - aRv.NoteJSContextException(); - return; - } + for (uint32_t i = 0; i < aIterable.Length(); ++i) { + JS::Rooted<JS::Value> value(cx, aIterable.ElementAt(i)); + RefPtr<Promise> nextPromise = Promise::Resolve(aGlobal, value, aRv); - // The arguments we're going to be passing to "then" on each loop iteration. - // The second one we know already; the first one will be created on each - // iteration of the loop. - JS::AutoValueArray<2> callbackFunctions(cx); - callbackFunctions[1].set(capability.mReject); + MOZ_ASSERT(!aRv.Failed()); - // Steps 8 and 9. - JS::ForOfIterator iter(cx); - if (!iter.init(aIterable, JS::ForOfIterator::AllowNonIterable)) { - capability.RejectWithException(cx, aRv); - return; - } - - if (!iter.valueIsIterable()) { - ThrowErrorMessage(cx, MSG_PROMISE_ARG_NOT_ITERABLE, - "Argument of Promise.all"); - capability.RejectWithException(cx, aRv); - return; + promiseList.AppendElement(Move(nextPromise)); } - // Step 10 doesn't need to be done, because ForOfIterator handles it - // for us. - - // Now we jump over to - // http://www.ecma-international.org/ecma-262/6.0/#sec-performpromiseall - // and do its steps. - - // Substep 4. Create our data holder that holds all the things shared across - // every step of the iterator. In particular, this holds the - // remainingElementsCount (as an integer reserved slot), the array of values, - // and the resolve function from our PromiseCapability. - // - // We have to be very careful about which compartments we create things in - // here. In particular, we have to maintain the invariant that anything - // stored in a reserved slot is same-compartment with the object whose - // reserved slot it's in. But we want to create the values array in the - // Promise reflector compartment, because that array can get exposed to code - // that has access to the Promise reflector (in particular code from that - // compartment), and that should work, even if the Promise reflector - // compartment is less-privileged than our caller compartment. - // - // So the plan is as follows: Create the values array in the promise reflector - // compartment. Create the PromiseAllResolveElement function and the data - // holder in our current compartment. Store a cross-compartment wrapper to - // the values array in the holder. This should be OK because the only things - // we hand the PromiseAllResolveElement function to are the "then" calls we do - // and in the case when the reflector compartment is not the current - // compartment those are happening over Xrays anyway, which means they get the - // canonical "then" function and content can't see our - // PromiseAllResolveElement. - JS::Rooted<JSObject*> dataHolder(cx); - dataHolder = JS_NewObjectWithGivenProto(cx, &PromiseAllDataHolderClass, - nullptr); - if (!dataHolder) { - capability.RejectWithException(cx, aRv); - return; - } - - JS::Rooted<JSObject*> reflectorGlobal(cx, global->GetGlobalJSObject()); - JS::Rooted<JSObject*> valuesArray(cx); - { // Scope for JSAutoCompartment. - JSAutoCompartment ac(cx, reflectorGlobal); - valuesArray = JS_NewArrayObject(cx, 0); - } - if (!valuesArray) { - // It's important that we've exited the JSAutoCompartment by now, before - // calling RejectWithException and possibly invoking capability.mReject. - capability.RejectWithException(cx, aRv); - return; - } - - // The values array as a value we can pass to a function in our current - // compartment, or store in the holder's reserved slot. - JS::Rooted<JS::Value> valuesArrayVal(cx, JS::ObjectValue(*valuesArray)); - if (!MaybeWrapObjectValue(cx, &valuesArrayVal)) { - capability.RejectWithException(cx, aRv); - return; - } - - js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT, - JS::Int32Value(1)); - js::SetReservedSlot(dataHolder, DATA_HOLDER_VALUES_ARRAY_SLOT, - valuesArrayVal); - js::SetReservedSlot(dataHolder, DATA_HOLDER_RESOLVE_FUNCTION_SLOT, - capability.mResolve); - - // Substep 5. - CheckedInt32 index = 0; - - // Substep 6. - JS::Rooted<JS::Value> nextValue(cx); - while (true) { - bool done; - // Steps a, b, c, e, f, g. - if (!iter.next(&nextValue, &done)) { - capability.RejectWithException(cx, aRv); - return; - } - - // Step d. - if (done) { - int32_t remainingCount = - js::GetReservedSlot(dataHolder, - DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32(); - remainingCount -= 1; - if (remainingCount == 0) { - JS::Rooted<JS::Value> ignored(cx); - if (!JS::Call(cx, JS::UndefinedHandleValue, capability.mResolve, - JS::HandleValueArray(valuesArrayVal), &ignored)) { - capability.RejectWithException(cx, aRv); - } - return; - } - js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT, - JS::Int32Value(remainingCount)); - // We're all set for now! - return; - } - - // Step h. - { // Scope for the JSAutoCompartment we need to work with valuesArray. We - // mostly do this for performance; we could go ahead and do the define via - // a cross-compartment proxy instead... - JSAutoCompartment ac(cx, valuesArray); - if (!JS_DefineElement(cx, valuesArray, index.value(), - JS::UndefinedHandleValue, JSPROP_ENUMERATE)) { - // Have to go back into the caller compartment before we try to touch - // capability.mReject. Luckily, capability.mReject is guaranteed to be - // an object in the right compartment here. - JSAutoCompartment ac2(cx, &capability.mReject.toObject()); - capability.RejectWithException(cx, aRv); - return; - } - } - - // Step i. Sadly, we can't take a shortcut here even if - // capability.mNativePromise exists, because someone could have overridden - // "resolve" on the canonical Promise constructor. - JS::Rooted<JS::Value> nextPromise(cx); - if (!JS_CallFunctionName(cx, constructorObj, "resolve", - JS::HandleValueArray(nextValue), - &nextPromise)) { - // Step j. - capability.RejectWithException(cx, aRv); - return; - } - - // Step k. - JS::Rooted<JSObject*> resolveElement(cx); - JSFunction* resolveFunc = - js::NewFunctionWithReserved(cx, PromiseAllResolveElement, - 1 /* nargs */, 0 /* flags */, nullptr); - if (!resolveFunc) { - capability.RejectWithException(cx, aRv); - return; - } - - resolveElement = JS_GetFunctionObject(resolveFunc); - // Steps l-p. - js::SetFunctionNativeReserved(resolveElement, - RESOLVE_ELEMENT_INDEX_SLOT, - JS::Int32Value(index.value())); - js::SetFunctionNativeReserved(resolveElement, - RESOLVE_ELEMENT_DATA_HOLDER_SLOT, - JS::ObjectValue(*dataHolder)); - - // Step q. - int32_t remainingElements = - js::GetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT).toInt32(); - js::SetReservedSlot(dataHolder, DATA_HOLDER_REMAINING_ELEMENTS_SLOT, - JS::Int32Value(remainingElements + 1)); - - // Step r. And now we don't know whether nextPromise has an overridden - // "then" method, so no shortcuts here either. - callbackFunctions[0].setObject(*resolveElement); - JS::Rooted<JSObject*> nextPromiseObj(cx); - JS::Rooted<JS::Value> ignored(cx); - if (!JS_ValueToObject(cx, nextPromise, &nextPromiseObj) || - !JS_CallFunctionName(cx, nextPromiseObj, "then", callbackFunctions, - &ignored)) { - // Step s. - capability.RejectWithException(cx, aRv); - } - - // Step t. - index += 1; - if (!index.isValid()) { - // Let's just claim OOM. - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - capability.RejectWithException(cx, aRv); - } - } + return Promise::All(aGlobal, promiseList, aRv); } /* static */ already_AddRefed<Promise> Promise::All(const GlobalObject& aGlobal, const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv) { nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); @@ -1851,125 +1024,57 @@ Promise::All(const GlobalObject& aGlobal // Every promise gets its own resolve callback, which will set the right // index in the array to the resolution value. aPromiseList[i]->AppendCallbacks(resolveCb, rejectCb); } return promise.forget(); } -/* static */ void -Promise::Race(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv) +/* static */ already_AddRefed<Promise> +Promise::Race(const GlobalObject& aGlobal, + const Sequence<JS::Value>& aIterable, ErrorResult& aRv) { - // Implements http://www.ecma-international.org/ecma-262/6.0/#sec-promise.race nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); if (!global) { aRv.Throw(NS_ERROR_UNEXPECTED); - return; + return nullptr; } JSContext* cx = aGlobal.Context(); - // Steps 1-5: nothing to do. Note that the @@species bits got removed in - // https://github.com/tc39/ecma262/pull/211 - PromiseCapability capability(cx); - - // Step 6. - NewPromiseCapability(cx, global, aThisv, true, capability, aRv); - // Step 7. - if (aRv.Failed()) { - return; + JS::Rooted<JSObject*> obj(cx, JS::CurrentGlobalOrNull(cx)); + if (!obj) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; } - MOZ_ASSERT(aThisv.isObject(), "How did NewPromiseCapability succeed?"); - JS::Rooted<JSObject*> constructorObj(cx, &aThisv.toObject()); - - // After this point we have a useful promise value in "capability", so just go - // ahead and put it in our retval now. Every single return path below would - // want to do that anyway. - aRetval.set(capability.PromiseValue()); - if (!MaybeWrapValue(cx, aRetval)) { - aRv.NoteJSContextException(); - return; - } - - // The arguments we're going to be passing to "then" on each loop iteration. - JS::AutoValueArray<2> callbackFunctions(cx); - callbackFunctions[0].set(capability.mResolve); - callbackFunctions[1].set(capability.mReject); - - // Steps 8 and 9. - JS::ForOfIterator iter(cx); - if (!iter.init(aIterable, JS::ForOfIterator::AllowNonIterable)) { - capability.RejectWithException(cx, aRv); - return; - } - - if (!iter.valueIsIterable()) { - ThrowErrorMessage(cx, MSG_PROMISE_ARG_NOT_ITERABLE, - "Argument of Promise.race"); - capability.RejectWithException(cx, aRv); - return; + RefPtr<Promise> promise = Create(global, aRv); + if (aRv.Failed()) { + return nullptr; } - // Step 10 doesn't need to be done, because ForOfIterator handles it - // for us. + RefPtr<PromiseCallback> resolveCb = + new ResolvePromiseCallback(promise, obj); - // Now we jump over to - // http://www.ecma-international.org/ecma-262/6.0/#sec-performpromiserace - // and do its steps. - JS::Rooted<JS::Value> nextValue(cx); - while (true) { - bool done; - // Steps a, b, c, e, f, g. - if (!iter.next(&nextValue, &done)) { - capability.RejectWithException(cx, aRv); - return; - } - - // Step d. - if (done) { - // We're all set! - return; - } + RefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(promise, obj); - // Step h. Sadly, we can't take a shortcut here even if - // capability.mNativePromise exists, because someone could have overridden - // "resolve" on the canonical Promise constructor. - JS::Rooted<JS::Value> nextPromise(cx); - if (!JS_CallFunctionName(cx, constructorObj, "resolve", - JS::HandleValueArray(nextValue), &nextPromise)) { - // Step i. - capability.RejectWithException(cx, aRv); - return; - } + for (uint32_t i = 0; i < aIterable.Length(); ++i) { + JS::Rooted<JS::Value> value(cx, aIterable.ElementAt(i)); + RefPtr<Promise> nextPromise = Promise::Resolve(aGlobal, value, aRv); + // According to spec, Resolve can throw, but our implementation never does. + // Well it does when window isn't passed on the main thread, but that is an + // implementation detail which should never be reached since we are checking + // for window above. Remove this when subclassing is supported. + MOZ_ASSERT(!aRv.Failed()); + nextPromise->AppendCallbacks(resolveCb, rejectCb); + } - // Step j. And now we don't know whether nextPromise has an overridden - // "then" method, so no shortcuts here either. - JS::Rooted<JSObject*> nextPromiseObj(cx); - JS::Rooted<JS::Value> ignored(cx); - if (!JS_ValueToObject(cx, nextPromise, &nextPromiseObj) || - !JS_CallFunctionName(cx, nextPromiseObj, "then", callbackFunctions, - &ignored)) { - // Step k. - capability.RejectWithException(cx, aRv); - } - } -} - -/* static */ -bool -Promise::PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp) -{ - JS::CallArgs args = CallArgsFromVp(aArgc, aVp); - args.rval().set(args.thisv()); - return true; + return promise.forget(); } void Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable) { RefPtr<PromiseCallback> resolveCb = new NativePromiseCallback(aRunnable, Resolved); @@ -2172,35 +1277,45 @@ Promise::ResolveInternal(JSContext* aCx, HandleException(aCx); return; } if (then.isObject() && JS::IsCallable(&then.toObject())) { // This is the then() function of the thenable aValueObj. JS::Rooted<JSObject*> thenObj(aCx, &then.toObject()); - // We used to have a fast path here for the case when the following - // requirements held: + // Add a fast path for the case when we're resolved with an actual + // Promise. This has two requirements: // // 1) valueObj is a Promise. // 2) thenObj is a JSFunction backed by our actual Promise::Then // implementation. // - // But now that we're doing subclassing in Promise.prototype.then we would - // also need the following requirements: - // - // 3) Getting valueObj.constructor has no side-effects. - // 4) Getting valueObj.constructor[@@species] has no side-effects. - // 5) valueObj.constructor[@@species] is a function and calling it has no - // side-effects (e.g. it's the canonical Promise constructor) and it - // provides some callback functions to call as arguments to its - // argument. - // - // Ensuring that stuff while not inside SpiderMonkey is painful, so let's - // drop the fast path for now. + // If those requirements are satisfied, then we know exactly what + // thenObj.call(valueObj) will do, so we can optimize a bit and avoid ever + // entering JS for this stuff. + Promise* nextPromise; + if (PromiseBinding::IsThenMethod(thenObj) && + NS_SUCCEEDED(UNWRAP_OBJECT(Promise, valueObj, nextPromise))) { + // If we were taking the codepath that involves PromiseResolveThenableJob and + // PromiseInit below, then eventually, in PromiseResolveThenableJob::Run, we + // would create some JSFunctions in the compartment of + // this->GetWrapper() and pass them to the PromiseInit. So by the time + // we'd see the resolution value it would be wrapped into the + // compartment of this->GetWrapper(). The global of that compartment is + // this->GetGlobalJSObject(), so use that as the global for + // ResolvePromiseCallback/RejectPromiseCallback. + JS::Rooted<JSObject*> glob(aCx, GlobalJSObject()); + RefPtr<PromiseCallback> resolveCb = new ResolvePromiseCallback(this, glob); + RefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(this, glob); + RefPtr<FastPromiseResolveThenableJob> task = + new FastPromiseResolveThenableJob(resolveCb, rejectCb, nextPromise); + DispatchToMicroTask(task); + return; + } RefPtr<PromiseInit> thenCallback = new PromiseInit(nullptr, thenObj, mozilla::dom::GetIncumbentGlobal()); RefPtr<PromiseResolveThenableJob> task = new PromiseResolveThenableJob(this, valueObj, thenCallback); DispatchToMicroTask(task); return; }
--- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -158,114 +158,81 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; static already_AddRefed<Promise> Constructor(const GlobalObject& aGlobal, PromiseInit& aInit, ErrorResult& aRv, JS::Handle<JSObject*> aDesiredProto); - static void - Resolve(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aValue, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv); + static already_AddRefed<Promise> + Resolve(const GlobalObject& aGlobal, + JS::Handle<JS::Value> aValue, ErrorResult& aRv); static already_AddRefed<Promise> Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv); - static void - Reject(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aValue, - JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv); + static already_AddRefed<Promise> + Reject(const GlobalObject& aGlobal, + JS::Handle<JS::Value> aValue, ErrorResult& aRv); static already_AddRefed<Promise> Reject(nsIGlobalObject* aGlobal, JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv); - void - Then(JSContext* aCx, - // aCalleeGlobal may not be in the compartment of aCx, when called over - // Xrays. - JS::Handle<JSObject*> aCalleeGlobal, - AnyCallback* aResolveCallback, AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv); + already_AddRefed<Promise> + Then(JSContext* aCx, AnyCallback* aResolveCallback, + AnyCallback* aRejectCallback, ErrorResult& aRv); - void - Catch(JSContext* aCx, - AnyCallback* aRejectCallback, - JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv); + already_AddRefed<Promise> + Catch(JSContext* aCx, AnyCallback* aRejectCallback, ErrorResult& aRv); - static void - All(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv); + static already_AddRefed<Promise> + All(const GlobalObject& aGlobal, + const Sequence<JS::Value>& aIterable, ErrorResult& aRv); static already_AddRefed<Promise> All(const GlobalObject& aGlobal, const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv); - static void - Race(const GlobalObject& aGlobal, JS::Handle<JS::Value> aThisv, - JS::Handle<JS::Value> aIterable, JS::MutableHandle<JS::Value> aRetval, - ErrorResult& aRv); - - static bool - PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp); + static already_AddRefed<Promise> + Race(const GlobalObject& aGlobal, + const Sequence<JS::Value>& aIterable, ErrorResult& aRv); void AppendNativeHandler(PromiseNativeHandler* aRunnable); JSObject* GlobalJSObject() const; JSCompartment* Compartment() const; // Return a unique-to-the-process identifier for this Promise. uint64_t GetID(); // Queue an async microtask to current main or worker thread. static void DispatchToMicroTask(nsIRunnable* aRunnable); - enum JSCallbackSlots { - SLOT_PROMISE = 0, - SLOT_DATA - }; - protected: - struct PromiseCapability; - // Do NOT call this unless you're Promise::Create. I wish we could enforce // that from inside this class too, somehow. explicit Promise(nsIGlobalObject* aGlobal); virtual ~Promise(); // Do JS-wrapping after Promise creation. Passing null for aDesiredProto will // use the default prototype for the sort of Promise we have. void CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv); // Create the JS resolving functions of resolve() and reject(). And provide // references to the two functions by calling PromiseInit passed from Promise // constructor. void CallInitFunction(const GlobalObject& aGlobal, PromiseInit& aInit, ErrorResult& aRv); - // The NewPromiseCapability function from - // <http://www.ecma-international.org/ecma-262/6.0/#sec-newpromisecapability>. - // Errors are communicated via aRv. If aForceCallbackCreation is - // true, then this function will ensure that aCapability has a - // useful mResolve/mReject even if mNativePromise is non-null. - static void NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal, - JS::Handle<JS::Value> aConstructor, - bool aForceCallbackCreation, - PromiseCapability& aCapability, - ErrorResult& aRv); - bool IsPending() { return mResolvePending; } void GetDependentPromises(nsTArray<RefPtr<Promise>>& aPromises); bool IsLastInChain() const
--- a/dom/promise/PromiseCallback.cpp +++ b/dom/promise/PromiseCallback.cpp @@ -4,18 +4,16 @@ * 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/. */ #include "PromiseCallback.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "jsapi.h" -#include "jsfriendapi.h" -#include "jswrapper.h" namespace mozilla { namespace dom { NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback) NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseCallback) @@ -151,123 +149,35 @@ RejectPromiseCallback::Call(JSContext* a return NS_ERROR_FAILURE; } mPromise->RejectInternal(aCx, value); return NS_OK; } -// InvokePromiseFuncCallback - -NS_IMPL_CYCLE_COLLECTION_CLASS(InvokePromiseFuncCallback) - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(InvokePromiseFuncCallback, - PromiseCallback) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromiseFunc) - tmp->mGlobal = nullptr; - tmp->mNextPromiseObj = nullptr; -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(InvokePromiseFuncCallback, - PromiseCallback) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromiseFunc) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(InvokePromiseFuncCallback) - NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) - NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNextPromiseObj) -NS_IMPL_CYCLE_COLLECTION_TRACE_END - -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(InvokePromiseFuncCallback) -NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) - -NS_IMPL_ADDREF_INHERITED(InvokePromiseFuncCallback, PromiseCallback) -NS_IMPL_RELEASE_INHERITED(InvokePromiseFuncCallback, PromiseCallback) - -InvokePromiseFuncCallback::InvokePromiseFuncCallback(JS::Handle<JSObject*> aGlobal, - JS::Handle<JSObject*> aNextPromiseObj, - AnyCallback* aPromiseFunc) - : mGlobal(aGlobal) - , mNextPromiseObj(aNextPromiseObj) - , mPromiseFunc(aPromiseFunc) -{ - MOZ_ASSERT(aGlobal); - MOZ_ASSERT(aNextPromiseObj); - MOZ_ASSERT(aPromiseFunc); - HoldJSObjects(this); -} - -InvokePromiseFuncCallback::~InvokePromiseFuncCallback() -{ - DropJSObjects(this); -} - -nsresult -InvokePromiseFuncCallback::Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) -{ - // Run resolver's algorithm with value and the synchronous flag set. - - JS::ExposeObjectToActiveJS(mGlobal); - JS::ExposeValueToActiveJS(aValue); - - JSAutoCompartment ac(aCx, mGlobal); - JS::Rooted<JS::Value> value(aCx, aValue); - if (!JS_WrapValue(aCx, &value)) { - NS_WARNING("Failed to wrap value into the right compartment."); - return NS_ERROR_FAILURE; - } - - ErrorResult rv; - JS::Rooted<JS::Value> ignored(aCx); - mPromiseFunc->Call(value, &ignored, rv); - // Useful exceptions already got reported. - rv.SuppressException(); - return NS_OK; -} - -Promise* -InvokePromiseFuncCallback::GetDependentPromise() -{ - Promise* promise; - if (NS_SUCCEEDED(UNWRAP_OBJECT(Promise, mNextPromiseObj, promise))) { - return promise; - } - - // Oh, well. - return nullptr; -} - // WrapperPromiseCallback NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WrapperPromiseCallback, PromiseCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextPromise) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveFunc) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectFunc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) tmp->mGlobal = nullptr; - tmp->mNextPromiseObj = nullptr; NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WrapperPromiseCallback, PromiseCallback) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNextPromise) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveFunc) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectFunc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WrapperPromiseCallback) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) - NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNextPromiseObj) NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WrapperPromiseCallback) NS_INTERFACE_MAP_END_INHERITING(PromiseCallback) NS_IMPL_ADDREF_INHERITED(WrapperPromiseCallback, PromiseCallback) NS_IMPL_RELEASE_INHERITED(WrapperPromiseCallback, PromiseCallback) @@ -278,34 +188,16 @@ WrapperPromiseCallback::WrapperPromiseCa , mGlobal(aGlobal) , mCallback(aCallback) { MOZ_ASSERT(aNextPromise); MOZ_ASSERT(aGlobal); HoldJSObjects(this); } -WrapperPromiseCallback::WrapperPromiseCallback(JS::Handle<JSObject*> aGlobal, - AnyCallback* aCallback, - JS::Handle<JSObject*> aNextPromiseObj, - AnyCallback* aResolveFunc, - AnyCallback* aRejectFunc) - : mNextPromiseObj(aNextPromiseObj) - , mResolveFunc(aResolveFunc) - , mRejectFunc(aRejectFunc) - , mGlobal(aGlobal) - , mCallback(aCallback) -{ - MOZ_ASSERT(mNextPromiseObj); - MOZ_ASSERT(aResolveFunc); - MOZ_ASSERT(aRejectFunc); - MOZ_ASSERT(aGlobal); - HoldJSObjects(this); -} - WrapperPromiseCallback::~WrapperPromiseCallback() { DropJSObjects(this); } nsresult WrapperPromiseCallback::Call(JSContext* aCx, JS::Handle<JS::Value> aValue) @@ -319,73 +211,46 @@ WrapperPromiseCallback::Call(JSContext* NS_WARNING("Failed to wrap value into the right compartment."); return NS_ERROR_FAILURE; } ErrorResult rv; // PromiseReactionTask step 6 JS::Rooted<JS::Value> retValue(aCx); - JSCompartment* compartment; - if (mNextPromise) { - compartment = mNextPromise->Compartment(); - } else { - MOZ_ASSERT(mNextPromiseObj); - compartment = js::GetObjectCompartment(mNextPromiseObj); - } mCallback->Call(value, &retValue, rv, "promise callback", CallbackObject::eRethrowExceptions, - compartment); + mNextPromise->Compartment()); rv.WouldReportJSException(); // PromiseReactionTask step 7 if (rv.Failed()) { JS::Rooted<JS::Value> value(aCx); { // Scope for JSAutoCompartment // Convert the ErrorResult to a JS exception object that we can reject // ourselves with. This will be exactly the exception that would get // thrown from a binding method whose ErrorResult ended up with whatever - // is on "rv" right now. Do this in the promise reflector compartment. - Maybe<JSAutoCompartment> ac; - if (mNextPromise) { - ac.emplace(aCx, mNextPromise->GlobalJSObject()); - } else { - ac.emplace(aCx, mNextPromiseObj); - } + // is on "rv" right now. + JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject()); DebugOnly<bool> conversionResult = ToJSValue(aCx, rv, &value); MOZ_ASSERT(conversionResult); } - if (mNextPromise) { - mNextPromise->RejectInternal(aCx, value); - } else { - JS::Rooted<JS::Value> ignored(aCx); - ErrorResult rejectRv; - mRejectFunc->Call(value, &ignored, rejectRv); - // This reported any JS exceptions; we just have a pointless exception on - // there now. - rejectRv.SuppressException(); - } + mNextPromise->RejectInternal(aCx, value); return NS_OK; } // If the return value is the same as the promise itself, throw TypeError. if (retValue.isObject()) { JS::Rooted<JSObject*> valueObj(aCx, &retValue.toObject()); - valueObj = js::CheckedUnwrap(valueObj); - JS::Rooted<JSObject*> nextPromiseObj(aCx); - if (mNextPromise) { - nextPromiseObj = mNextPromise->GetWrapper(); - } else { - MOZ_ASSERT(mNextPromiseObj); - nextPromiseObj = mNextPromiseObj; - } - // XXXbz shouldn't this check be over in ResolveInternal anyway? - if (valueObj == nextPromiseObj) { + Promise* returnedPromise; + nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise); + + if (NS_SUCCEEDED(r) && returnedPromise == mNextPromise) { const char* fileName = nullptr; uint32_t lineNumber = 0; // Try to get some information about the callback to report a sane error, // but don't try too hard (only deals with scripted functions). JS::Rooted<JSObject*> unwrapped(aCx, js::CheckedUnwrap(mCallback->Callback())); @@ -424,86 +289,31 @@ WrapperPromiseCallback::Call(JSContext* JS::Rooted<JS::Value> typeError(aCx); if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, fn, lineNumber, 0, nullptr, message, &typeError)) { // Out of memory. Promise will stay unresolved. JS_ClearPendingException(aCx); return NS_ERROR_OUT_OF_MEMORY; } - if (mNextPromise) { - mNextPromise->RejectInternal(aCx, typeError); - } else { - JS::Rooted<JS::Value> ignored(aCx); - ErrorResult rejectRv; - mRejectFunc->Call(typeError, &ignored, rejectRv); - // This reported any JS exceptions; we just have a pointless exception - // on there now. - rejectRv.SuppressException(); - } + mNextPromise->RejectInternal(aCx, typeError); return NS_OK; } } // Otherwise, run resolver's resolve with value. if (!JS_WrapValue(aCx, &retValue)) { NS_WARNING("Failed to wrap value into the right compartment."); return NS_ERROR_FAILURE; } - if (mNextPromise) { - mNextPromise->ResolveInternal(aCx, retValue); - } else { - JS::Rooted<JS::Value> ignored(aCx); - ErrorResult resolveRv; - mResolveFunc->Call(retValue, &ignored, resolveRv); - // This reported any JS exceptions; we just have a pointless exception - // on there now. - resolveRv.SuppressException(); - } - + mNextPromise->ResolveInternal(aCx, retValue); return NS_OK; } -Promise* -WrapperPromiseCallback::GetDependentPromise() -{ - // Per spec, various algorithms like all() and race() are actually implemented - // in terms of calling then() but passing it the resolve/reject functions that - // are passed as arguments to function passed to the Promise constructor. - // That will cause the promise in question to hold on to a - // WrapperPromiseCallback, but the dependent promise should really be the one - // whose constructor those functions came from, not the about-to-be-ignored - // return value of "then". So try to determine whether we're in that case and - // if so go ahead and dig the dependent promise out of the function we have. - JSObject* callable = mCallback->Callable(); - // Unwrap it, in case it's a cross-compartment wrapper. Our caller here is - // system, so it's really ok to just go and unwrap. - callable = js::UncheckedUnwrap(callable); - if (JS_IsNativeFunction(callable, Promise::JSCallback)) { - JS::Value promiseVal = - js::GetFunctionNativeReserved(callable, Promise::SLOT_PROMISE); - Promise* promise; - UNWRAP_OBJECT(Promise, &promiseVal.toObject(), promise); - return promise; - } - - if (mNextPromise) { - return mNextPromise; - } - - Promise* promise; - if (NS_SUCCEEDED(UNWRAP_OBJECT(Promise, mNextPromiseObj, promise))) { - return promise; - } - - // Oh, well. - return nullptr; -} - // NativePromiseCallback NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback, PromiseCallback, mHandler) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback) NS_INTERFACE_MAP_END_INHERITING(PromiseCallback)
--- a/dom/promise/PromiseCallback.h +++ b/dom/promise/PromiseCallback.h @@ -40,57 +40,40 @@ public: // This factory returns a PromiseCallback object with refcount of 0. static PromiseCallback* Factory(Promise* aNextPromise, JS::Handle<JSObject*> aObject, AnyCallback* aCallback, Task aTask); }; // WrapperPromiseCallback execs a JS Callback with a value, and then the return -// value is sent to either: -// a) If aNextPromise is non-null, the aNextPromise->ResolveFunction() or to -// aNextPromise->RejectFunction() if the JS Callback throws. -// or -// b) If aNextPromise is null, in which case aResolveFunc and aRejectFunc must -// be non-null, then to aResolveFunc, unless aCallback threw, in which case -// aRejectFunc. +// value is sent to the aNextPromise->ResolveFunction() or to +// aNextPromise->RejectFunction() if the JS Callback throws. class WrapperPromiseCallback final : public PromiseCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WrapperPromiseCallback, PromiseCallback) nsresult Call(JSContext* aCx, JS::Handle<JS::Value> aValue) override; - Promise* GetDependentPromise() override; + Promise* GetDependentPromise() override + { + return mNextPromise; + } - // Constructor for when we know we have a vanilla Promise. WrapperPromiseCallback(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal, AnyCallback* aCallback); - // Constructor for when all we have to work with are resolve/reject functions. - WrapperPromiseCallback(JS::Handle<JSObject*> aGlobal, - AnyCallback* aCallback, - JS::Handle<JSObject*> mNextPromiseObj, - AnyCallback* aResolveFunc, - AnyCallback* aRejectFunc); - private: ~WrapperPromiseCallback(); - // Either mNextPromise is non-null or all three of mNextPromiseObj, - // mResolveFund and mRejectFunc must are non-null. RefPtr<Promise> mNextPromise; - // mNextPromiseObj is the reflector itself; it may not be in the - // same compartment as anything else we have. - JS::Heap<JSObject*> mNextPromiseObj; - RefPtr<AnyCallback> mResolveFunc; - RefPtr<AnyCallback> mRejectFunc; JS::Heap<JSObject*> mGlobal; RefPtr<AnyCallback> mCallback; }; // ResolvePromiseCallback calls aPromise->ResolveFunction() with the value // received by Call(). class ResolvePromiseCallback final : public PromiseCallback { @@ -137,42 +120,16 @@ public: private: ~RejectPromiseCallback(); RefPtr<Promise> mPromise; JS::Heap<JSObject*> mGlobal; }; -// InvokePromiseFuncCallback calls the given function with the value -// received by Call(). -class InvokePromiseFuncCallback final : public PromiseCallback -{ -public: - NS_DECL_ISUPPORTS_INHERITED - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(InvokePromiseFuncCallback, - PromiseCallback) - - nsresult Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) override; - - Promise* GetDependentPromise() override; - - InvokePromiseFuncCallback(JS::Handle<JSObject*> aGlobal, - JS::Handle<JSObject*> aNextPromiseObj, - AnyCallback* aPromiseFunc); - -private: - ~InvokePromiseFuncCallback(); - - JS::Heap<JSObject*> mGlobal; - JS::Heap<JSObject*> mNextPromiseObj; - RefPtr<AnyCallback> mPromiseFunc; -}; - // NativePromiseCallback wraps a PromiseNativeHandler. class NativePromiseCallback final : public PromiseCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NativePromiseCallback, PromiseCallback)
--- a/dom/promise/PromiseDebugging.cpp +++ b/dom/promise/PromiseDebugging.cpp @@ -9,17 +9,16 @@ #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/ThreadLocal.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/Promise.h" -#include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/PromiseDebugging.h" #include "mozilla/dom/PromiseDebuggingBinding.h" namespace mozilla { namespace dom { class FlushRejections: public nsCancelableRunnable { @@ -61,49 +60,33 @@ private: // `true` if an instance of `FlushRejections` is currently dispatched // and has not been executed yet. static ThreadLocal<bool> sDispatched; }; /* static */ ThreadLocal<bool> FlushRejections::sDispatched; -static Promise* -UnwrapPromise(JS::Handle<JSObject*> aPromise, ErrorResult& aRv) +/* static */ void +PromiseDebugging::GetState(GlobalObject&, Promise& aPromise, + PromiseDebuggingStateHolder& aState) { - Promise* promise; - if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Promise, aPromise, promise)))) { - aRv.ThrowTypeError<MSG_IS_NOT_PROMISE>(NS_LITERAL_STRING("Argument")); - return nullptr; - } - return promise; -} - -/* static */ void -PromiseDebugging::GetState(GlobalObject&, JS::Handle<JSObject*> aPromise, - PromiseDebuggingStateHolder& aState, - ErrorResult& aRv) -{ - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return; - } - switch (promise->mState) { + switch (aPromise.mState) { case Promise::Pending: aState.mState = PromiseDebuggingState::Pending; break; case Promise::Resolved: aState.mState = PromiseDebuggingState::Fulfilled; - JS::ExposeValueToActiveJS(promise->mResult); - aState.mValue = promise->mResult; + JS::ExposeValueToActiveJS(aPromise.mResult); + aState.mValue = aPromise.mResult; break; case Promise::Rejected: aState.mState = PromiseDebuggingState::Rejected; - JS::ExposeValueToActiveJS(promise->mResult); - aState.mReason = promise->mResult; + JS::ExposeValueToActiveJS(aPromise.mResult); + aState.mReason = aPromise.mResult; break; } } /*static */ nsString PromiseDebugging::sIDPrefix; /* static */ void @@ -130,89 +113,59 @@ PromiseDebugging::Shutdown() /* static */ void PromiseDebugging::FlushUncaughtRejections() { MOZ_ASSERT(!NS_IsMainThread()); FlushRejections::FlushSync(); } /* static */ void -PromiseDebugging::GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise, - JS::MutableHandle<JSObject*> aStack, - ErrorResult& aRv) +PromiseDebugging::GetAllocationStack(GlobalObject&, Promise& aPromise, + JS::MutableHandle<JSObject*> aStack) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return; - } - aStack.set(promise->mAllocationStack); + aStack.set(aPromise.mAllocationStack); } /* static */ void -PromiseDebugging::GetRejectionStack(GlobalObject&, JS::Handle<JSObject*> aPromise, - JS::MutableHandle<JSObject*> aStack, - ErrorResult& aRv) +PromiseDebugging::GetRejectionStack(GlobalObject&, Promise& aPromise, + JS::MutableHandle<JSObject*> aStack) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return; - } - aStack.set(promise->mRejectionStack); + aStack.set(aPromise.mRejectionStack); } /* static */ void -PromiseDebugging::GetFullfillmentStack(GlobalObject&, JS::Handle<JSObject*> aPromise, - JS::MutableHandle<JSObject*> aStack, - ErrorResult& aRv) +PromiseDebugging::GetFullfillmentStack(GlobalObject&, Promise& aPromise, + JS::MutableHandle<JSObject*> aStack) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return; - } - aStack.set(promise->mFullfillmentStack); + aStack.set(aPromise.mFullfillmentStack); } /* static */ void -PromiseDebugging::GetDependentPromises(GlobalObject&, JS::Handle<JSObject*> aPromise, - nsTArray<RefPtr<Promise>>& aPromises, - ErrorResult& aRv) +PromiseDebugging::GetDependentPromises(GlobalObject&, Promise& aPromise, + nsTArray<RefPtr<Promise>>& aPromises) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return; - } - promise->GetDependentPromises(aPromises); + aPromise.GetDependentPromises(aPromises); } /* static */ double -PromiseDebugging::GetPromiseLifetime(GlobalObject&, - JS::Handle<JSObject*> aPromise, - ErrorResult& aRv) +PromiseDebugging::GetPromiseLifetime(GlobalObject&, Promise& aPromise) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return 0; - } - return (TimeStamp::Now() - promise->mCreationTimestamp).ToMilliseconds(); + return (TimeStamp::Now() - aPromise.mCreationTimestamp).ToMilliseconds(); } /* static */ double -PromiseDebugging::GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise, +PromiseDebugging::GetTimeToSettle(GlobalObject&, Promise& aPromise, ErrorResult& aRv) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return 0; - } - if (promise->mState == Promise::Pending) { + if (aPromise.mState == Promise::Pending) { aRv.Throw(NS_ERROR_UNEXPECTED); return 0; } - return (promise->mSettlementTimestamp - - promise->mCreationTimestamp).ToMilliseconds(); + return (aPromise.mSettlementTimestamp - + aPromise.mCreationTimestamp).ToMilliseconds(); } /* static */ void PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&, UncaughtRejectionObserver& aObserver) { CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get(); nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers; @@ -246,25 +199,20 @@ PromiseDebugging::AddUncaughtRejection(P PromiseDebugging::AddConsumedRejection(Promise& aPromise) { CycleCollectedJSRuntime::Get()->mConsumedRejections.AppendElement(&aPromise); FlushRejections::DispatchNeeded(); } /* static */ void PromiseDebugging::GetPromiseID(GlobalObject&, - JS::Handle<JSObject*> aPromise, - nsString& aID, - ErrorResult& aRv) + Promise& aPromise, + nsString& aID) { - Promise* promise = UnwrapPromise(aPromise, aRv); - if (aRv.Failed()) { - return; - } - uint64_t promiseID = promise->GetID(); + uint64_t promiseID = aPromise.GetID(); aID = sIDPrefix; aID.AppendInt(promiseID); } /* static */ void PromiseDebugging::FlushUncaughtRejectionsInternal() { CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
--- a/dom/promise/PromiseDebugging.h +++ b/dom/promise/PromiseDebugging.h @@ -27,42 +27,32 @@ class UncaughtRejectionObserver; class FlushRejections; class PromiseDebugging { public: static void Init(); static void Shutdown(); - static void GetState(GlobalObject&, JS::Handle<JSObject*> aPromise, - PromiseDebuggingStateHolder& aState, - ErrorResult& aRv); + static void GetState(GlobalObject&, Promise& aPromise, + PromiseDebuggingStateHolder& aState); - static void GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise, - JS::MutableHandle<JSObject*> aStack, - ErrorResult& aRv); - static void GetRejectionStack(GlobalObject&, JS::Handle<JSObject*> aPromise, - JS::MutableHandle<JSObject*> aStack, - ErrorResult& aRv); - static void GetFullfillmentStack(GlobalObject&, - JS::Handle<JSObject*> aPromise, - JS::MutableHandle<JSObject*> aStack, - ErrorResult& aRv); - static void GetDependentPromises(GlobalObject&, - JS::Handle<JSObject*> aPromise, - nsTArray<RefPtr<Promise>>& aPromises, - ErrorResult& aRv); - static double GetPromiseLifetime(GlobalObject&, - JS::Handle<JSObject*> aPromise, - ErrorResult& aRv); - static double GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise, + static void GetAllocationStack(GlobalObject&, Promise& aPromise, + JS::MutableHandle<JSObject*> aStack); + static void GetRejectionStack(GlobalObject&, Promise& aPromise, + JS::MutableHandle<JSObject*> aStack); + static void GetFullfillmentStack(GlobalObject&, Promise& aPromise, + JS::MutableHandle<JSObject*> aStack); + static void GetDependentPromises(GlobalObject&, Promise& aPromise, + nsTArray<RefPtr<Promise>>& aPromises); + static double GetPromiseLifetime(GlobalObject&, Promise& aPromise); + static double GetTimeToSettle(GlobalObject&, Promise& aPromise, ErrorResult& aRv); - static void GetPromiseID(GlobalObject&, JS::Handle<JSObject*>, nsString&, - ErrorResult&); + static void GetPromiseID(GlobalObject&, Promise&, nsString&); // Mechanism for watching uncaught instances of Promise. static void AddUncaughtRejectionObserver(GlobalObject&, UncaughtRejectionObserver& aObserver); static bool RemoveUncaughtRejectionObserver(GlobalObject&, UncaughtRejectionObserver& aObserver); // Mark a Promise as having been left uncaught at script completion.
--- a/dom/promise/tests/chrome.ini +++ b/dom/promise/tests/chrome.ini @@ -1,8 +1,7 @@ [DEFAULT] skip-if = buildapp == 'b2g' [test_dependentPromises.html] [test_on_new_promise.html] [test_on_promise_settled.html] [test_on_promise_settled_duplicates.html] -[test_promise_xrays.html]
deleted file mode 100644 --- a/dom/promise/tests/file_promise_xrays.html +++ /dev/null @@ -1,30 +0,0 @@ -<!DOCTYPE html> -<html> - <script> - function vendGetter(name) { - return function() { throw "Getting " + String(name) }; - } - function vendSetter(name) { - return function() { throw "Setting " + String(name) }; - } - var setupThrew = false; - try { - // Neuter everything we can think of on Promise. - for (var obj of [Promise, Promise.prototype]) { - propNames = Object.getOwnPropertyNames(obj); - propNames = propNames.concat(Object.getOwnPropertySymbols(obj)); - for (var propName of propNames) { - if (propName == "prototype" && obj == Promise) { - // It's not configurable - continue; - } - Object.defineProperty(obj, propName, - { get: vendGetter(propName), set: vendSetter(propName) }); - } - } - } catch (e) { - // Something went wrong. Save that info so the test can check for it. - setupThrew = e; - } - </script> -</html>
--- a/dom/promise/tests/mochitest.ini +++ b/dom/promise/tests/mochitest.ini @@ -1,17 +1,13 @@ [DEFAULT] -# Support files for chrome tests that we want to load over HTTP need -# to go in here, not chrome.ini, apparently. -support-files = file_promise_xrays.html [test_abortable_promise.html] [test_bug883683.html] [test_promise.html] [test_promise_utils.html] [test_resolve.html] [test_resolver_return_value.html] [test_thenable_vs_promise_ordering.html] [test_promise_and_timeout_ordering.html] support-files = file_promise_and_timeout_ordering.js [test_promise_and_timeout_ordering_workers.html] support-files = file_promise_and_timeout_ordering.js -[test_species_getter.html]
--- a/dom/promise/tests/test_bug883683.html +++ b/dom/promise/tests/test_bug883683.html @@ -12,20 +12,20 @@ <p id="display"></p> <div id="content" style="display: none"> </div> <pre id="test"> <script type="application/javascript"><!-- function runTest() { - [{}, {}, {}, {}, {}].reduce(Promise.reject.bind(Promise)); + [{}, {}, {}, {}, {}].reduce(Promise.reject); ok(true, "No leaks with reject?"); - [{}, {}, {}, {}, {}].reduce(Promise.resolve.bind(Promise)); + [{}, {}, {}, {}, {}].reduce(Promise.resolve); ok(true, "No leaks with resolve?"); [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { throw a; }); }); ok(true, "No leaks with exception?"); [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); }); ok(true, "No leaks with empty promise?");
deleted file mode 100644 --- a/dom/promise/tests/test_promise_xrays.html +++ /dev/null @@ -1,301 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=1170760 ---> -<head> - <meta charset="utf-8"> - <title>Test for Bug 1170760</title> - <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="chrome://global/skin"/> - <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> -</head> -<body> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170760">Mozilla Bug 1170760</a> -<p id="display"></p> -<div id="content" style="display: none"> -<iframe id="t" src="http://example.org/tests/dom/promise/tests/file_promise_xrays.html"></iframe> -</div> - -<pre id="test"> -<script type="application/javascript"> - -var win = $("t").contentWindow; - -/** Test for Bug 1170760 **/ -SimpleTest.waitForExplicitFinish(); - -function testLoadComplete() { - is(win.location.href, $("t").src, "Should have loaded the right thing"); - nextTest(); -} - -function testHaveXray() { - is(typeof win.Promise.race, "function", "Should see a race() function"); - var exception; - try { - win.Promise.wrappedJSObject.race; - } catch (e) { - exception = e; - } - is(exception, "Getting race", "Should have thrown the right exception"); - is(win.wrappedJSObject.setupThrew, false, "Setup should not have thrown"); - nextTest(); -} - -function testRace1() { - var p = win.Promise.race(new win.Array(1, 2)); - p.then( - function(arg) { - ok(arg == 1 || arg == 2, - "Should get the right value when racing content-side array"); - }, - function(e) { - ok(false, "testRace1 threw exception: " + e); - } - ).then(nextTest); -} - -function testRace2() { - var p = win.Promise.race( - new Array(win.Promise.resolve(1), win.Promise.resolve(2))); - p.then( - function(arg) { - ok(arg == 1 || arg == 2, - "Should get the right value when racing content-side array of explicit Promises"); - }, - function(e) { - ok(false, "testRace2 threw exception: " + e); - } - ).then(nextTest); -} - -function testRace3() { - // This works with a chrome-side array because we do the iteration - // while still in the Xray compartment. - var p = win.Promise.race([1, 2]); - p.then( - function(arg) { - ok(arg == 1 || arg == 2, - "Should get the right value when racing chrome-side array"); - }, - function(e) { - ok(false, "testRace3 threw exception: " + e); - } - ).then(nextTest); -} - -function testRace4() { - // This works with both content-side and chrome-side Promises because we want - // it to and go to some lengths to make it work. - var p = win.Promise.race([Promise.resolve(1), win.Promise.resolve(2)]); - p.then( - function(arg) { - ok(arg == 1 || arg == 2, - "Should get the right value when racing chrome-side promises"); - }, - function(e) { - ok(false, "testRace4 threw exception: " + e); - } - ).then(nextTest); -} - -function testAll1() { - var p = win.Promise.all(new win.Array(1, 2)); - p.then( - function(arg) { - ok(arg instanceof win.Array, "Should get an Array from Promise.all (1)"); - is(arg[0], 1, "First entry of Promise.all return value should be correct (1)"); - is(arg[1], 2, "Second entry of Promise.all return value should be correct (1)"); - }, - function(e) { - ok(false, "testAll1 threw exception: " + e); - } - ).then(nextTest); -} - -function testAll2() { - var p = win.Promise.all( - new Array(win.Promise.resolve(1), win.Promise.resolve(2))); - p.then( - function(arg) { - ok(arg instanceof win.Array, "Should get an Array from Promise.all (2)"); - is(arg[0], 1, "First entry of Promise.all return value should be correct (2)"); - is(arg[1], 2, "Second entry of Promise.all return value should be correct (2)"); - }, - function(e) { - ok(false, "testAll2 threw exception: " + e); - } - ).then(nextTest); -} - -function testAll3() { - // This works with a chrome-side array because we do the iteration - // while still in the Xray compartment. - var p = win.Promise.all([1, 2]); - p.then( - function(arg) { - ok(arg instanceof win.Array, "Should get an Array from Promise.all (3)"); - is(arg[0], 1, "First entry of Promise.all return value should be correct (3)"); - is(arg[1], 2, "Second entry of Promise.all return value should be correct (3)"); - }, - function(e) { - ok(false, "testAll3 threw exception: " + e); - } - ).then(nextTest); -} - -function testAll4() { - // This works with both content-side and chrome-side Promises because we want - // it to and go to some lengths to make it work. - var p = win.Promise.all([Promise.resolve(1), win.Promise.resolve(2)]); - p.then( - function(arg) { - ok(arg instanceof win.Array, "Should get an Array from Promise.all (4)"); - is(arg[0], 1, "First entry of Promise.all return value should be correct (4)"); - is(arg[1], 2, "Second entry of Promise.all return value should be correct (4)"); - }, - function(e) { - ok(false, "testAll4 threw exception: " + e); - } - ).then(nextTest); -} - -function testAll5() { - var p = win.Promise.all(new win.Array()); - p.then( - function(arg) { - ok(arg instanceof win.Array, "Should get an Array from Promise.all (5)"); - }, - function(e) { - ok(false, "testAll5 threw exception: " + e); - } - ).then(nextTest); -} - -function testResolve1() { - var p = win.Promise.resolve(5); - ok(p instanceof win.Promise, "Promise.resolve should return a promise"); - p.then( - function(arg) { - is(arg, 5, "Should get correct Promise.resolve value"); - }, - function(e) { - ok(false, "testAll5 threw exception: " + e); - } - ).then(nextTest); -} - -function testResolve2() { - var p = win.Promise.resolve(5); - var q = win.Promise.resolve(p); - is(q, p, "Promise.resolve should just pass through Promise values"); - nextTest(); -} - -function testResolve3() { - var p = win.Promise.resolve(Promise.resolve(5)); - p.then( - function(arg) { - is(arg, 5, "Promise.resolve with chrome Promise should work"); - }, - function(e) { - ok(false, "Promise.resolve with chrome Promise should not fail"); - } - ).then(nextTest); -} - -function testReject1() { - var p = win.Promise.reject(5); - ok(p instanceof win.Promise, "Promise.reject should return a promise"); - p.then( - function(arg) { - ok(false, "Promise should be rejected"); - }, - function(e) { - is(e, 5, "Should get correct Promise.reject value"); - } - ).then(nextTest); -} - -function testThen1() { - var p = win.Promise.resolve(5); - var q = p.then((x) => x*x); - ok(q instanceof win.Promise, - "Promise.then should return a promise from the right global"); - q.then( - function(arg) { - is(arg, 25, "Promise.then should work"); - }, - function(e) { - ok(false, "Promise.then should not fail"); - } - ).then(nextTest); -} - -function testThen2() { - var p = win.Promise.resolve(5); - var q = p.then((x) => Promise.resolve(x*x)); - ok(q instanceof win.Promise, - "Promise.then should return a promise from the right global"); - q.then( - function(arg) { - is(arg, 25, "Promise.then resolved with chrome promise should work"); - }, - function(e) { - ok(false, "Promise.then resolved with chrome promise should not fail"); - } - ).then(nextTest); -} - -function testCatch1() { - var p = win.Promise.reject(5); - ok(p instanceof win.Promise, "Promise.resolve should return a promise"); - var q = p.catch((x) => x*x); - ok(q instanceof win.Promise, - "Promise.catch should return a promise from the right global"); - q.then( - function(arg) { - is(arg, 25, "Promise.catch should work"); - }, - function(e) { - ok(false, "Promise.catch should not fail"); - } - ).then(nextTest); -} - -var tests = [ - testLoadComplete, - testHaveXray, - testRace1, - testRace2, - testRace3, - testRace4, - testAll1, - testAll2, - testAll3, - testAll4, - testAll5, - testResolve1, - testResolve2, - testResolve3, - testReject1, - testThen1, - testThen2, - testCatch1, -]; - -function nextTest() { - if (tests.length == 0) { - SimpleTest.finish(); - return; - } - tests.shift()(); -} - -addLoadEvent(nextTest); - -</script> -</pre> -</body> -</html>
deleted file mode 100644 --- a/dom/promise/tests/test_species_getter.html +++ /dev/null @@ -1,24 +0,0 @@ -<!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> -test(function() { - var desc = Object.getOwnPropertyDescriptor(Promise, Symbol.species); - assert_not_equals(desc, undefined, "Should have a property"); - - assert_equals(desc.configurable, true, "Property should be configurable"); - assert_equals(desc.enumerable, false, "Property should not be enumerable"); - assert_equals(desc.set, undefined, "Should not have a setter"); - var getter = desc.get; - - var things = [undefined, null, 5, "xyz", Promise, Object]; - for (var thing of things) { - assert_equals(getter.call(thing), thing, - "Getter should return its this value"); - } - -}, "Promise should have an @@species getter that works per spec"); -</script>
--- a/dom/webidl/DOMRequest.webidl +++ b/dom/webidl/DOMRequest.webidl @@ -15,15 +15,14 @@ interface DOMRequestShared { attribute EventHandler onsuccess; attribute EventHandler onerror; }; [Exposed=(Window,Worker)] interface DOMRequest : EventTarget { // The [TreatNonCallableAsNull] annotation is required since then() should do // nothing instead of throwing errors when non-callable arguments are passed. - // See documentation for Promise.then to see why we return "any". - [NewObject, Throws] - any then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null, - [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); + [NewObject] + Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null, + [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); }; DOMRequest implements DOMRequestShared;
--- a/dom/webidl/Promise.webidl +++ b/dom/webidl/Promise.webidl @@ -15,44 +15,33 @@ callback PromiseInit = void (object reso [TreatNonCallableAsNull] callback AnyCallback = any (any value); // REMOVE THE RELEVANT ENTRY FROM test_interfaces.html WHEN THIS IS IMPLEMENTED IN JS. [Constructor(PromiseInit init), Exposed=(Window,Worker,System)] // Need to escape "Promise" so it's treated as an identifier. interface _Promise { - // Have to use "any" (or "object", but "any" is simpler) as the type to - // support the subclassing behavior, since nothing actually requires the - // return value of PromiseSubclass.resolve/reject to be a Promise object. - [NewObject, Throws] - static any resolve(optional any value); - [NewObject, Throws] - static any reject(optional any value); + // Disable the static methods when the interface object is supposed to be + // disabled, just in case some code decides to walk over to .constructor from + // the proto of a promise object or someone screws up and manages to create a + // Promise object in this scope without having resolved the interface object + // first. + [NewObject] + static Promise<any> resolve(optional any value); + [NewObject] + static Promise<void> reject(optional any value); // The [TreatNonCallableAsNull] annotation is required since then() should do // nothing instead of throwing errors when non-callable arguments are passed. - // Have to use "any" (or "object", but "any" is simpler) as the type to - // support the subclassing behavior, since nothing actually requires the - // return value of PromiseSubclass.then/catch to be a Promise object. - [NewObject, Throws] - any then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null, - [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); + [NewObject, MethodIdentityTestable] + Promise<any> then([TreatNonCallableAsNull] optional AnyCallback? fulfillCallback = null, + [TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); - [NewObject, Throws] - any catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); + [NewObject] + Promise<any> catch([TreatNonCallableAsNull] optional AnyCallback? rejectCallback = null); - // Have to use "any" (or "object", but "any" is simpler) as the type to - // support the subclassing behavior, since nothing actually requires the - // return value of PromiseSubclass.all to be a Promise object. As a result, - // we also have to do our argument conversion manually, because we want to - // convert its exceptions into rejections. - [NewObject, Throws] - static any all(optional any iterable); + [NewObject] + static Promise<any> all(sequence<any> iterable); - // Have to use "any" (or "object", but "any" is simpler) as the type to - // support the subclassing behavior, since nothing actually requires the - // return value of PromiseSubclass.race to be a Promise object. As a result, - // we also have to do our argument conversion manually, because we want to - // convert its exceptions into rejections. - [NewObject, Throws] - static any race(optional any iterable); + [NewObject] + static Promise<any> race(sequence<any> iterable); };
--- a/dom/webidl/PromiseDebugging.webidl +++ b/dom/webidl/PromiseDebugging.webidl @@ -47,60 +47,43 @@ callback interface UncaughtRejectionObse * @param p A Promise that was previously left in uncaught state is * now caught, i.e. it is not the last in its chain anymore. */ void onConsumed(Promise<any> p); }; [ChromeOnly, Exposed=(Window,System)] interface PromiseDebugging { - /** - * The various functions on this interface all expect to take promises but - * don't want the WebIDL behavior of assimilating random passed-in objects - * into promises. They also want to treat Promise subclass instances as - * promises instead of wrapping them in a vanilla Promise, which is what the - * IDL spec says to do. So we list all our arguments as "object" instead of - * "Promise" and check for them being a Promise internally. - */ - - /** - * Get the current state of the given promise. - */ - [Throws] - static PromiseDebuggingStateHolder getState(object p); + static PromiseDebuggingStateHolder getState(Promise<any> p); /** * Return the stack to the promise's allocation point. This can * return null if the promise was not created from script. */ - [Throws] - static object? getAllocationStack(object p); + static object? getAllocationStack(Promise<any> p); /** * Return the stack to the promise's rejection point, if the * rejection happened from script. This can return null if the * promise has not been rejected or was not rejected from script. */ - [Throws] - static object? getRejectionStack(object p); + static object? getRejectionStack(Promise<any> p); /** * Return the stack to the promise's fulfillment point, if the * fulfillment happened from script. This can return null if the * promise has not been fulfilled or was not fulfilled from script. */ - [Throws] - static object? getFullfillmentStack(object p); + static object? getFullfillmentStack(Promise<any> p); /** * Return an identifier for a promise. This identifier is guaranteed * to be unique to this instance of Firefox. */ - [Throws] - static DOMString getPromiseID(object p); + static DOMString getPromiseID(Promise<any> p); /** * Get the promises directly depending on a given promise. These are: * * 1) Return values of then() calls on the promise * 2) Return values of Promise.all() if the given promise was passed in as one * of the arguments. * 3) Return values of Promise.race() if the given promise was passed in as @@ -108,32 +91,30 @@ interface PromiseDebugging { * * Once a promise is settled, it will generally notify its dependent promises * and forget about them, so this is most useful on unsettled promises. * * Note that this function only returns the promises that directly depend on * p. It does not recursively return promises that depend on promises that * depend on p. */ - [Throws] - static sequence<Promise<any>> getDependentPromises(object p); + static sequence<Promise<any>> getDependentPromises(Promise<any> p); /** * Get the number of milliseconds elapsed since the given promise was created. */ - [Throws] - static DOMHighResTimeStamp getPromiseLifetime(object p); + static DOMHighResTimeStamp getPromiseLifetime(Promise<any> p); /* * Get the number of milliseconds elapsed between the promise being created * and being settled. Throws NS_ERROR_UNEXPECTED if the promise has not * settled. */ [Throws] - static DOMHighResTimeStamp getTimeToSettle(object p); + static DOMHighResTimeStamp getTimeToSettle(Promise<any> p); /** * Watching uncaught rejections on the current thread. * * Adding an observer twice will cause it to be notified twice * of events. */ static void addUncaughtRejectionObserver(UncaughtRejectionObserver o);
--- a/services/cloudsync/CloudSyncBookmarks.jsm +++ b/services/cloudsync/CloudSyncBookmarks.jsm @@ -289,33 +289,33 @@ var RootFolder = function (rootId, rootN results = Array.prototype.concat.apply([], results); let promises = []; results.map(function (result) { let promise = PlacesWrapper.localIdToGuid(result.parent).then( function (guidResult) { result.parent = guidResult; return Promise.resolve(result); }, - Promise.reject.bind(Promise) + Promise.reject ); promises.push(promise); }); return Promise.all(promises); } function getAnnos(results) { results = Array.prototype.concat.apply([], results); let promises = []; results.map(function (result) { let promise = PlacesWrapper.getItemAnnotationsForLocalId(result.id).then( function (annos) { result.annos = annos; return Promise.resolve(result); }, - Promise.reject.bind(Promise) + Promise.reject ); promises.push(promise); }); return Promise.all(promises); } let promises = [ getFolders(folders), @@ -347,17 +347,17 @@ var RootFolder = function (rootId, rootN function getParentGuids(results) { let promises = []; results.map(function (result) { let promise = PlacesWrapper.localIdToGuid(result.parent).then( function (guidResult) { result.parent = guidResult; return Promise.resolve(result); }, - Promise.reject.bind(Promise) + Promise.reject ); promises.push(promise); }); return Promise.all(promises); } PlacesWrapper.getItemsByGuid(guids, types) .then(getParentGuids) @@ -554,17 +554,17 @@ var RootFolder = function (rootId, rootN } } for (let item of items) { if (!item || 'object' !== typeof(item)) { continue; } - let promise = exists(item).then(handleSortedItem, Promise.reject.bind(Promise)); + let promise = exists(item).then(handleSortedItem, Promise.reject); promises.push(promise); } return Promise.all(promises); } let processNewFolders = function () { let newFolderGuids = Object.keys(newFolders); @@ -583,28 +583,28 @@ var RootFolder = function (rootId, rootN let promises = []; for (let guid of newFolderRoots) { let root = newFolders[guid]; let promise = Promise.resolve(); promise = promise.then( function () { return _createItem(root); }, - Promise.reject.bind(Promise) + Promise.reject ); let items = [].concat(root._children); while (items.length) { let item = newFolders[items.shift()]; items = items.concat(item._children); promise = promise.then( function () { return _createItem(item); }, - Promise.reject.bind(Promise) + Promise.reject ); } promises.push(promise); } return Promise.all(promises); }
--- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -29914,26 +29914,17 @@ }, { "path": "webdriver/windows/window_manipulation.py" } ] }, "local_changes": { "deleted": [], - "items": { - "testharness": { - "js/builtins/Promise-subclassing.html": [ - { - "path": "js/builtins/Promise-subclassing.html", - "url": "/js/builtins/Promise-subclassing.html" - } - ] - } - }, + "items": {}, "reftest_nodes": {} }, "reftest_nodes": { "2dcontext/building-paths/canvas_complexshapes_arcto_001.htm": [ { "path": "2dcontext/building-paths/canvas_complexshapes_arcto_001.htm", "references": [ [
deleted file mode 100644 --- a/testing/web-platform/tests/js/builtins/Promise-subclassing.html +++ /dev/null @@ -1,265 +0,0 @@ -<!doctype html> -<meta charset=utf-8> -<title></title> -<script src=/resources/testharness.js></script> -<script src=/resources/testharnessreport.js></script> -<script> - -var theLog = []; -var speciesGets = 0; -var speciesCalls = 0; -var constructorCalls = 0; -var constructorGets = 0; -var resolveCalls = 0; -var rejectCalls = 0; -var thenCalls = 0; -var catchCalls = 0; -var allCalls = 0; -var raceCalls = 0; -var nextCalls = 0; - -function takeLog() { - var oldLog = theLog; - theLog = []; - speciesGets = speciesCalls = constructorCalls = resolveCalls = - rejectCalls = thenCalls = catchCalls = allCalls = raceCalls = - nextCalls = constructorGets = 0; - return oldLog; -} - -function clearLog() { - takeLog(); -} - -function log(str) { - theLog.push(str); -} - -class LoggingPromise extends Promise { - constructor(func) { - super(func); - Object.defineProperty(this, "constructor", - { - get: function() { - ++constructorGets; - log(`Constructor get ${constructorGets}`); - return Object.getPrototypeOf(this).constructor; - } - }); - ++constructorCalls; - log(`Constructor ${constructorCalls}`); - } - - static get [Symbol.species]() { - ++speciesGets; - log(`Species get ${speciesGets}`); - return LoggingSpecies; - } - - static resolve(val) { - ++resolveCalls; - log(`Resolve ${resolveCalls}`); - return super.resolve(val); - } - - static reject(val) { - ++rejectCalls; - log(`Reject ${rejectCalls}`); - return super.reject(val); - } - - then(resolve, reject) { - ++thenCalls; - log(`Then ${thenCalls}`); - return super.then(resolve, reject); - } - - catch(handler) { - ++catchCalls; - log(`Catch ${catchCalls}`); - return super.catch(handler); - } - - static all(val) { - ++allCalls; - log(`All ${allCalls}`); - return super.all(val); - } - - static race(val) { - ++raceCalls; - log(`Race ${raceCalls}`); - return super.race(val); - } -} - -class LoggingIterable { - constructor(array) { - this.iter = array[Symbol.iterator](); - } - - get [Symbol.iterator]() { return () => this } - - next() { - ++nextCalls; - log(`Next ${nextCalls}`); - return this.iter.next(); - } -} - -class LoggingSpecies extends LoggingPromise { - constructor(func) { - ++speciesCalls; - log(`Species call ${speciesCalls}`); - super(func) - } -} - -class SpeciesLessPromise extends LoggingPromise { - static get [Symbol.species]() { - return undefined; - } -} - -promise_test(function testBasicConstructor() { - var p = new LoggingPromise((resolve) => resolve(5)); - var log = takeLog(); - assert_array_equals(log, ["Constructor 1"]); - assert_true(p instanceof LoggingPromise); - return p.then(function(arg) { - assert_equals(arg, 5); - }); -}, "Basic constructor behavior"); - -promise_test(function testPromiseRace() { - clearLog(); - var p = LoggingPromise.race(new LoggingIterable([1, 2])); - var log = takeLog(); - assert_array_equals(log, ["Race 1", "Constructor 1", - "Next 1", "Resolve 1", "Constructor 2", - "Then 1", "Constructor get 1", "Species get 1", "Species call 1", "Constructor 3", - "Next 2", "Resolve 2", "Constructor 4", - "Then 2", "Constructor get 2", "Species get 2", "Species call 2", "Constructor 5", - "Next 3"]); - assert_true(p instanceof LoggingPromise); - return p.then(function(arg) { - assert_true(arg == 1 || arg == 2); - }); -}, "Promise.race behavior"); - -promise_test(function testPromiseRaceNoSpecies() { - clearLog(); - var p = SpeciesLessPromise.race(new LoggingIterable([1, 2])); - var log = takeLog(); - assert_array_equals(log, ["Race 1", "Constructor 1", - "Next 1", "Resolve 1", "Constructor 2", - "Then 1", "Constructor get 1", - "Next 2", "Resolve 2", "Constructor 3", - "Then 2", "Constructor get 2", - "Next 3"]); - assert_true(p instanceof SpeciesLessPromise); - return p.then(function(arg) { - assert_true(arg == 1 || arg == 2); - }); -}, "Promise.race without species behavior"); - -promise_test(function testPromiseAll() { - clearLog(); - var p = LoggingPromise.all(new LoggingIterable([1, 2])); - var log = takeLog(); - assert_array_equals(log, ["All 1", "Constructor 1", - "Next 1", "Resolve 1", "Constructor 2", - "Then 1", "Constructor get 1", "Species get 1", "Species call 1", "Constructor 3", - "Next 2", "Resolve 2", "Constructor 4", - "Then 2", "Constructor get 2", "Species get 2", "Species call 2", "Constructor 5", - "Next 3"]); - assert_true(p instanceof LoggingPromise); - return p.then(function(arg) { - assert_array_equals(arg, [1, 2]); - }); -}, "Promise.all behavior"); - -promise_test(function testPromiseResolve() { - clearLog(); - var p = LoggingPromise.resolve(5); - var log = takeLog(); - assert_array_equals(log, ["Resolve 1", "Constructor 1"]); - var q = LoggingPromise.resolve(p); - assert_equals(p, q, - "Promise.resolve with same constructor should preserve identity"); - log = takeLog(); - assert_array_equals(log, ["Resolve 1", "Constructor get 1"]); - - var r = Promise.resolve(p); - log = takeLog(); - assert_array_equals(log, ["Constructor get 1"]); - assert_not_equals(p, r, - "Promise.resolve with different constructor should " + - "create a new Promise instance (1)") - var s = Promise.resolve(6); - var u = LoggingPromise.resolve(s); - log = takeLog(); - assert_array_equals(log, ["Resolve 1", "Constructor 1"]); - assert_not_equals(s, u, - "Promise.resolve with different constructor should " + - "create a new Promise instance (2)") - - Object.defineProperty(s, "constructor", { value: LoggingPromise }); - var v = LoggingPromise.resolve(s); - log = takeLog(); - assert_array_equals(log, ["Resolve 1"]); - assert_equals(v, s, "Faking the .constructor should work"); - assert_false(v instanceof LoggingPromise); - - var results = Promise.all([p, q, r, s, u, v]); - return results.then(function(arg) { - assert_array_equals(arg, [5, 5, 5, 6, 6, 6]); - }); -}, "Promise.resolve behavior"); - -promise_test(function testPromiseReject() { - clearLog(); - var p = LoggingPromise.reject(5); - var log = takeLog(); - assert_array_equals(log, ["Reject 1", "Constructor 1"]); - - return p.catch(function(arg) { - assert_equals(arg, 5); - }); -}, "Promise.reject behavior"); - -promise_test(function testPromiseThen() { - clearLog(); - var p = LoggingPromise.resolve(5); - var log = takeLog(); - assert_array_equals(log, ["Resolve 1", "Constructor 1"]); - - var q = p.then((x) => x*x); - log = takeLog(); - assert_array_equals(log, ["Then 1", "Constructor get 1", "Species get 1", - "Species call 1", "Constructor 1"]); - assert_true(q instanceof LoggingPromise); - - return q.then(function(arg) { - assert_equals(arg, 25); - }); -}, "Promise.then behavior"); - -promise_test(function testPromiseCatch() { - clearLog(); - var p = LoggingPromise.reject(5); - var log = takeLog(); - assert_array_equals(log, ["Reject 1", "Constructor 1"]); - - var q = p.catch((x) => x*x); - log = takeLog(); - assert_array_equals(log, ["Catch 1", "Then 1", "Constructor get 1", - "Species get 1", "Species call 1", "Constructor 1"]); - assert_true(q instanceof LoggingPromise); - - return q.then(function(arg) { - assert_equals(arg, 25); - }); -}, "Promise.catch behavior"); - -</script>
--- a/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js +++ b/toolkit/components/asyncshutdown/tests/xpcshell/test_AsyncShutdown.js @@ -143,17 +143,17 @@ add_task(function* test_phase_removeBloc } do_print("Waiting (should lift very quickly)"); yield lock.wait(); do_remove_blocker(lock, blockers[0], false); do_print("Attempt to remove a blocker after wait"); lock = makeLock(kind); - blocker = Promise.resolve.bind(Promise); + blocker = Promise.resolve; yield lock.wait(); do_remove_blocker(lock, blocker, false); do_print("Attempt to remove non-registered blocker after wait()"); do_remove_blocker(lock, "foo", false); do_remove_blocker(lock, null, false); }