author | Nikhil Marathe <nsm.nikhil@gmail.com> |
Mon, 27 Apr 2015 12:00:41 -0700 | |
changeset 241240 | 531631f340210c8e885b0f9a34ec00e1d474a15b |
parent 241239 | b2dd209a43df1379f9f7e7272032ec4b94585b32 |
child 241241 | e44777d69e7a949a3fbaaae37e3c7986c9847e49 |
push id | 59063 |
push user | nsm.nikhil@gmail.com |
push date | Mon, 27 Apr 2015 19:08:01 +0000 |
treeherder | mozilla-inbound@531631f34021 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bholley |
bugs | 1058695 |
milestone | 40.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/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1434,16 +1434,18 @@ nsGlobalWindow::DropOuterWindowDocs() void nsGlobalWindow::CleanUp() { // Guarantee idempotence. if (mCleanedUp) return; mCleanedUp = true; + StartDying(); + mEventTargetObjects.EnumerateEntries(DisconnectEventTargetObjects, nullptr); mEventTargetObjects.Clear(); if (mObserver) { nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); os->RemoveObserver(mObserver, "dom-storage2-changed");
--- a/dom/base/nsIGlobalObject.h +++ b/dom/base/nsIGlobalObject.h @@ -12,21 +12,55 @@ #define NS_IGLOBALOBJECT_IID \ { 0xe2538ded, 0x13ef, 0x4f4d, \ { 0x94, 0x6b, 0x65, 0xd3, 0x33, 0xb4, 0xf0, 0x3c } } class nsIPrincipal; class nsIGlobalObject : public nsISupports { + bool mIsDying; + +protected: + nsIGlobalObject() + : mIsDying(false) + {} + public: NS_DECLARE_STATIC_IID_ACCESSOR(NS_IGLOBALOBJECT_IID) + /** + * This check is added to deal with Promise microtask queues. On the main + * thread, we do not impose restrictions about when script stops running or + * when runnables can no longer be dispatched to the main thread. This means + * it is possible for a Promise chain to keep resolving an infinite chain of + * promises, preventing the browser from shutting down. See Bug 1058695. To + * prevent this, the nsGlobalWindow subclass sets this flag when it is + * closed. The Promise implementation checks this and prohibits new runnables + * from being dispatched. + * + * We pair this with checks during processing the promise microtask queue + * that pops up the slow script dialog when the Promise queue is preventing + * a window from going away. + */ + bool + IsDying() const + { + return mIsDying; + } + virtual JSObject* GetGlobalJSObject() = 0; // This method is not meant to be overridden. nsIPrincipal* PrincipalOrNull(); + +protected: + void + StartDying() + { + mIsDying = true; + } }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIGlobalObject, NS_IGLOBALOBJECT_IID) #endif // nsIGlobalObject_h__
--- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -258,17 +258,17 @@ protected: mPromise->RejectInternal(cx, exn); } // At least one of resolveFunc or rejectFunc have been called, so ignore // the exception. FIXME(nsm): This should be reported to the error // console though, for debugging. } - return NS_OK; + return rv.StealNSResult(); } private: nsRefPtr<Promise> mPromise; JS::PersistentRooted<JSObject*> mThenable; nsRefPtr<PromiseInit> mThen; NS_DECL_OWNINGTHREAD; }; @@ -484,23 +484,34 @@ Promise::PerformMicroTaskCheckpoint() CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get(); nsTArray<nsCOMPtr<nsIRunnable>>& microtaskQueue = runtime->GetPromiseMicroTaskQueue(); if (microtaskQueue.IsEmpty()) { return false; } + Maybe<AutoSafeJSContext> cx; + if (NS_IsMainThread()) { + cx.emplace(); + } + do { nsCOMPtr<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(); + nsresult rv = runnable->Run(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + if (cx.isSome()) { + JS_CheckForInterrupt(cx.ref()); + } } while (!microtaskQueue.IsEmpty()); return true; } /* static */ bool Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { @@ -1066,16 +1077,20 @@ Promise::Compartment() const { return js::GetObjectCompartment(GlobalJSObject()); } void Promise::AppendCallbacks(PromiseCallback* aResolveCallback, PromiseCallback* aRejectCallback) { + if (mGlobal->IsDying()) { + return; + } + MOZ_ASSERT(aResolveCallback); MOZ_ASSERT(aRejectCallback); if (mIsLastInChain && mState == PromiseState::Rejected) { // This rejection is now consumed. PromiseDebugging::AddConsumedRejection(*this); // Note that we may not have had the opportunity to call // RunResolveTask() yet, so we may never have called @@ -1292,17 +1307,22 @@ Promise::RejectInternal(JSContext* aCx, mResolvePending = true; MaybeSettle(aValue, Rejected); } void Promise::Settle(JS::Handle<JS::Value> aValue, PromiseState aState) { + if (mGlobal->IsDying()) { + return; + } + mSettlementTimestamp = TimeStamp::Now(); + SetResult(aValue); SetState(aState); AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); JS::RootedObject wrapper(cx, GetWrapper()); MOZ_ASSERT(wrapper); // We preserved it
--- a/dom/promise/PromiseCallback.cpp +++ b/dom/promise/PromiseCallback.cpp @@ -66,30 +66,31 @@ ResolvePromiseCallback::ResolvePromiseCa HoldJSObjects(this); } ResolvePromiseCallback::~ResolvePromiseCallback() { DropJSObjects(this); } -void +nsresult ResolvePromiseCallback::Call(JSContext* aCx, JS::Handle<JS::Value> aValue) { // Run resolver's algorithm with value and the synchronous flag set. 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; + return NS_ERROR_FAILURE; } mPromise->ResolveInternal(aCx, value); + return NS_OK; } // RejectPromiseCallback NS_IMPL_CYCLE_COLLECTION_CLASS(RejectPromiseCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RejectPromiseCallback, PromiseCallback) @@ -124,31 +125,32 @@ RejectPromiseCallback::RejectPromiseCall HoldJSObjects(this); } RejectPromiseCallback::~RejectPromiseCallback() { DropJSObjects(this); } -void +nsresult RejectPromiseCallback::Call(JSContext* aCx, JS::Handle<JS::Value> aValue) { // Run resolver's algorithm with value and the synchronous flag set. 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; + return NS_ERROR_FAILURE; } mPromise->RejectInternal(aCx, value); + return NS_OK; } // WrapperPromiseCallback NS_IMPL_CYCLE_COLLECTION_CLASS(WrapperPromiseCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WrapperPromiseCallback, PromiseCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNextPromise) @@ -185,25 +187,25 @@ WrapperPromiseCallback::WrapperPromiseCa HoldJSObjects(this); } WrapperPromiseCallback::~WrapperPromiseCallback() { DropJSObjects(this); } -void +nsresult WrapperPromiseCallback::Call(JSContext* aCx, JS::Handle<JS::Value> 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; + return NS_ERROR_FAILURE; } ErrorResult rv; // PromiseReactionTask step 6 JS::Rooted<JS::Value> retValue(aCx); mCallback->Call(value, &retValue, rv, "promise callback", CallbackObject::eRethrowExceptions, @@ -214,30 +216,30 @@ WrapperPromiseCallback::Call(JSContext* // PromiseReactionTask step 7 if (rv.Failed()) { JS::Rooted<JS::Value> value(aCx); if (rv.IsJSException()) { rv.StealJSException(aCx, &value); if (!JS_WrapValue(aCx, &value)) { NS_WARNING("Failed to wrap value into the right compartment."); - return; + return NS_ERROR_FAILURE; } } else { // 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. JSAutoCompartment ac(aCx, mNextPromise->GlobalJSObject()); DebugOnly<bool> conversionResult = ToJSValue(aCx, rv, &value); MOZ_ASSERT(conversionResult); } mNextPromise->RejectInternal(aCx, value); - return; + 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()); Promise* returnedPromise; nsresult r = UNWRAP_OBJECT(Promise, valueObj, returnedPromise); @@ -265,48 +267,49 @@ WrapperPromiseCallback::Call(JSContext* } } // We're back in aValue's compartment here. JS::Rooted<JSString*> fn(aCx, JS_NewStringCopyZ(aCx, fileName)); if (!fn) { // Out of memory. Promise will stay unresolved. JS_ClearPendingException(aCx); - return; + return NS_ERROR_OUT_OF_MEMORY; } JS::Rooted<JSString*> message(aCx, JS_NewStringCopyZ(aCx, "then() cannot return same Promise that it resolves.")); if (!message) { // Out of memory. Promise will stay unresolved. JS_ClearPendingException(aCx); - return; + return NS_ERROR_OUT_OF_MEMORY; } JS::Rooted<JS::Value> typeError(aCx); if (!JS::CreateError(aCx, JSEXN_TYPEERR, JS::NullPtr(), fn, lineNumber, 0, nullptr, message, &typeError)) { // Out of memory. Promise will stay unresolved. JS_ClearPendingException(aCx); - return; + return NS_ERROR_OUT_OF_MEMORY; } mNextPromise->RejectInternal(aCx, typeError); - return; + 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; + return NS_ERROR_FAILURE; } mNextPromise->ResolveInternal(aCx, retValue); + return NS_OK; } // NativePromiseCallback NS_IMPL_CYCLE_COLLECTION_INHERITED(NativePromiseCallback, PromiseCallback, mHandler) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NativePromiseCallback) @@ -322,31 +325,32 @@ NativePromiseCallback::NativePromiseCall { MOZ_ASSERT(aHandler); } NativePromiseCallback::~NativePromiseCallback() { } -void +nsresult NativePromiseCallback::Call(JSContext* aCx, JS::Handle<JS::Value> aValue) { if (mState == Promise::Resolved) { mHandler->ResolvedCallback(aCx, aValue); - return; + return NS_OK; } if (mState == Promise::Rejected) { mHandler->RejectedCallback(aCx, aValue); - return; + return NS_OK; } NS_NOTREACHED("huh?"); + return NS_ERROR_FAILURE; } /* static */ PromiseCallback* PromiseCallback::Factory(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal, AnyCallback* aCallback, Task aTask) { MOZ_ASSERT(aNextPromise);
--- a/dom/promise/PromiseCallback.h +++ b/dom/promise/PromiseCallback.h @@ -21,18 +21,18 @@ protected: virtual ~PromiseCallback(); public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(PromiseCallback) PromiseCallback(); - virtual void Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) = 0; + virtual nsresult Call(JSContext* aCx, + JS::Handle<JS::Value> aValue) = 0; // Return the Promise that this callback will end up resolving or // rejecting, if any. virtual Promise* GetDependentPromise() = 0; enum Task { Resolve, Reject @@ -49,18 +49,18 @@ public: // 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) - void Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) override; + nsresult Call(JSContext* aCx, + JS::Handle<JS::Value> aValue) override; Promise* GetDependentPromise() override { return mNextPromise; } WrapperPromiseCallback(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal, AnyCallback* aCallback); @@ -77,18 +77,18 @@ private: // received by Call(). class ResolvePromiseCallback final : public PromiseCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(ResolvePromiseCallback, PromiseCallback) - void Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) override; + nsresult Call(JSContext* aCx, + JS::Handle<JS::Value> aValue) override; Promise* GetDependentPromise() override { return mPromise; } ResolvePromiseCallback(Promise* aPromise, JS::Handle<JSObject*> aGlobal); @@ -103,18 +103,18 @@ private: // received by Call(). class RejectPromiseCallback final : public PromiseCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(RejectPromiseCallback, PromiseCallback) - void Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) override; + nsresult Call(JSContext* aCx, + JS::Handle<JS::Value> aValue) override; Promise* GetDependentPromise() override { return mPromise; } RejectPromiseCallback(Promise* aPromise, JS::Handle<JSObject*> aGlobal); @@ -128,18 +128,18 @@ private: // NativePromiseCallback wraps a PromiseNativeHandler. class NativePromiseCallback final : public PromiseCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NativePromiseCallback, PromiseCallback) - void Call(JSContext* aCx, - JS::Handle<JS::Value> aValue) override; + nsresult Call(JSContext* aCx, + JS::Handle<JS::Value> aValue) override; Promise* GetDependentPromise() override { return nullptr; } NativePromiseCallback(PromiseNativeHandler* aHandler, Promise::PromiseState aState);
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4457,16 +4457,22 @@ JS_New(JSContext* cx, HandleObject ctor, RootedObject obj(cx); { AutoLastFrameCheck lfc(cx); obj = JS_NewHelper(cx, ctor, inputArgs); } return obj; } +JS_PUBLIC_API(bool) +JS_CheckForInterrupt(JSContext* cx) +{ + return js::CheckForInterrupt(cx); +} + JS_PUBLIC_API(JSInterruptCallback) JS_SetInterruptCallback(JSRuntime* rt, JSInterruptCallback callback) { JSInterruptCallback old = rt->interruptCallback; rt->interruptCallback = callback; return old; }
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3943,19 +3943,21 @@ Call(JSContext* cx, JS::HandleValue this JS::RootedValue fun(cx, JS::ObjectValue(*funObj)); return Call(cx, thisv, fun, args, rval); } extern JS_PUBLIC_API(bool) Construct(JSContext* cx, JS::HandleValue fun, const JS::HandleValueArray& args, MutableHandleValue rval); - } /* namespace JS */ +extern JS_PUBLIC_API(bool) +JS_CheckForInterrupt(JSContext* cx); + /* * These functions allow setting an interrupt callback that will be called * from the JS thread some time after any thread triggered the callback using * JS_RequestInterruptCallback(rt). * * To schedule the GC and for other activities the engine internally triggers * interrupt callbacks. The embedding should thus not rely on callbacks being * triggered through the external API only.
--- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1456,18 +1456,23 @@ XPCJSRuntime::InterruptCallback(JSContex if (!JS_GetPrototype(cx, global, &proto)) return false; if (proto && IsSandboxPrototypeProxy(proto) && (proto = js::CheckedUnwrap(proto, /* stopAtOuter = */ false))) { win = WindowGlobalOrNull(proto); } } - if (!win) + + if (!win) { + NS_WARNING("No active window"); return true; + } + + MOZ_ASSERT(!win->IsDying()); if (win->GetIsPrerendered()) { // We cannot display a dialog if the page is being prerendered, so // just kill the page. mozilla::dom::HandlePrerenderingViolation(win); return false; }