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.
This commit is contained in:
Bobby Holley 2013-05-22 10:05:25 -06:00
parent cfe8b9c5d2
commit 3aeb0284ff
6 changed files with 342 additions and 307 deletions

View File

@ -41,6 +41,7 @@ EXPORTS += [
'nsContentPolicyUtils.h',
'nsContentUtils.h',
'nsCopySupport.h',
'nsCxPusher.h',
'nsDOMFile.h',
'nsDeprecatedOperationList.h',
'nsDocElementCreatedNotificationRunner.h',

View File

@ -2266,40 +2266,6 @@ typedef nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace>
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<nsIScriptContext> 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___ */

View File

@ -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<nsIScriptContext> 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___ */

View File

@ -36,6 +36,7 @@ CPPSRCS = \
nsCopySupport.cpp \
nsCrossSiteListenerProxy.cpp \
nsCSPService.cpp \
nsCxPusher.cpp \
nsDataDocumentContentPolicy.cpp \
Attr.cpp \
nsDOMAttributeMap.cpp \

View File

@ -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<JSContext*> 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)
{
}

View File

@ -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<JSContext*> 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