From 3aeb0284ff55056d6bb24389c659d318178bcef0 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 22 May 2013 10:05:25 -0600 Subject: [PATCH] Bug 868130 - Separate nsCxPusher an friends into their own file. r=gabor We want to put a JSAutoRequest into nsCxPusher, but that would involve including jsapi.h in nsContentUtils.h, which we'd probably rather avoid doing. Let's just bite the bullet and do this. --- content/base/public/moz.build | 1 + content/base/public/nsContentUtils.h | 100 +----------- content/base/public/nsCxPusher.h | 108 +++++++++++++ content/base/src/Makefile.in | 1 + content/base/src/nsContentUtils.cpp | 209 ------------------------ content/base/src/nsCxPusher.cpp | 230 +++++++++++++++++++++++++++ 6 files changed, 342 insertions(+), 307 deletions(-) create mode 100644 content/base/public/nsCxPusher.h create mode 100644 content/base/src/nsCxPusher.cpp diff --git a/content/base/public/moz.build b/content/base/public/moz.build index 7da0a383a5f3..68d8f48d0ee5 100644 --- a/content/base/public/moz.build +++ b/content/base/public/moz.build @@ -41,6 +41,7 @@ EXPORTS += [ 'nsContentPolicyUtils.h', 'nsContentUtils.h', 'nsCopySupport.h', + 'nsCxPusher.h', 'nsDOMFile.h', 'nsDeprecatedOperationList.h', 'nsDocElementCreatedNotificationRunner.h', diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 35cac9409874..153d0d45b8fb 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -2266,40 +2266,6 @@ typedef nsCharSeparatedTokenizerTemplate nsContentUtils::DropJSObjects(NS_CYCLE_COLLECTION_UPCAST(obj, clazz)) -class MOZ_STACK_CLASS nsCxPusher -{ -public: - nsCxPusher(); - ~nsCxPusher(); // Calls Pop(); - - // Returns false if something erroneous happened. - bool Push(mozilla::dom::EventTarget *aCurrentTarget); - // If nothing has been pushed to stack, this works like Push. - // Otherwise if context will change, Pop and Push will be called. - bool RePush(mozilla::dom::EventTarget *aCurrentTarget); - // If a null JSContext is passed to Push(), that will cause no - // push to happen and false to be returned. - void Push(JSContext *cx); - // Explicitly push a null JSContext on the the stack - void PushNull(); - - // Pop() will be a no-op if Push() or PushNull() fail - void Pop(); - - nsIScriptContext* GetCurrentScriptContext() { return mScx; } -private: - // Combined code for PushNull() and Push(JSContext*) - void DoPush(JSContext* cx); - - nsCOMPtr mScx; - bool mScriptIsRunning; - bool mPushedSomething; -#ifdef DEBUG - JSContext* mPushedContext; - unsigned mCompartmentDepthOnEntry; -#endif -}; - class MOZ_STACK_CLASS nsAutoScriptBlocker { public: nsAutoScriptBlocker(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) { @@ -2341,70 +2307,6 @@ public: } }; -namespace mozilla { - -/** - * Use AutoJSContext when you need a JS context on the stack but don't have one - * passed as a parameter. AutoJSContext will take care of finding the most - * appropriate JS context and release it when leaving the stack. - */ -class MOZ_STACK_CLASS AutoJSContext { -public: - AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); - operator JSContext*(); - -protected: - AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - -private: - // We need this Init() method because we can't use delegating constructor for - // the moment. It is a C++11 feature and we do not require C++11 to be - // supported to be able to compile Gecko. - void Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); - - JSContext* mCx; - nsCxPusher mPusher; - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; - -/** - * AutoSafeJSContext is similar to AutoJSContext but will only return the safe - * JS context. That means it will never call ::GetCurrentJSContext(). - */ -class MOZ_STACK_CLASS AutoSafeJSContext : public AutoJSContext { -public: - AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); -}; - -/** - * Use AutoPushJSContext when you want to use a specific JSContext that may or - * may not be already on the stack. This differs from nsCxPusher in that it only - * pushes in the case that the given cx is not the active cx on the JSContext - * stack, which avoids an expensive JS_SaveFrameChain in the common case. - * - * Most consumers of this should probably just use AutoJSContext. But the goal - * here is to preserve the existing behavior while ensure proper cx-stack - * semantics in edge cases where the context being used doesn't match the active - * context. - * - * NB: This will not push a null cx even if aCx is null. Make sure you know what - * you're doing. - */ -class MOZ_STACK_CLASS AutoPushJSContext { - nsCxPusher mPusher; - JSContext* mCx; - -public: - AutoPushJSContext(JSContext* aCx) : mCx(aCx) { - if (mCx && mCx != nsContentUtils::GetCurrentJSContext()) { - mPusher.Push(mCx); - } - } - operator JSContext*() { return mCx; } -}; - -} // namespace mozilla - #define NS_INTERFACE_MAP_ENTRY_TEAROFF(_interface, _allocator) \ if (aIID.Equals(NS_GET_IID(_interface))) { \ foundInterface = static_cast<_interface *>(_allocator); \ @@ -2473,4 +2375,6 @@ private: nsIMIMEHeaderParam* mService; }; +#include "nsCxPusher.h" + #endif /* nsContentUtils_h___ */ diff --git a/content/base/public/nsCxPusher.h b/content/base/public/nsCxPusher.h new file mode 100644 index 000000000000..9be3275c2056 --- /dev/null +++ b/content/base/public/nsCxPusher.h @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ + +#ifndef nsCxPusher_h___ +#define nsCxPusher_h___ + +#include "nsContentUtils.h" + +class MOZ_STACK_CLASS nsCxPusher +{ +public: + nsCxPusher(); + ~nsCxPusher(); // Calls Pop(); + + // Returns false if something erroneous happened. + bool Push(mozilla::dom::EventTarget *aCurrentTarget); + // If nothing has been pushed to stack, this works like Push. + // Otherwise if context will change, Pop and Push will be called. + bool RePush(mozilla::dom::EventTarget *aCurrentTarget); + // If a null JSContext is passed to Push(), that will cause no + // push to happen and false to be returned. + void Push(JSContext *cx); + // Explicitly push a null JSContext on the the stack + void PushNull(); + + // Pop() will be a no-op if Push() or PushNull() fail + void Pop(); + + nsIScriptContext* GetCurrentScriptContext() { return mScx; } +private: + // Combined code for PushNull() and Push(JSContext*) + void DoPush(JSContext* cx); + + nsCOMPtr mScx; + bool mScriptIsRunning; + bool mPushedSomething; +#ifdef DEBUG + JSContext* mPushedContext; + unsigned mCompartmentDepthOnEntry; +#endif +}; + +namespace mozilla { + +/** + * Use AutoJSContext when you need a JS context on the stack but don't have one + * passed as a parameter. AutoJSContext will take care of finding the most + * appropriate JS context and release it when leaving the stack. + */ +class MOZ_STACK_CLASS AutoJSContext { +public: + AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); + operator JSContext*(); + +protected: + AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + +private: + // We need this Init() method because we can't use delegating constructor for + // the moment. It is a C++11 feature and we do not require C++11 to be + // supported to be able to compile Gecko. + void Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + + JSContext* mCx; + nsCxPusher mPusher; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +/** + * AutoSafeJSContext is similar to AutoJSContext but will only return the safe + * JS context. That means it will never call ::GetCurrentJSContext(). + */ +class MOZ_STACK_CLASS AutoSafeJSContext : public AutoJSContext { +public: + AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); +}; + +/** + * Use AutoPushJSContext when you want to use a specific JSContext that may or + * may not be already on the stack. This differs from nsCxPusher in that it only + * pushes in the case that the given cx is not the active cx on the JSContext + * stack, which avoids an expensive JS_SaveFrameChain in the common case. + * + * Most consumers of this should probably just use AutoJSContext. But the goal + * here is to preserve the existing behavior while ensure proper cx-stack + * semantics in edge cases where the context being used doesn't match the active + * context. + * + * NB: This will not push a null cx even if aCx is null. Make sure you know what + * you're doing. + */ +class MOZ_STACK_CLASS AutoPushJSContext { + nsCxPusher mPusher; + JSContext* mCx; + +public: + AutoPushJSContext(JSContext* aCx) : mCx(aCx) + { + if (mCx && mCx != nsContentUtils::GetCurrentJSContext()) { + mPusher.Push(mCx); + } + } + operator JSContext*() { return mCx; } +}; + +} // namespace mozilla + +#endif /* nsCxPusher_h___ */ diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in index 208af67ccfbc..a1387e107480 100644 --- a/content/base/src/Makefile.in +++ b/content/base/src/Makefile.in @@ -36,6 +36,7 @@ CPPSRCS = \ nsCopySupport.cpp \ nsCrossSiteListenerProxy.cpp \ nsCSPService.cpp \ + nsCxPusher.cpp \ nsDataDocumentContentPolicy.cpp \ Attr.cpp \ nsDOMAttributeMap.cpp \ diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index ae932a9d73ea..8a5eea6300f8 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -2954,176 +2954,6 @@ nsContentUtils::GetEventArgNames(int32_t aNameSpaceID, } } -nsCxPusher::nsCxPusher() - : mScriptIsRunning(false), - mPushedSomething(false) -{ -} - -nsCxPusher::~nsCxPusher() -{ - Pop(); -} - -bool -nsCxPusher::Push(EventTarget *aCurrentTarget) -{ - if (mPushedSomething) { - NS_ERROR("Whaaa! No double pushing with nsCxPusher::Push()!"); - - return false; - } - - NS_ENSURE_TRUE(aCurrentTarget, false); - nsresult rv; - nsIScriptContext* scx = - aCurrentTarget->GetContextForEventHandlers(&rv); -#ifdef DEBUG_smaug - NS_ENSURE_SUCCESS(rv, false); -#else - if(NS_FAILED(rv)) { - return false; - } -#endif - - if (!scx) { - // The target may have a special JS context for event handlers. - JSContext* cx = aCurrentTarget->GetJSContextForEventHandlers(); - if (cx) { - DoPush(cx); - } - - // Nothing to do here, I guess. Have to return true so that event firing - // will still work correctly even if there is no associated JSContext - return true; - } - - JSContext* cx = scx ? scx->GetNativeContext() : nullptr; - - // If there's no native context in the script context it must be - // in the process or being torn down. We don't want to notify the - // script context about scripts having been evaluated in such a - // case, calling with a null cx is fine in that case. - Push(cx); - return true; -} - -bool -nsCxPusher::RePush(EventTarget *aCurrentTarget) -{ - if (!mPushedSomething) { - return Push(aCurrentTarget); - } - - if (aCurrentTarget) { - nsresult rv; - nsIScriptContext* scx = - aCurrentTarget->GetContextForEventHandlers(&rv); - if (NS_FAILED(rv)) { - Pop(); - return false; - } - - // If we have the same script context and native context is still - // alive, no need to Pop/Push. - if (scx && scx == mScx && - scx->GetNativeContext()) { - return true; - } - } - - Pop(); - return Push(aCurrentTarget); -} - -void -nsCxPusher::Push(JSContext *cx) -{ - MOZ_ASSERT(!mPushedSomething, "No double pushing with nsCxPusher::Push()!"); - MOZ_ASSERT(cx); - - // Hold a strong ref to the nsIScriptContext, just in case - // XXXbz do we really need to? If we don't get one of these in Pop(), is - // that really a problem? Or do we need to do this to effectively root |cx|? - mScx = GetScriptContextFromJSContext(cx); - - DoPush(cx); -} - -void -nsCxPusher::DoPush(JSContext* cx) -{ - nsIXPConnect *xpc = nsContentUtils::XPConnect(); - if (!xpc) { - // If someone tries to push a cx when we don't have the relevant state, - // it's probably safest to just crash. - MOZ_CRASH(); - } - - // NB: The GetDynamicScriptContext is historical and might not be sane. - if (cx && nsJSUtils::GetDynamicScriptContext(cx) && - xpc::danger::IsJSContextOnStack(cx)) - { - // If the context is on the stack, that means that a script - // is running at the moment in the context. - mScriptIsRunning = true; - } - - if (!xpc::danger::PushJSContext(cx)) { - MOZ_CRASH(); - } - - mPushedSomething = true; -#ifdef DEBUG - mPushedContext = cx; - if (cx) - mCompartmentDepthOnEntry = js::GetEnterCompartmentDepth(cx); -#endif -} - -void -nsCxPusher::PushNull() -{ - DoPush(nullptr); -} - -void -nsCxPusher::Pop() -{ - MOZ_ASSERT(nsContentUtils::XPConnect()); - if (!mPushedSomething) { - mScx = nullptr; - mPushedSomething = false; - - NS_ASSERTION(!mScriptIsRunning, "Huh, this can't be happening, " - "mScriptIsRunning can't be set here!"); - - return; - } - - // When we push a context, we may save the frame chain and pretend like we - // haven't entered any compartment. This gets restored on Pop(), but we can - // run into trouble if a Push/Pop are interleaved with a - // JSAutoEnterCompartment. Make sure the compartment depth right before we - // pop is the same as it was right after we pushed. - MOZ_ASSERT_IF(mPushedContext, mCompartmentDepthOnEntry == - js::GetEnterCompartmentDepth(mPushedContext)); - DebugOnly stackTop; - MOZ_ASSERT(mPushedContext == nsContentUtils::GetCurrentJSContext()); - xpc::danger::PopJSContext(); - - if (!mScriptIsRunning && mScx) { - // No JS is running in the context, but executing the event handler might have - // caused some JS to run. Tell the script context that it's done. - - mScx->ScriptEvaluated(true); - } - - mScx = nullptr; - mScriptIsRunning = false; - mPushedSomething = false; -} - static const char gPropertiesFiles[nsContentUtils::PropertiesFile_COUNT][56] = { // Must line up with the enum values in |PropertiesFile| enum. "chrome://global/locale/css.properties", @@ -6744,42 +6574,3 @@ nsContentUtils::InternalIsSupported(nsISupports* aObject, // Otherwise, we claim to support everything return true; } - -AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : mCx(nullptr) -{ - Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); -} - -AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) - : mCx(nullptr) -{ - Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); -} - -void -AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) -{ - MOZ_ASSERT(!mCx, "mCx should not be initialized!"); - - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - if (!aSafe) { - mCx = nsContentUtils::GetCurrentJSContext(); - } - - if (!mCx) { - mCx = nsContentUtils::GetSafeJSContext(); - mPusher.Push(mCx); - } -} - -AutoJSContext::operator JSContext*() -{ - return mCx; -} - -AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) - : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) -{ -} diff --git a/content/base/src/nsCxPusher.cpp b/content/base/src/nsCxPusher.cpp new file mode 100644 index 000000000000..9eecbb41eaaa --- /dev/null +++ b/content/base/src/nsCxPusher.cpp @@ -0,0 +1,230 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 "nsContentUtils.h" +#include "nsCxPusher.h" +#include "nsIScriptContext.h" +#include "mozilla/dom/EventTarget.h" +#include "nsJSUtils.h" +#include "nsDOMJSUtils.h" +#include "mozilla/Util.h" +#include "xpcpublic.h" + +using mozilla::dom::EventTarget; +using mozilla::DebugOnly; + +nsCxPusher::nsCxPusher() + : mScriptIsRunning(false), + mPushedSomething(false) +{ +} + +nsCxPusher::~nsCxPusher() +{ + Pop(); +} + +bool +nsCxPusher::Push(EventTarget *aCurrentTarget) +{ + if (mPushedSomething) { + NS_ERROR("Whaaa! No double pushing with nsCxPusher::Push()!"); + + return false; + } + + NS_ENSURE_TRUE(aCurrentTarget, false); + nsresult rv; + nsIScriptContext* scx = + aCurrentTarget->GetContextForEventHandlers(&rv); +#ifdef DEBUG_smaug + NS_ENSURE_SUCCESS(rv, false); +#else + if(NS_FAILED(rv)) { + return false; + } +#endif + + if (!scx) { + // The target may have a special JS context for event handlers. + JSContext* cx = aCurrentTarget->GetJSContextForEventHandlers(); + if (cx) { + DoPush(cx); + } + + // Nothing to do here, I guess. Have to return true so that event firing + // will still work correctly even if there is no associated JSContext + return true; + } + + JSContext* cx = scx ? scx->GetNativeContext() : nullptr; + + // If there's no native context in the script context it must be + // in the process or being torn down. We don't want to notify the + // script context about scripts having been evaluated in such a + // case, calling with a null cx is fine in that case. + Push(cx); + return true; +} + +bool +nsCxPusher::RePush(EventTarget *aCurrentTarget) +{ + if (!mPushedSomething) { + return Push(aCurrentTarget); + } + + if (aCurrentTarget) { + nsresult rv; + nsIScriptContext* scx = + aCurrentTarget->GetContextForEventHandlers(&rv); + if (NS_FAILED(rv)) { + Pop(); + return false; + } + + // If we have the same script context and native context is still + // alive, no need to Pop/Push. + if (scx && scx == mScx && + scx->GetNativeContext()) { + return true; + } + } + + Pop(); + return Push(aCurrentTarget); +} + +void +nsCxPusher::Push(JSContext *cx) +{ + MOZ_ASSERT(!mPushedSomething, "No double pushing with nsCxPusher::Push()!"); + MOZ_ASSERT(cx); + + // Hold a strong ref to the nsIScriptContext, just in case + // XXXbz do we really need to? If we don't get one of these in Pop(), is + // that really a problem? Or do we need to do this to effectively root |cx|? + mScx = GetScriptContextFromJSContext(cx); + + DoPush(cx); +} + +void +nsCxPusher::DoPush(JSContext* cx) +{ + nsIXPConnect *xpc = nsContentUtils::XPConnect(); + if (!xpc) { + // If someone tries to push a cx when we don't have the relevant state, + // it's probably safest to just crash. + MOZ_CRASH(); + } + + // NB: The GetDynamicScriptContext is historical and might not be sane. + if (cx && nsJSUtils::GetDynamicScriptContext(cx) && + xpc::danger::IsJSContextOnStack(cx)) + { + // If the context is on the stack, that means that a script + // is running at the moment in the context. + mScriptIsRunning = true; + } + + if (!xpc::danger::PushJSContext(cx)) { + MOZ_CRASH(); + } + + mPushedSomething = true; +#ifdef DEBUG + mPushedContext = cx; + if (cx) + mCompartmentDepthOnEntry = js::GetEnterCompartmentDepth(cx); +#endif +} + +void +nsCxPusher::PushNull() +{ + DoPush(nullptr); +} + +void +nsCxPusher::Pop() +{ + MOZ_ASSERT(nsContentUtils::XPConnect()); + if (!mPushedSomething) { + mScx = nullptr; + mPushedSomething = false; + + NS_ASSERTION(!mScriptIsRunning, "Huh, this can't be happening, " + "mScriptIsRunning can't be set here!"); + + return; + } + + // When we push a context, we may save the frame chain and pretend like we + // haven't entered any compartment. This gets restored on Pop(), but we can + // run into trouble if a Push/Pop are interleaved with a + // JSAutoEnterCompartment. Make sure the compartment depth right before we + // pop is the same as it was right after we pushed. + MOZ_ASSERT_IF(mPushedContext, mCompartmentDepthOnEntry == + js::GetEnterCompartmentDepth(mPushedContext)); + DebugOnly stackTop; + MOZ_ASSERT(mPushedContext == nsContentUtils::GetCurrentJSContext()); + xpc::danger::PopJSContext(); + + if (!mScriptIsRunning && mScx) { + // No JS is running in the context, but executing the event handler might have + // caused some JS to run. Tell the script context that it's done. + + mScx->ScriptEvaluated(true); + } + + mScx = nullptr; + mScriptIsRunning = false; + mPushedSomething = false; +} + +namespace mozilla { + +AutoJSContext::AutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : mCx(nullptr) +{ + Init(false MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); +} + +AutoJSContext::AutoJSContext(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mCx(nullptr) +{ + Init(aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT); +} + +void +AutoJSContext::Init(bool aSafe MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) +{ + MOZ_ASSERT(!mCx, "mCx should not be initialized!"); + + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + if (!aSafe) { + mCx = nsContentUtils::GetCurrentJSContext(); + } + + if (!mCx) { + mCx = nsContentUtils::GetSafeJSContext(); + mPusher.Push(mCx); + } +} + +AutoJSContext::operator JSContext*() +{ + return mCx; +} + +AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) + : AutoJSContext(true MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) +{ +} + +} // namespace mozilla