author | Paolo Amadini <paolo.mozmail@amadzone.org> |
Tue, 28 Oct 2014 12:08:19 +0000 | |
changeset 212704 | 47a470e57e9d677df4dc933ed110cbe319e7de8f |
parent 212703 | 048a239423086c8ea77b78dba035bd892b35d50f |
child 212705 | dcfb1ac1935ceaa2caf1cba02aa2f4251aa90c61 |
push id | 51042 |
push user | ryanvm@gmail.com |
push date | Tue, 28 Oct 2014 20:25:03 +0000 |
treeherder | mozilla-inbound@53d84829b2b8 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bz, khuey |
bugs | 1013625 |
milestone | 36.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -34,16 +34,17 @@ #include "mozilla/DebugOnly.h" #include "mozilla/LoadInfo.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/HTMLContentElement.h" #include "mozilla/dom/HTMLShadowElement.h" +#include "mozilla/dom/Promise.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/TextDecoder.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStateManager.h" #include "mozilla/IMEStateManager.h" @@ -5076,17 +5077,17 @@ nsContentUtils::EnterMicroTask() ++sMicroTaskLevel; } void nsContentUtils::LeaveMicroTask() { MOZ_ASSERT(NS_IsMainThread()); if (--sMicroTaskLevel == 0) { - nsDOMMutationObserver::HandleMutations(); + PerformMainThreadMicroTaskCheckpoint(); nsDocument::ProcessBaseElementQueue(); } } bool nsContentUtils::IsInMicroTask() { MOZ_ASSERT(NS_IsMainThread()); @@ -5102,16 +5103,24 @@ nsContentUtils::MicroTaskLevel() void nsContentUtils::SetMicroTaskLevel(uint32_t aLevel) { MOZ_ASSERT(NS_IsMainThread()); sMicroTaskLevel = aLevel; } +void +nsContentUtils::PerformMainThreadMicroTaskCheckpoint() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsDOMMutationObserver::HandleMutations(); +} + /* * Helper function for nsContentUtils::ProcessViewportInfo. * * Handles a single key=value pair. If it corresponds to a valid viewport * attribute, add it to the document header data. No validation is done on the * value itself (this is done at display time). */ static void ProcessViewportToken(nsIDocument *aDocument,
--- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1537,16 +1537,18 @@ public: // Usually the best way to do this is to use nsAutoMicroTask. static void EnterMicroTask(); static void LeaveMicroTask(); static bool IsInMicroTask(); static uint32_t MicroTaskLevel(); static void SetMicroTaskLevel(uint32_t aLevel); + static void PerformMainThreadMicroTaskCheckpoint(); + /* Process viewport META data. This gives us information for the scale * and zoom of a page on mobile devices. We stick the information in * the document header and use it later on after rendering. * * See Bug #436083 */ static nsresult ProcessViewportInfo(nsIDocument *aDocument, const nsAString &viewportInfo);
--- a/dom/promise/AbortablePromise.cpp +++ b/dom/promise/AbortablePromise.cpp @@ -95,17 +95,17 @@ AbortablePromise::Abort() { if (IsPending()) { return; } MaybeReject(NS_ERROR_ABORT); nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableMethod(this, &AbortablePromise::DoAbort); - Promise::DispatchToMainOrWorkerThread(runnable); + Promise::DispatchToMicroTask(runnable); } void AbortablePromise::DoAbort() { if (mAbortCallback.HasWebIDLCallback()) { ErrorResult rv; mAbortCallback.GetWebIDLCallback()->Call(rv);
--- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -349,16 +349,33 @@ Promise::MaybeResolve(JSContext* aCx, void Promise::MaybeReject(JSContext* aCx, JS::Handle<JS::Value> aValue) { MaybeRejectInternal(aCx, aValue); } +void +Promise::PerformMicroTaskCheckpoint() +{ + CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); + nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue = + runtime->GetPromiseMicroTaskQueue(); + + while (!microtaskQueue.IsEmpty()) { + nsRefPtr<nsIRunnable> runnable = microtaskQueue.ElementAt(0); + MOZ_ASSERT(runnable); + + // This function can re-enter, so we remove the element before calling. + microtaskQueue.RemoveElementAt(0); + runnable->Run(); + } +} + /* static */ bool Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { JS::CallArgs args = CallArgsFromVp(aArgc, aVp); JS::Rooted<JS::Value> v(aCx, js::GetFunctionNativeReserved(&args.callee(), SLOT_PROMISE)); @@ -949,27 +966,25 @@ private: NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable); } nsCOMPtr<nsIRunnable> mRunnable; NS_DECL_OWNINGTHREAD }; /* static */ void -Promise::DispatchToMainOrWorkerThread(nsIRunnable* aRunnable) +Promise::DispatchToMicroTask(nsIRunnable* aRunnable) { MOZ_ASSERT(aRunnable); - if (NS_IsMainThread()) { - NS_DispatchToCurrentThread(aRunnable); - return; - } - WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(worker); - nsRefPtr<WrappedWorkerRunnable> task = new WrappedWorkerRunnable(worker, aRunnable); - task->Dispatch(worker->GetJSContext()); + + CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); + nsTArray<nsRefPtr<nsIRunnable>>& microtaskQueue = + runtime->GetPromiseMicroTaskQueue(); + + microtaskQueue.AppendElement(aRunnable); } void Promise::MaybeReportRejected() { if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) { return; } @@ -1063,17 +1078,17 @@ Promise::ResolveInternal(JSContext* aCx, if (then.isObject() && JS::IsCallable(&then.toObject())) { // This is the then() function of the thenable aValueObj. JS::Rooted<JSObject*> thenObj(aCx, &then.toObject()); nsRefPtr<PromiseInit> thenCallback = new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal()); nsRefPtr<ThenableResolverTask> task = new ThenableResolverTask(this, valueObj, thenCallback); - DispatchToMainOrWorkerThread(task); + DispatchToMicroTask(task); return; } } MaybeSettle(aValue, Resolved); } void @@ -1129,17 +1144,17 @@ Promise::EnqueueCallbackTasks() callbacks.SwapElements(mState == Resolved ? mResolveCallbacks : mRejectCallbacks); mResolveCallbacks.Clear(); mRejectCallbacks.Clear(); for (uint32_t i = 0; i < callbacks.Length(); ++i) { nsRefPtr<PromiseCallbackTask> task = new PromiseCallbackTask(this, callbacks[i], mResult); - DispatchToMainOrWorkerThread(task); + DispatchToMicroTask(task); } } void Promise::RemoveFeature() { if (mFeature) { WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
--- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -106,16 +106,19 @@ public: // every translation unit that includes this header, because that would // require use to include DOMError.h either here or in all those translation // units. template<typename T> void MaybeRejectBrokenly(const T& aArg); // Not implemented by default; see // specializations in the .cpp for // the T values we support. + // Called by DOM to let us execute our callbacks. May be called recursively. + static void PerformMicroTaskCheckpoint(); + // WebIDL nsIGlobalObject* GetParentObject() const { return mGlobal; } virtual JSObject* @@ -160,19 +163,19 @@ public: protected: // 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(); - // Queue an async task to current main or worker thread. + // Queue an async microtask to current main or worker thread. static void - DispatchToMainOrWorkerThread(nsIRunnable* aRunnable); + DispatchToMicroTask(nsIRunnable* aRunnable); // Do JS-wrapping after Promise creation. void CreateWrapper(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,
--- a/dom/promise/tests/test_promise.html +++ b/dom/promise/tests/test_promise.html @@ -111,30 +111,67 @@ function promiseGC() { SpecialPowers.gc(); SpecialPowers.forceGC(); SpecialPowers.forceCC(); resolve(42); } -function promiseAsync() { - var global = "foo"; - var f = new Promise(function(r1, r2) { - is(global, "foo", "Global should be foo"); - r1(42); - is(global, "foo", "Global should still be foo"); - setTimeout(function() { - // is(global, "bar", "Global should still be bar!"); // Bug 1013625 - runTest(); - }, 0); - }).then(function() { - global = "bar"; +function promiseAsync_TimeoutResolveThen() { + var handlerExecuted = false; + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + Promise.resolve().then(function() { + handlerExecuted = true; }); - is(global, "foo", "Global should still be foo (2)"); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveTimeoutThen() { + var handlerExecuted = false; + + var promise = Promise.resolve(); + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + promise.then(function() { + handlerExecuted = true; + }); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveThenTimeout() { + var handlerExecuted = false; + + Promise.resolve().then(function() { + handlerExecuted = true; + }); + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); } function promiseDoubleThen() { var steps = 0; var promise = new Promise(function(r1, r2) { r1(42); }); @@ -710,17 +747,20 @@ function promiseWrapperAsyncResolution() runTest(); }, function() { ok(false, "promiseWrapperAsyncResolution: One of the promises failed."); runTest(); }); } var tests = [ promiseResolve, promiseReject, - promiseException, promiseGC, promiseAsync, + promiseException, promiseGC, + promiseAsync_TimeoutResolveThen, + promiseAsync_ResolveTimeoutThen, + promiseAsync_ResolveThenTimeout, promiseDoubleThen, promiseThenException, promiseThenCatchThen, promiseRejectThenCatchThen, promiseRejectThenCatchThen2, promiseRejectThenCatchExceptionThen, promiseThenCatchOrderingResolve, promiseThenCatchOrderingReject, promiseNestedPromise, promiseNestedNestedPromise, promiseWrongNestedPromise, promiseLoop,
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -44,16 +44,17 @@ #include "mozilla/dom/ErrorEventBinding.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/ImageData.h" #include "mozilla/dom/ImageDataBinding.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/MessagePortList.h" +#include "mozilla/dom/Promise.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/StructuredClone.h" #include "mozilla/dom/WorkerBinding.h" #include "mozilla/Preferences.h" #include "nsAlgorithm.h" #include "nsContentUtils.h" #include "nsError.h" #include "nsDOMJSUtils.h" @@ -4107,16 +4108,20 @@ WorkerPrivate::DoRunLoop(JSContext* aCx) MOZ_ASSERT(NS_HasPendingEvents(mThread)); // Start the periodic GC timer if it is not already running. SetGCTimerMode(PeriodicTimer); // Process a single runnable from the main queue. MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false)); + // Only perform the Promise microtask checkpoint on the outermost event + // loop. Don't run it, for example, during sync XHR or importScripts. + Promise::PerformMicroTaskCheckpoint(); + if (NS_HasPendingEvents(mThread)) { // Now *might* be a good time to GC. Let the JS engine make the decision. if (workerCompartment) { JS_MaybeGC(aCx); } } else { // The normal event queue has been exhausted, cancel the periodic GC timer
--- a/dom/workers/test/promise_worker.js +++ b/dom/workers/test/promise_worker.js @@ -90,30 +90,91 @@ function promiseException() { runTest(); }, function(what) { ok(true, "Then - rejectCb has been called"); is(what, 42, "RejectCb received 42"); runTest(); }); } -function promiseAsync() { - var global = "foo"; - var f = new Promise(function(r1, r2) { - is(global, "foo", "Global should be foo"); - r1(42); - is(global, "foo", "Global should still be foo"); - setTimeout(function() { - is(global, "bar", "Global should still be bar!"); - runTest(); - }, 0); - }).then(function() { - global = "bar"; +function promiseAsync_TimeoutResolveThen() { + var handlerExecuted = false; + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + Promise.resolve().then(function() { + handlerExecuted = true; + }); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveTimeoutThen() { + var handlerExecuted = false; + + var promise = Promise.resolve(); + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + promise.then(function() { + handlerExecuted = true; }); - is(global, "foo", "Global should still be foo (2)"); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_ResolveThenTimeout() { + var handlerExecuted = false; + + Promise.resolve().then(function() { + handlerExecuted = true; + }); + + setTimeout(function() { + ok(handlerExecuted, "Handler should have been called before the timeout."); + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }, 0); + + ok(!handlerExecuted, "Handlers are not called before 'then' returns."); +} + +function promiseAsync_SyncHXRAndImportScripts() +{ + var handlerExecuted = false; + + Promise.resolve().then(function() { + handlerExecuted = true; + + // Allow other assertions to run so the test could fail before the next one. + setTimeout(runTest, 0); + }); + + ok(!handlerExecuted, "Handlers are not called until the next microtask."); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "testXHR.txt", false); + xhr.send(null); + + ok(!handlerExecuted, "Sync XHR should not trigger microtask execution."); + + importScripts("relativeLoad_import.js"); + + ok(!handlerExecuted, "importScripts should not trigger microtask execution."); } function promiseDoubleThen() { var steps = 0; var promise = new Promise(function(r1, r2) { r1(42); }); @@ -678,21 +739,63 @@ function promiseResolveThenableCleanStac p.then(function() { results.push(x); is(results[1], 2, "Expected thenable to be called asynchronously"); runTest(); }); },1000); } +// Bug 1062323 +function promiseWrapperAsyncResolution() +{ + var p = new Promise(function(resolve, reject){ + resolve(); + }); + + var results = []; + var q = p.then(function () { + results.push("1-1"); + }).then(function () { + results.push("1-2"); + }).then(function () { + results.push("1-3"); + }); + + var r = p.then(function () { + results.push("2-1"); + }).then(function () { + results.push("2-2"); + }).then(function () { + results.push("2-3"); + }); + + Promise.all([q, r]).then(function() { + var match = results[0] == "1-1" && + results[1] == "2-1" && + results[2] == "1-2" && + results[3] == "2-2" && + results[4] == "1-3" && + results[5] == "2-3"; + ok(match, "Chained promises should resolve asynchronously."); + runTest(); + }, function() { + ok(false, "promiseWrapperAsyncResolution: One of the promises failed."); + runTest(); + }); +} + var tests = [ promiseResolve, promiseReject, promiseException, - promiseAsync, + promiseAsync_TimeoutResolveThen, + promiseAsync_ResolveTimeoutThen, + promiseAsync_ResolveThenTimeout, + promiseAsync_SyncHXRAndImportScripts, promiseDoubleThen, promiseThenException, promiseThenCatchThen, promiseRejectThenCatchThen, promiseRejectThenCatchThen2, promiseRejectThenCatchExceptionThen, promiseThenCatchOrderingResolve, promiseThenCatchOrderingReject, @@ -724,16 +827,18 @@ var tests = [ promiseRaceReject, promiseRaceThrow, promiseResolveArray, promiseResolveThenable, promiseResolvePromise, promiseResolveThenableCleanStack, + + promiseWrapperAsyncResolution, ]; function runTest() { if (!tests.length) { postMessage({ type: 'finish' }); return; }
--- a/js/xpconnect/src/nsXPConnect.cpp +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -17,17 +17,17 @@ #include "nsThreadUtils.h" #include "nsDOMJSUtils.h" #include "WrapperFactory.h" #include "AccessCheck.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Exceptions.h" -#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/Promise.h" #include "mozilla/dom/TextDecoderBinding.h" #include "mozilla/dom/TextEncoderBinding.h" #include "mozilla/dom/DOMErrorBinding.h" #include "nsDOMMutationObserver.h" #include "nsICycleCollectorListener.h" #include "nsThread.h" #include "mozilla/XPTInterfaceInfoManager.h" @@ -1048,17 +1048,20 @@ nsXPConnect::AfterProcessNextEvent(nsITh mEventDepth--; // Now that we're back to the event loop, reset the slow script checkpoint. mRuntime->OnAfterProcessNextEvent(); // Call cycle collector occasionally. MOZ_ASSERT(NS_IsMainThread()); nsJSContext::MaybePokeCC(); - nsDOMMutationObserver::HandleMutations(); + + nsContentUtils::PerformMainThreadMicroTaskCheckpoint(); + + Promise::PerformMicroTaskCheckpoint(); PopJSContextNoScriptContext(); return NS_OK; } NS_IMETHODIMP nsXPConnect::OnDispatchedEvent(nsIThreadInternal* aThread)
--- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -950,16 +950,22 @@ CycleCollectedJSRuntime::GetPendingExcep } void CycleCollectedJSRuntime::SetPendingException(nsIException* aException) { mPendingException = aException; } +nsTArray<nsRefPtr<nsIRunnable>>& +CycleCollectedJSRuntime::GetPromiseMicroTaskQueue() +{ + return mPromiseMicroTaskQueue; +} + nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() { return &mGCThingCycleCollectorGlobal; } nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant()
--- a/xpcom/base/CycleCollectedJSRuntime.h +++ b/xpcom/base/CycleCollectedJSRuntime.h @@ -13,16 +13,17 @@ #include "nsCycleCollector.h" #include "nsCycleCollectionParticipant.h" #include "nsDataHashtable.h" #include "nsHashKeys.h" #include "nsTArray.h" class nsCycleCollectionNoteRootCallback; class nsIException; +class nsIRunnable; namespace js { struct Class; } namespace mozilla { class JSGCThingParticipant: public nsCycleCollectionParticipant @@ -252,16 +253,18 @@ public: #ifdef DEBUG bool IsJSHolder(void* aHolder); void AssertNoObjectsToTrace(void* aPossibleJSHolder); #endif already_AddRefed<nsIException> GetPendingException() const; void SetPendingException(nsIException* aException); + nsTArray<nsRefPtr<nsIRunnable>>& GetPromiseMicroTaskQueue(); + nsCycleCollectionParticipant* GCThingParticipant(); nsCycleCollectionParticipant* ZoneParticipant(); nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb); bool UsefulToMergeZones() const; void FixWeakMappingGrayBits() const; bool AreGCGrayBitsValid() const; void GarbageCollect(uint32_t aReason) const; @@ -301,16 +304,18 @@ private: typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*> DeferredFinalizerTable; DeferredFinalizerTable mDeferredFinalizerTable; nsRefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable; nsCOMPtr<nsIException> mPendingException; + nsTArray<nsRefPtr<nsIRunnable>> mPromiseMicroTaskQueue; + OOMState mOutOfMemoryState; OOMState mLargeAllocationFailureState; }; MOZ_FINISH_NESTED_ENUM_CLASS(CycleCollectedJSRuntime::OOMState) void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer);