Do lazy evaluation of XBL fields. Bug 372769, r=mrbkap, sr=sicking

This commit is contained in:
bzbarsky@mit.edu 2007-09-26 06:55:06 -07:00
parent 8929db3c74
commit ffd52a5efc
15 changed files with 492 additions and 143 deletions

View File

@ -111,6 +111,7 @@ LOCAL_INCLUDES = \
-I$(srcdir)/../../xul/document/src \
-I$(srcdir)/../../events/src \
-I$(srcdir)/../../../layout/style \
-I$(srcdir)/../../../dom/src/base \
$(NULL)
DEFINES += -D_IMPL_NS_LAYOUT

View File

@ -102,27 +102,127 @@
#include "prprf.h"
#include "nsNodeUtils.h"
// Nasty hack. Maybe we could move some of the classinfo utility methods
// (e.g. WrapNative and ThrowJSException) over to nsContentUtils?
#include "nsDOMClassInfo.h"
#include "nsJSUtils.h"
// Helper classes
/***********************************************************************/
//
// The JS class for XBLBinding
//
PR_STATIC_CALLBACK(void)
JS_STATIC_DLL_CALLBACK(void)
XBLFinalize(JSContext *cx, JSObject *obj)
{
nsXBLPrototypeBinding* protoBinding =
static_cast<nsXBLPrototypeBinding*>(::JS_GetPrivate(cx, obj));
protoBinding->XBLDocumentInfo()->Release();
nsXBLJSClass* c = static_cast<nsXBLJSClass*>(::JS_GetClass(cx, obj));
c->Drop();
}
JS_STATIC_DLL_CALLBACK(JSBool)
XBLResolve(JSContext *cx, JSObject *obj, jsval id, uintN flags,
JSObject **objp)
{
// Note: if we get here, that means that the implementation for some binding
// was installed, which means that AllowScripts() tested true. Hence no need
// to do checks like that here.
// Default to not resolving things.
NS_ASSERTION(*objp, "Must have starting object");
JSObject* origObj = *objp;
*objp = NULL;
if (!JSVAL_IS_STRING(id)) {
return JS_TRUE;
}
nsDependentJSString fieldName(id);
nsXBLPrototypeBinding* protoBinding =
static_cast<nsXBLPrototypeBinding*>(::JS_GetPrivate(cx, obj));
NS_ASSERTION(protoBinding, "Must have prototype binding!");
nsXBLProtoImplField* field = protoBinding->FindField(fieldName);
if (!field) {
return JS_TRUE;
}
// We have this field. Time to install it. Get our node.
JSClass* nodeClass = ::JS_GetClass(cx, origObj);
if (!nodeClass) {
return JS_FALSE;
}
if (~nodeClass->flags &
(JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS)) {
// Looks like whatever |origObj| is it's not our nsIContent. It might well
// be the proto our binding installed, however, so just baul out quietly.
// Do NOT throw an exception here.
// We could make this stricter by checking the class maybe, but whatever
return JS_TRUE;
}
nsCOMPtr<nsIXPConnectWrappedNative> xpcWrapper =
do_QueryInterface(static_cast<nsISupports*>(::JS_GetPrivate(cx, origObj)));
if (!xpcWrapper) {
nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED);
return JS_FALSE;
}
nsCOMPtr<nsIContent> content = do_QueryWrappedNative(xpcWrapper);
if (!content) {
nsDOMClassInfo::ThrowJSException(cx, NS_ERROR_UNEXPECTED);
return JS_FALSE;
}
// This mirrors code in nsXBLProtoImpl::InstallImplementation
nsIDocument* doc = content->GetOwnerDoc();
if (!doc) {
return JS_TRUE;
}
nsIScriptGlobalObject* global = doc->GetScriptGlobalObject();
if (!global) {
return JS_TRUE;
}
nsCOMPtr<nsIScriptContext> context = global->GetContext();
if (!context) {
return JS_TRUE;
}
// Now we either resolve or fail
*objp = origObj;
nsresult rv = field->InstallField(context, origObj,
protoBinding->DocURI());
if (NS_FAILED(rv)) {
if (!::JS_IsExceptionPending(cx)) {
nsDOMClassInfo::ThrowJSException(cx, rv);
}
return JS_FALSE;
}
return JS_TRUE;
}
nsXBLJSClass::nsXBLJSClass(const nsAFlatCString& aClassName)
{
memset(this, 0, sizeof(nsXBLJSClass));
next = prev = static_cast<JSCList*>(this);
name = ToNewCString(aClassName);
flags =
JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_RESOLVE_GETS_START;
addProperty = delProperty = setProperty = getProperty = ::JS_PropertyStub;
enumerate = ::JS_EnumerateStub;
resolve = ::JS_ResolveStub;
resolve = (JSResolveOp)XBLResolve;
convert = ::JS_ConvertStub;
finalize = XBLFinalize;
}
@ -1034,6 +1134,7 @@ nsXBLBinding::WalkRules(nsIStyleRuleProcessor::EnumFunc aFunc, void* aData)
nsresult
nsXBLBinding::DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj,
const nsAFlatCString& aClassName,
nsXBLPrototypeBinding* aProtoBinding,
void **aClassObject)
{
// First ensure our JS class is initialized.
@ -1139,6 +1240,11 @@ nsXBLBinding::DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj,
return NS_ERROR_OUT_OF_MEMORY;
}
::JS_SetPrivate(cx, proto, aProtoBinding);
// Keep this proto binding alive while we're alive
aProtoBinding->XBLDocumentInfo()->AddRef();
*aClassObject = (void*)proto;
}
else {
@ -1155,63 +1261,6 @@ nsXBLBinding::DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj,
return NS_OK;
}
nsresult
nsXBLBinding::InitClass(const nsCString& aClassName,
nsIScriptContext* aContext,
nsIDocument* aDocument, void** aScriptObject,
void** aClassObject)
{
*aClassObject = nsnull;
*aScriptObject = nsnull;
nsresult rv;
// Obtain the bound element's current script object.
JSContext* cx = (JSContext*)aContext->GetNativeContext();
nsIDocument *ownerDoc = mBoundElement->GetOwnerDoc();
nsIScriptGlobalObject *sgo;
if (!ownerDoc || !(sgo = ownerDoc->GetScriptGlobalObject())) {
NS_ERROR("Can't find global object for bound content!");
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
rv = nsContentUtils::XPConnect()->WrapNative(cx, sgo->GetGlobalJSObject(),
mBoundElement,
NS_GET_IID(nsISupports),
getter_AddRefs(wrapper));
NS_ENSURE_SUCCESS(rv, rv);
JSObject* object = nsnull;
rv = wrapper->GetJSObject(&object);
NS_ENSURE_SUCCESS(rv, rv);
*aScriptObject = object;
// First ensure our JS class is initialized.
rv = DoInitJSClass(cx, sgo->GetGlobalJSObject(), object, aClassName,
aClassObject);
NS_ENSURE_SUCCESS(rv, rv);
// Root mBoundElement so that it doesn't lose it's binding
nsIDocument* doc = mBoundElement->GetOwnerDoc();
if (doc) {
nsCOMPtr<nsIXPConnectWrappedNative> native_wrapper =
do_QueryInterface(wrapper);
if (native_wrapper) {
doc->AddReference(mBoundElement, native_wrapper);
}
}
return NS_OK;
}
PRBool
nsXBLBinding::AllowScripts()
{
@ -1344,6 +1393,20 @@ nsXBLBinding::GetFirstStyleBinding()
return mNextBinding ? mNextBinding->GetFirstStyleBinding() : nsnull;
}
PRBool
nsXBLBinding::ResolveAllFields(JSContext *cx, JSObject *obj) const
{
if (!mPrototypeBinding->ResolveAllFields(cx, obj)) {
return PR_FALSE;
}
if (mNextBinding) {
return mNextBinding->ResolveAllFields(cx, obj);
}
return PR_TRUE;
}
void
nsXBLBinding::MarkForDeath()
{

View File

@ -132,6 +132,10 @@ public:
nsXBLBinding* RootBinding();
nsXBLBinding* GetFirstStyleBinding();
// Resolve all the fields for this binding and all ancestor bindings on the
// object |obj|. False return means a JS exception was set.
PRBool ResolveAllFields(JSContext *cx, JSObject *obj) const;
// Get the list of insertion points for aParent. The nsInsertionPointList
// is owned by the binding, you should not delete it.
nsresult GetInsertionPointsFor(nsIContent* aParent,
@ -155,16 +159,11 @@ public:
static nsresult DoInitJSClass(JSContext *cx, JSObject *global, JSObject *obj,
const nsAFlatCString& aClassName,
nsXBLPrototypeBinding* aProtoBinding,
void **aClassObject);
PRBool AllowScripts(); // XXX make const
// Internal member functions
protected:
nsresult InitClass(const nsCString& aClassName, nsIScriptContext* aContext,
nsIDocument* aDocument, void** aScriptObject,
void** aClassObject);
// MEMBER VARIABLES
protected:
nsAutoRefCnt mRefCnt;

View File

@ -83,12 +83,13 @@ nsXBLContentSink::nsXBLContentSink()
: mState(eXBL_InDocument),
mSecondaryState(eXBL_None),
mDocInfo(nsnull),
mFoundFirstBinding(PR_FALSE),
mIsChromeOrResource(PR_FALSE),
mFoundFirstBinding(PR_FALSE),
mBinding(nsnull),
mHandler(nsnull),
mImplementation(nsnull),
mImplMember(nsnull),
mImplField(nsnull),
mProperty(nsnull),
mMethod(nsnull),
mField(nsnull)
@ -263,6 +264,18 @@ nsXBLContentSink::AddMember(nsXBLProtoImplMember* aMember)
mImplMember = aMember; // Adjust our pointer to point to the new last member in the chain.
}
void
nsXBLContentSink::AddField(nsXBLProtoImplField* aField)
{
// Add this field to our chain.
if (mImplField)
mImplField->SetNext(aField); // Already have a chain. Just append to the end.
else
mImplementation->SetFieldList(aField); // We're the first member in the chain.
mImplField = aField; // Adjust our pointer to point to the new last field in the chain.
}
NS_IMETHODIMP
nsXBLContentSink::HandleStartElement(const PRUnichar *aName,
const PRUnichar **aAtts,
@ -703,7 +716,8 @@ nsXBLContentSink::ConstructImplementation(const PRUnichar **aAtts)
{
mImplementation = nsnull;
mImplMember = nsnull;
mImplField = nsnull;
if (!mBinding)
return;
@ -777,7 +791,7 @@ nsXBLContentSink::ConstructField(const PRUnichar **aAtts, PRUint32 aLineNumber)
mField = new nsXBLProtoImplField(name, readonly);
if (mField) {
mField->SetLineNumber(aLineNumber);
AddMember(mField);
AddField(mField);
}
}
}

View File

@ -157,6 +157,7 @@ protected:
nsresult ReportUnexpectedElement(nsIAtom* aElementName, PRUint32 aLineNumber);
void AddMember(nsXBLProtoImplMember* aMember);
void AddField(nsXBLProtoImplField* aField);
XBLPrimaryState mState;
XBLSecondaryState mSecondaryState;
@ -168,6 +169,7 @@ protected:
nsXBLPrototypeHandler* mHandler; // current handler, owned by its PrototypeBinding
nsXBLProtoImpl* mImplementation;
nsXBLProtoImplMember* mImplMember;
nsXBLProtoImplField* mImplField;
nsXBLProtoImplProperty* mProperty;
nsXBLProtoImplMethod* mMethod;
nsXBLProtoImplField* mField;

View File

@ -47,6 +47,7 @@
#include "nsIServiceManager.h"
#include "nsIXBLDocumentInfo.h"
#include "nsIDOMNode.h"
#include "nsXBLPrototypeBinding.h"
nsresult
nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement)
@ -55,7 +56,7 @@ nsXBLProtoImpl::InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIConten
// this prototype implementation as a guide. The prototype implementation is compiled lazily,
// so for the first bound element that needs a concrete implementation, we also build the
// prototype implementation.
if (!mMembers) // Constructor and destructor also live in mMembers
if (!mMembers && !mFields) // Constructor and destructor also live in mMembers
return NS_OK; // Nothing to do, so let's not waste time.
// If the way this gets the script context changes, fix
@ -214,6 +215,37 @@ nsXBLProtoImpl::Traverse(nsCycleCollectionTraversalCallback &cb) const
}
}
nsXBLProtoImplField*
nsXBLProtoImpl::FindField(const nsString& aFieldName) const
{
for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
if (aFieldName.Equals(f->GetName())) {
return f;
}
}
return nsnull;
}
PRBool
nsXBLProtoImpl::ResolveAllFields(JSContext *cx, JSObject *obj) const
{
for (nsXBLProtoImplField* f = mFields; f; f = f->GetNext()) {
// Using OBJ_LOOKUP_PROPERTY is a pain, since what we have is a
// PRUnichar* for the property name. Let's just use the public API and
// all.
nsDependentString name(f->GetName());
jsval dummy;
if (!::JS_LookupUCProperty(cx, obj,
reinterpret_cast<const jschar*>(name.get()),
name.Length(), &dummy)) {
return PR_FALSE;
}
}
return PR_TRUE;
}
void
nsXBLProtoImpl::DestroyMembers(nsXBLProtoImplMember* aBrokenMember)
{

View File

@ -42,9 +42,11 @@
#include "nsMemory.h"
#include "nsXBLPrototypeHandler.h"
#include "nsXBLProtoImplMember.h"
#include "nsXBLPrototypeBinding.h"
#include "nsXBLProtoImplField.h"
class nsIXPConnectJSObjectHolder;
class nsXBLPrototypeBinding;
class nsXBLProtoImplAnonymousMethod;
class nsXBLProtoImpl
{
@ -52,6 +54,7 @@ public:
nsXBLProtoImpl()
: mClassObject(nsnull),
mMembers(nsnull),
mFields(nsnull),
mConstructor(nsnull),
mDestructor(nsnull)
{
@ -64,7 +67,8 @@ public:
// clean them up automatically.
for (nsXBLProtoImplMember* curr = mMembers; curr; curr=curr->GetNext())
curr->Destroy(mClassObject != nsnull);
delete mMembers;
delete mMembers;
delete mFields;
}
nsresult InstallImplementation(nsXBLPrototypeBinding* aBinding, nsIContent* aBoundElement);
@ -74,10 +78,26 @@ public:
void** aTargetClassObject);
nsresult CompilePrototypeMembers(nsXBLPrototypeBinding* aBinding);
void SetMemberList(nsXBLProtoImplMember* aMemberList) { delete mMembers; mMembers = aMemberList; }
void SetMemberList(nsXBLProtoImplMember* aMemberList)
{
delete mMembers;
mMembers = aMemberList;
}
void SetFieldList(nsXBLProtoImplField* aFieldList)
{
delete mFields;
mFields = aFieldList;
}
void Traverse(nsCycleCollectionTraversalCallback &cb) const;
nsXBLProtoImplField* FindField(const nsString& aFieldName) const;
// Resolve all the fields for this implementation on the object |obj| False
// return means a JS exception was set.
PRBool ResolveAllFields(JSContext *cx, JSObject *obj) const;
protected:
// Function to call if compilation of a member fails. When this is called,
// all members before aBrokenMember are compiled, compilation of
@ -93,6 +113,8 @@ protected:
// and methods for the binding.
nsXBLProtoImplMember* mMembers; // The members of an implementation are chained in this singly-linked list.
nsXBLProtoImplField* mFields; // Our fields
public:
nsXBLProtoImplAnonymousMethod* mConstructor; // Our class constructor.

View File

@ -46,14 +46,17 @@
#include "nsXBLProtoImplField.h"
#include "nsIScriptContext.h"
#include "nsContentUtils.h"
#include "nsIURI.h"
nsXBLProtoImplField::nsXBLProtoImplField(const PRUnichar* aName, const PRUnichar* aReadOnly)
: nsXBLProtoImplMember(aName),
: mNext(nsnull),
mFieldText(nsnull),
mFieldTextLength(0),
mLineNumber(0)
{
MOZ_COUNT_CTOR(nsXBLProtoImplField);
mName = NS_strdup(aName); // XXXbz make more sense to use a stringbuffer?
mJSAttributes = JSPROP_ENUMERATE;
if (aReadOnly) {
nsAutoString readOnly; readOnly.Assign(*aReadOnly);
@ -67,11 +70,8 @@ nsXBLProtoImplField::~nsXBLProtoImplField()
MOZ_COUNT_DTOR(nsXBLProtoImplField);
if (mFieldText)
nsMemory::Free(mFieldText);
}
void
nsXBLProtoImplField::Destroy(PRBool aIsCompiled)
{
NS_Free(mName);
delete mNext;
}
void
@ -92,27 +92,15 @@ nsXBLProtoImplField::AppendFieldText(const nsAString& aText)
}
nsresult
nsXBLProtoImplField::InstallMember(nsIScriptContext* aContext,
nsIContent* aBoundElement,
void* aScriptObject,
void* aTargetClassObject,
const nsCString& aClassStr)
nsXBLProtoImplField::InstallField(nsIScriptContext* aContext,
JSObject* aBoundNode,
nsIURI* aBindingDocURI) const
{
if (mFieldTextLength == 0)
return NS_OK; // nothing to do.
NS_PRECONDITION(aBoundNode,
"uh-oh, bound node should NOT be null or bad things will "
"happen");
JSContext* cx = (JSContext*) aContext->GetNativeContext();
NS_ASSERTION(aScriptObject, "uh-oh, script Object should NOT be null or bad things will happen");
if (!aScriptObject)
return NS_ERROR_FAILURE;
nsCAutoString bindingURI(aClassStr);
PRInt32 hash = bindingURI.RFindChar('#');
if (hash != kNotFound)
bindingURI.Truncate(hash);
// compile the literal string
jsval result = JSVAL_NULL;
jsval result = JSVAL_VOID;
// EvaluateStringWithValue and JS_DefineUCProperty can both trigger GC, so
// protect |result| here.
@ -120,39 +108,40 @@ nsXBLProtoImplField::InstallMember(nsIScriptContext* aContext,
nsAutoGCRoot root(&result, &rv);
if (NS_FAILED(rv))
return rv;
PRBool undefined;
// XXX Need a URI here!
nsCOMPtr<nsIScriptContext> context = aContext;
rv = context->EvaluateStringWithValue(nsDependentString(mFieldText,
mFieldTextLength),
aScriptObject,
nsnull, bindingURI.get(),
mLineNumber, nsnull,
(void*) &result, &undefined);
if (NS_FAILED(rv))
return rv;
if (!undefined) {
// Define the evaluated result as a JS property
nsDependentString name(mName);
JSAutoRequest ar(cx);
if (!::JS_DefineUCProperty(cx, static_cast<JSObject *>(aScriptObject),
reinterpret_cast<const jschar*>(mName),
name.Length(), result, nsnull, nsnull, mJSAttributes))
return NS_ERROR_OUT_OF_MEMORY;
if (mFieldTextLength != 0) {
nsCAutoString uriSpec;
aBindingDocURI->GetSpec(uriSpec);
// compile the literal string
// XXX Could we produce a better principal here? Should be able
// to, really!
PRBool undefined;
nsCOMPtr<nsIScriptContext> context = aContext;
rv = context->EvaluateStringWithValue(nsDependentString(mFieldText,
mFieldTextLength),
aBoundNode,
nsnull, uriSpec.get(),
mLineNumber, nsnull,
(void*) &result, &undefined);
if (NS_FAILED(rv))
return rv;
if (undefined) {
result = JSVAL_VOID;
}
}
// Define the evaluated result as a JS property
nsDependentString name(mName);
JSContext* cx = (JSContext*) aContext->GetNativeContext();
JSAutoRequest ar(cx);
if (!::JS_DefineUCProperty(cx, aBoundNode,
reinterpret_cast<const jschar*>(mName),
name.Length(), result, nsnull, nsnull,
mJSAttributes)) {
return NS_ERROR_OUT_OF_MEMORY;
}
return NS_OK;
}
nsresult
nsXBLProtoImplField::CompileMember(nsIScriptContext* aContext, const nsCString& aClassStr,
void* aClassObject)
{
return NS_OK;
}
void
nsXBLProtoImplField::Traverse(nsCycleCollectionTraversalCallback &cb) const
{
}

View File

@ -46,30 +46,31 @@
#include "nsString.h"
#include "nsXBLProtoImplMember.h"
class nsXBLProtoImplField: public nsXBLProtoImplMember
class nsIURI;
class nsXBLProtoImplField
{
public:
nsXBLProtoImplField(const PRUnichar* aName, const PRUnichar* aReadOnly);
virtual ~nsXBLProtoImplField();
virtual void Destroy(PRBool aIsCompiled);
~nsXBLProtoImplField();
void AppendFieldText(const nsAString& aText);
void SetLineNumber(PRUint32 aLineNumber) {
mLineNumber = aLineNumber;
}
virtual nsresult InstallMember(nsIScriptContext* aContext,
nsIContent* aBoundElement,
void* aScriptObject,
void* aTargetClassObject,
const nsCString& aClassStr);
virtual nsresult CompileMember(nsIScriptContext* aContext,
const nsCString& aClassStr,
void* aClassObject);
nsXBLProtoImplField* GetNext() const { return mNext; }
void SetNext(nsXBLProtoImplField* aNext) { mNext = aNext; }
virtual void Traverse(nsCycleCollectionTraversalCallback &cb) const;
nsresult InstallField(nsIScriptContext* aContext,
JSObject* aBoundNode, nsIURI*
aBindingDocURI) const;
const PRUnichar* GetName() const { return mName; }
protected:
nsXBLProtoImplField* mNext;
PRUnichar* mName;
PRUnichar* mFieldText;
PRUint32 mFieldTextLength;
PRUint32 mLineNumber;

View File

@ -822,7 +822,7 @@ nsXBLPrototypeBinding::InitClass(const nsCString& aClassName,
*aClassObject = nsnull;
return nsXBLBinding::DoInitJSClass(aContext, aGlobal, aScriptObject,
aClassName, aClassObject);
aClassName, this, aClassObject);
}
nsIContent*

View File

@ -50,6 +50,7 @@
#include "nsHashtable.h"
#include "nsIXBLDocumentInfo.h"
#include "nsCOMArray.h"
#include "nsXBLProtoImpl.h"
class nsIAtom;
class nsIDocument;
@ -58,7 +59,7 @@ class nsISupportsArray;
class nsSupportsHashtable;
class nsIXBLService;
class nsFixedSizeAllocator;
class nsXBLProtoImpl;
class nsXBLProtoImplField;
class nsXBLBinding;
// *********************************************************************/
@ -94,6 +95,22 @@ public:
nsXBLProtoImplAnonymousMethod* GetDestructor();
nsresult SetDestructor(nsXBLProtoImplAnonymousMethod* aDestructor);
nsXBLProtoImplField* FindField(const nsString& aFieldName) const
{
return mImplementation ? mImplementation->FindField(aFieldName) : nsnull;
}
// Resolve all the fields for this binding on the object |obj|.
// False return means a JS exception was set.
PRBool ResolveAllFields(JSContext* cx, JSObject* obj) const
{
return !mImplementation || mImplementation->ResolveAllFields(cx, obj);
}
const nsCString& ClassName() const {
return mImplementation ? mImplementation->mClassName : EmptyCString();
}
nsresult InitClass(const nsCString& aClassName, JSContext * aContext,
JSObject * aGlobal, JSObject * aScriptObject,
void ** aClassObject);

View File

@ -50,6 +50,7 @@ _TEST_FILES = \
test_bug296375.xul \
test_bug366770.html \
test_bug371724.xhtml \
test_bug372769.xhtml \
$(NULL)
libs:: $(_TEST_FILES)

View File

@ -0,0 +1,179 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=372769
-->
<head>
<title>Test for Bug 372769</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<bindings xmlns="http://www.mozilla.org/xbl">
<binding id="test1">
<implementation>
<field name="one">1</field>
<field name="two">9</field>
<field name="three">3</field>
<field name="four">10</field>
<field name="five">11</field>
<field name="six">this.four = 4; 6;</field>
<field name="seven">this.five = 5; 7;</field>
</implementation>
</binding>
<binding id="test2">
<implementation>
<!-- Tests for recursive resolves -->
<field name="eight">this.eight</field>
<field name="nine">this.ten</field>
<field name="ten">this.nine</field>
<!-- Tests for non-DOM overrides -->
<field name="eleven">11</field>
<field name="twelve">12</field>
<!-- Tests for DOM overrides -->
<field name="parentNode">this.parentNode</field>
<field name="ownerDocument">"ownerDocument override"</field>
</implementation>
</binding>
<binding id="test3-ancestor">
<implementation>
<field name="thirteen">"13 ancestor"</field>
<field name="fourteen">"14 ancestor"</field>
<property name="fifteen" readonly="true" onget="return '15 ancestor'"/>
</implementation>
</binding>
<binding id="test3" extends="#test3-ancestor">
<implementation>
<field name="thirteen">13</field>
<field name="fifteen">15</field>
<field name="sixteen">16</field>
<field name="sixteen">"16 later"</field>
<field name="seventeen">17</field>
<field name="eighteen">"18 field"</field>
<property name="eighteen" readonly="true" onget="return 18"/>
<property name="nineteen" readonly="true" onget="return 19"/>
<field name="nineteen">"19 field"</field>
</implementation>
</binding>
<binding id="test4">
<implementation>
<field name="twenty">for (var i in this) ; 20;</field>
</implementation>
</binding>
<binding id="test5">
<implementation>
<field name="twenty-one">for (var i in this) ; 21;</field>
</implementation>
</binding>
</bindings>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=372769">Mozilla Bug 372769</a>
<p id="display1" style="-moz-binding: url(#test1)"></p>
<p id="display2" style="-moz-binding: url(#test2)"></p>
<p id="display3" style="-moz-binding: url(#test3)"></p>
<p id="display4" style="-moz-binding: url(#test4)"></p>
<p id="display5" style="-moz-binding: url(#test5)"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
/** Test for Bug 372769 **/
SimpleTest.waitForExplicitFinish();
addLoadEvent(function() {
var d = $("display1");
is(d.one, 1, "Should be able to read field");
d.two = 2;
is(d.two, 2, "Should be able to write field");
is("three" in d, true, 'Should have a property named "three"');
is(d.three, 3, "Should be 3");
is(d.four, 10, "Unexpected value so far");
// Save "five" for now
is(d.six, 6, "Should be 6");
is(d.four, 4, "Now should be 4");
d.four = 9;
is(d.four, 9, "Just set it to 9");
var found = false;
for (var prop in d) {
if (prop == "seven") {
found = true;
break;
}
}
is(found, true, "Enumeration is broken");
is(d.four, 9, "Shouldn't have rerun field six");
is(d.five, 5, "Should have run field 7");
is(d.seven, 7, "Should be 7")
d = $("display2");
is(typeof(d.eight), "undefined", "Recursive resolve should bail out");
is(typeof(d.nine), "undefined", "Recursive double resolve should bail out");
is(typeof(d.ten), "undefined",
"This recursive double resolve should bail out too");
// Get .eleven so it's resolved now
is(d.eleven, 11, "Unexpected value for .eleven");
var newProto = {};
newProto.eleven = "Proto 11";
newProto.twelve = "Proto 12";
newProto.__proto__ = d.__proto__;
d.__proto__ = newProto;
is(d.eleven, 11, "Proto should not have affected this");
is(d.twelve, "Proto 12", "Proto should have overridden 'twelve'");
is(d.parentNode, undefined, "We overrode this, yes we did");
is(typeof(d.parentNode), "undefined", "This is a recursive resolve too");
is(d.ownerDocument, "ownerDocument override",
"Should have overridden ownerDocument");
d = $("display3");
is(d.thirteen, 13, "descendant should win here");
is(d.fourteen, "14 ancestor",
"ancestor should win if descendant does nothing")
is(d.fifteen, 15,
"Field beats ancestor's property, since the latter lives on higher proto")
is(d.sixteen, 16, "First field wins");
is(d.__proto__.seventeen, undefined, "Shouldn't have this on proto");
is(typeof(d.__proto__.seventeen), "undefined",
"Really, should be undefined");
is(d.seventeen, 17, "Should have this prop on the node itself, though");
is(d.eighteen, 18, "Property beats field");
is(d.nineteen, 19, "Property still beats field");
d = $("display4");
is(d.twenty, 20, "Should be 20");
d = $("display5");
found = false;
for (var prop2 in d) {
if (prop2 == "twenty-one") {
found = true;
break;
}
}
is(found, true, "Enumeration is broken");
is(d["twenty-one"], 21, "Should be 21");
});
addLoadEvent(SimpleTest.finish);
]]>
</script>
</pre>
</body>
</html>

View File

@ -477,7 +477,8 @@ static const char kDOMStringBundleURL[] =
// possible.
#define ELEMENT_SCRIPTABLE_FLAGS \
(NODE_SCRIPTABLE_FLAGS & ~nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY)
((NODE_SCRIPTABLE_FLAGS & ~nsIXPCScriptable::CLASSINFO_INTERFACES_ONLY) | \
nsIXPCScriptable::WANT_ENUMERATE)
#define EXTERNAL_OBJ_SCRIPTABLE_FLAGS \
(ELEMENT_SCRIPTABLE_FLAGS & ~nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \
@ -6940,6 +6941,32 @@ nsElementSH::PostCreate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
return NS_OK;
}
NS_IMETHODIMP
nsElementSH::Enumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, PRBool *_retval)
{
// Make sure to not call the superclass here!
nsCOMPtr<nsIContent> content(do_QueryWrappedNative(wrapper));
NS_ENSURE_TRUE(content, NS_ERROR_UNEXPECTED);
nsIDocument* doc = content->GetOwnerDoc();
if (!doc) {
// Nothing else to do here
return NS_OK;
}
nsXBLBinding* binding = doc->BindingManager()->GetBinding(content);
if (!binding) {
// Nothing else to do here
return NS_OK;
}
*_retval = binding->ResolveAllFields(cx, obj);
return NS_OK;
}
// Generic array scriptable helper.
NS_IMETHODIMP

View File

@ -563,6 +563,8 @@ protected:
public:
NS_IMETHOD PostCreate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj);
NS_IMETHOD Enumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
JSObject *obj, PRBool *_retval);
static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
{