/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=4 sw=4 et tw=78:
 *
 * 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 <stddef.h>
#include "jsversion.h"

size_t sE4XObjectsCreated = 0;

#if JS_HAS_XML_SUPPORT

#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "mozilla/Util.h"

#include "jstypes.h"
#include "jsprf.h"
#include "jsutil.h"
#include "jsapi.h"
#include "jsarray.h"
#include "jsatom.h"
#include "jsbool.h"
#include "jscntxt.h"
#include "jsfun.h"
#include "jsgc.h"
#include "jslock.h"
#include "jsnum.h"
#include "jsobj.h"
#include "jsopcode.h"
#include "jsscope.h"
#include "jsscript.h"
#include "jsstr.h"
#include "jsxml.h"

#include "frontend/Parser.h"
#include "frontend/TokenStream.h"
#include "gc/Marking.h"
#include "vm/GlobalObject.h"
#include "vm/StringBuffer.h"

#include "jsatominlines.h"
#include "jsinferinlines.h"
#include "jsobjinlines.h"

#include "vm/Stack-inl.h"
#include "vm/String-inl.h"

#ifdef DEBUG
#include <string.h>     /* for #ifdef DEBUG memset calls */
#endif

using namespace js;
using namespace js::gc;
using namespace js::types;
using namespace js::frontend;

using mozilla::ArrayLength;

template<class T, class U>
struct IdentityOp
{
    typedef JSBool (* compare)(const T *a, const U *b);
};

template<class T>
static JSBool
pointer_match(const T *a, const T *b)
{
    return a == b;
}

/*
 * NOTES
 * - in the js shell, you must use the -x command line option, or call
 *   options('xml') before compiling anything that uses XML literals
 *
 * TODO
 * - XXXbe patrol
 * - Fuse objects and their JSXML* private data into single GC-things
 * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
 */

/*
 * Random utilities and global functions.
 */
const char js_AttributeName_str[] = "AttributeName";
const char js_localName_str[]     = "localName";
const char js_xml_parent_str[]    = "parent";
const char js_prefix_str[]        = "prefix";
const char js_toXMLString_str[]   = "toXMLString";
const char js_uri_str[]           = "uri";

const char js_amp_entity_str[]    = "&amp;";
const char js_gt_entity_str[]     = "&gt;";
const char js_lt_entity_str[]     = "&lt;";
const char js_quot_entity_str[]   = "&quot;";
const char js_leftcurly_entity_str[]   = "&#123;";

#define IS_STAR(str)  ((str)->length() == 1 && *(str)->chars() == '*')

static JSBool
GetXMLFunction(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp);

static JSBool
IsDeclared(const JSObject *obj)
{
    jsval v;

    JS_ASSERT(obj->getClass() == &NamespaceClass);
    v = obj->getNamespaceDeclared();
    JS_ASSERT(JSVAL_IS_VOID(v) || v == JSVAL_TRUE);
    return v == JSVAL_TRUE;
}

static JSBool
xml_isXMLName(JSContext *cx, unsigned argc, jsval *vp)
{
    *vp = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argc ? vp[2] : JSVAL_VOID));
    return JS_TRUE;
}

/*
 * This wrapper is needed because NewBuiltinClassInstance doesn't
 * call the constructor, and we need a place to set the
 * HAS_EQUALITY bit.
 */
static inline JSObject *
NewBuiltinClassInstanceXML(JSContext *cx, Class *clasp)
{
    if (!cx->runningWithTrustedPrincipals())
        ++sE4XObjectsCreated;

    return NewBuiltinClassInstance(cx, clasp);
}

#define DEFINE_GETTER(name,code)                                               \
    static JSBool                                                              \
    name(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)  \
    {                                                                          \
        code;                                                                  \
        return true;                                                           \
    }

/*
 * Namespace class and library functions.
 */
DEFINE_GETTER(NamePrefix_getter,
              if (obj->getClass() == &NamespaceClass) vp.set(obj->getNamePrefixVal()))
DEFINE_GETTER(NameURI_getter,
              if (obj->getClass() == &NamespaceClass) vp.set(obj->getNameURIVal()))

static JSBool
namespace_equality(JSContext *cx, HandleObject obj, HandleValue v, JSBool *bp)
{
    JSObject *obj2;

    JS_ASSERT(v.isObjectOrNull());
    obj2 = v.toObjectOrNull();
    *bp = (!obj2 || obj2->getClass() != &NamespaceClass)
          ? JS_FALSE
          : EqualStrings(obj->getNameURI(), obj2->getNameURI());
    return JS_TRUE;
}

JS_FRIEND_DATA(Class) js::NamespaceClass = {
    "Namespace",
    JSCLASS_HAS_RESERVED_SLOTS(JSObject::NAMESPACE_CLASS_RESERVED_SLOTS) |
    JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
    JS_PropertyStub,         /* addProperty */
    JS_PropertyStub,         /* delProperty */
    JS_PropertyStub,         /* getProperty */
    JS_StrictPropertyStub,   /* setProperty */
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    NULL,                    /* finalize    */
    NULL,                    /* checkAccess */
    NULL,                    /* call        */
    NULL,                    /* construct   */
    NULL,                    /* hasInstance */
    NULL,                    /* trace       */
    {
        namespace_equality,
        NULL,                /* outerObject    */
        NULL,                /* innerObject    */
        NULL,                /* iteratorObject */
        NULL,                /* wrappedObject  */
    }
};

#define NAMESPACE_ATTRS                                                       \
    (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)

static JSPropertySpec namespace_props[] = {
    {js_prefix_str, 0, NAMESPACE_ATTRS, JSOP_WRAPPER(NamePrefix_getter), JSOP_NULLWRAPPER},
    {js_uri_str,    0, NAMESPACE_ATTRS, JSOP_WRAPPER(NameURI_getter), JSOP_NULLWRAPPER},
    {0,0,0,JSOP_NULLWRAPPER, JSOP_NULLWRAPPER}
};

static JSBool
namespace_toString(JSContext *cx, unsigned argc, Value *vp)
{
    JSObject *obj = ToObject(cx, HandleValue::fromMarkedLocation(&vp[1]));
    if (!obj)
        return JS_FALSE;
    if (!obj->isNamespace()) {
        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &NamespaceClass);
        return JS_FALSE;
    }
    *vp = obj->getNameURIVal();
    return JS_TRUE;
}

static JSFunctionSpec namespace_methods[] = {
    JS_FN(js_toString_str,  namespace_toString,        0,0),
    JS_FS_END
};

static JSObject *
NewXMLNamespace(JSContext *cx, JSLinearString *prefix, JSLinearString *uri, JSBool declared)
{
    RootedObject obj(cx, NewBuiltinClassInstanceXML(cx, &NamespaceClass));
    if (!obj)
        return NULL;

    JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefixVal()));
    JS_ASSERT(JSVAL_IS_VOID(obj->getNameURIVal()));
    JS_ASSERT(JSVAL_IS_VOID(obj->getNamespaceDeclared()));

    /* Per ECMA-357, 13.2.5, these properties must be "own". */
    if (!JS_DefineProperties(cx, obj, namespace_props))
        return NULL;

    if (prefix)
        obj->setNamePrefix(prefix);
    if (uri)
        obj->setNameURI(uri);
    if (declared)
        obj->setNamespaceDeclared(JSVAL_TRUE);
    return obj;
}

/*
 * QName class and library functions.
 */
DEFINE_GETTER(QNameNameURI_getter,
              if (obj->getClass() == &QNameClass)
                  vp.set(JSVAL_IS_VOID(obj->getNameURIVal()) ? JSVAL_NULL : obj->getNameURIVal()))
DEFINE_GETTER(QNameLocalName_getter,
              if (obj->getClass() == &QNameClass)
                  vp.set(obj->getQNameLocalNameVal()))

static JSBool
qname_identity(JSObject *qna, const JSObject *qnb)
{
    JSLinearString *uri1 = qna->getNameURI();
    JSLinearString *uri2 = qnb->getNameURI();

    if (!uri1 ^ !uri2)
        return JS_FALSE;
    if (uri1 && !EqualStrings(uri1, uri2))
        return JS_FALSE;
    return EqualStrings(qna->getQNameLocalName(), qnb->getQNameLocalName());
}

static JSBool
qname_equality(JSContext *cx, HandleObject qn, HandleValue v, JSBool *bp)
{
    JSObject *obj2;

    obj2 = v.toObjectOrNull();
    *bp = (!obj2 || obj2->getClass() != &QNameClass)
          ? JS_FALSE
          : qname_identity(qn, obj2);
    return JS_TRUE;
}

JS_FRIEND_DATA(Class) js::QNameClass = {
    "QName",
    JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
    JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
    JS_PropertyStub,         /* addProperty */
    JS_PropertyStub,         /* delProperty */
    JS_PropertyStub,         /* getProperty */
    JS_StrictPropertyStub,   /* setProperty */
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    NULL,                    /* finalize    */
    NULL,                    /* checkAccess */
    NULL,                    /* call        */
    NULL,                    /* construct   */
    NULL,                    /* hasInstance */
    NULL,                    /* trace       */
    {
        qname_equality,
        NULL,                /* outerObject    */
        NULL,                /* innerObject    */
        NULL,                /* iteratorObject */
        NULL,                /* wrappedObject  */
    }
};

/*
 * Classes for the ECMA-357-internal types AttributeName and AnyName, which
 * are like QName, except that they have no property getters.  They share the
 * qname_toString method, and therefore are exposed as constructable objects
 * in this implementation.
 */
JS_FRIEND_DATA(Class) js::AttributeNameClass = {
    js_AttributeName_str,
    JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_PropertyStub,         /* addProperty */
    JS_PropertyStub,         /* delProperty */
    JS_PropertyStub,         /* getProperty */
    JS_StrictPropertyStub,   /* setProperty */
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub
};

JS_FRIEND_DATA(Class) js::AnyNameClass = {
    js_AnyName_str,
    JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
    JSCLASS_IS_ANONYMOUS,
    JS_PropertyStub,         /* addProperty */
    JS_PropertyStub,         /* delProperty */
    JS_PropertyStub,         /* getProperty */
    JS_StrictPropertyStub,   /* setProperty */
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub
};

#define QNAME_ATTRS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)

static JSPropertySpec qname_props[] = {
    {js_uri_str,       0, QNAME_ATTRS, JSOP_WRAPPER(QNameNameURI_getter),   JSOP_NULLWRAPPER},
    {js_localName_str, 0, QNAME_ATTRS, JSOP_WRAPPER(QNameLocalName_getter), JSOP_NULLWRAPPER},
    {0,0,0,JSOP_NULLWRAPPER,JSOP_NULLWRAPPER}
};

static JSString *
ConvertQNameToString(JSContext *cx, JSObject *obj)
{
    JS_ASSERT(obj->isQName());
    RootedString uri(cx, obj->getNameURI());
    RootedString str(cx);
    if (!uri) {
        /* No uri means wildcard qualifier. */
        str = cx->names().starQualifier;
    } else if (uri->empty()) {
        /* Empty string for uri means localName is in no namespace. */
        str = cx->runtime->emptyString;
    } else {
        RootedString qualstr(cx, cx->names().qualifier);
        str = js_ConcatStrings(cx, uri, qualstr);
        if (!str)
            return NULL;
    }
    Rooted<JSString*> localName(cx, obj->getQNameLocalName());
    str = js_ConcatStrings(cx, str, localName);
    if (!str)
        return NULL;

    if (obj->getClass() == &AttributeNameClass) {
        JS::Anchor<JSString *> anchor(str);
        size_t length = str->length();
        jschar *chars = cx->pod_malloc<jschar>(length + 2);
        if (!chars)
            return NULL;
        *chars = '@';
        const jschar *strChars = str->getChars(cx);
        if (!strChars) {
            js_free(chars);
            return NULL;
        }
        js_strncpy(chars + 1, strChars, length);
        chars[++length] = 0;
        str = js_NewString(cx, chars, length);
        if (!str) {
            js_free(chars);
            return NULL;
        }
    }
    return str;
}

static JSBool
qname_toString(JSContext *cx, unsigned argc, Value *vp)
{
    JSObject *obj = ToObject(cx, HandleValue::fromMarkedLocation(&vp[1]));
    if (!obj)
        return false;

    if (!obj->isQName()) {
        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &QNameClass);
        return false;
    }

    JSString *str = ConvertQNameToString(cx, obj);
    if (!str)
        return false;

    vp->setString(str);
    return true;
}

static JSFunctionSpec qname_methods[] = {
    JS_FN(js_toString_str,  qname_toString,    0,0),
    JS_FS_END
};


static bool
InitXMLQName(JSContext *cx, HandleObject obj, JSLinearString *uri, JSLinearString *prefix,
             JSAtom *localName)
{
    JS_ASSERT(obj->isQName());
    JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefixVal()));
    JS_ASSERT(JSVAL_IS_VOID(obj->getNameURIVal()));
    JS_ASSERT(JSVAL_IS_VOID(obj->getQNameLocalNameVal()));

    /* Per ECMA-357, 13.3.5, these properties must be "own". */
    if (!JS_DefineProperties(cx, obj, qname_props))
        return false;

    if (uri)
        obj->setNameURI(uri);
    if (prefix)
        obj->setNamePrefix(prefix);
    if (localName)
        obj->setQNameLocalName(localName);
    return true;
}

static JSObject *
NewXMLQName(JSContext *cx, JSLinearString *uri, JSLinearString *prefix,
            JSAtom *localName)
{
    RootedObject obj(cx, NewBuiltinClassInstanceXML(cx, &QNameClass));
    if (!obj)
        return NULL;
    if (!InitXMLQName(cx, obj, uri, prefix, localName))
        return NULL;
    return obj;
}

static JSObject *
NewXMLAttributeName(JSContext *cx, JSLinearString *uri, JSLinearString *prefix,
                    JSAtom *localName)
{
    /*
     * AttributeName is an internal anonymous class which instances are not
     * exposed to scripts.
     */
    RootedObject obj(cx, NewObjectWithGivenProto(cx, &AttributeNameClass, NULL, cx->global()));
    if (!obj)
        return NULL;
    JS_ASSERT(obj->isQName());
    if (!InitXMLQName(cx, obj, uri, prefix, localName))
        return NULL;
    return obj;
}

static JSObject *
ConstructObjectWithArguments(JSContext *cx, Class *clasp,
                             unsigned argc, jsval *argv)
{
    assertSameCompartment(cx, JSValueArray(argv, argc));

    AutoArrayRooter argtvr(cx, argc, argv);

    JSProtoKey protoKey = GetClassProtoKey(clasp);

    /* Protect constructor in case a crazy getter for .prototype uproots it. */
    RootedValue value(cx);
    RootedObject null(cx);
    if (!js_FindClassObject(cx, protoKey, &value, clasp))
        return NULL;

    Value rval;
    if (!InvokeConstructor(cx, value, argc, argv, &rval))
        return NULL;

    /*
     * If the instance's class differs from what was requested, throw a type
     * error.
     */
    if (!rval.isObject() || rval.toObject().getClass() != clasp) {
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                             JSMSG_WRONG_CONSTRUCTOR, clasp->name);
        return NULL;
    }
    return &rval.toObject();
}

JSObject *
js_ConstructXMLQNameObject(JSContext *cx, const Value &nsval, const Value &lnval)
{
    Value argv[2];

    /*
     * ECMA-357 11.1.2,
     * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
     * production, step 2.
     */
    if (nsval.isObject() &&
        nsval.toObject().getClass() == &AnyNameClass) {
        argv[0].setNull();
    } else {
        argv[0] = nsval;
    }
    argv[1] = lnval;
    return ConstructObjectWithArguments(cx, &QNameClass, 2, argv);
}

static JSBool
IsXMLName(const jschar *cp, size_t n)
{
    JSBool rv;
    jschar c;

    rv = JS_FALSE;
    if (n != 0 && unicode::IsXMLNamespaceStart(*cp)) {
        while (--n != 0) {
            c = *++cp;
            if (!unicode::IsXMLNamespacePart(c))
                return rv;
        }
        rv = JS_TRUE;
    }
    return rv;
}

JSBool
js_IsXMLName(JSContext *cx, jsval v)
{
    JSLinearString *name = NULL;
    JSErrorReporter older;

    /*
     * Inline specialization of the QName constructor called with v passed as
     * the only argument, to compute the localName for the constructed qname,
     * without actually allocating the object or computing its uri and prefix.
     * See ECMA-357 13.1.2.1 step 1 and 13.3.2.
     */
    if (!JSVAL_IS_PRIMITIVE(v) &&
        JSVAL_TO_OBJECT(v)->isQName()) {
        name = JSVAL_TO_OBJECT(v)->getQNameLocalName();
    } else {
        older = JS_SetErrorReporter(cx, NULL);
        JSString *str = ToString(cx, v);
        if (str)
            name = str->ensureLinear(cx);
        JS_SetErrorReporter(cx, older);
        if (!name) {
            JS_ClearPendingException(cx);
            return JS_FALSE;
        }
    }

    return IsXMLName(name->chars(), name->length());
}

/*
 * When argc is -1, it indicates argv is empty but the code should behave as
 * if argc is 1 and argv[0] is JSVAL_VOID.
 */
static JSBool
NamespaceHelper(JSContext *cx, int argc, jsval *argv, jsval *rval)
{
    jsval urival, prefixval;
    JSObject *uriobj;
    JSBool isNamespace, isQName;
    Class *clasp;
    JSLinearString *empty, *prefix, *uri;

    isNamespace = isQName = JS_FALSE;
#ifdef __GNUC__         /* suppress bogus gcc warnings */
    uriobj = NULL;
#endif
    if (argc <= 0) {
        urival = JSVAL_VOID;
    } else {
        urival = argv[argc > 1];
        if (!JSVAL_IS_PRIMITIVE(urival)) {
            uriobj = JSVAL_TO_OBJECT(urival);
            clasp = uriobj->getClass();
            isNamespace = (clasp == &NamespaceClass);
            isQName = (clasp == &QNameClass);
        }
    }

    /* Namespace called as function. */
    if (argc == 1 && isNamespace) {
        /* Namespace called with one Namespace argument is identity. */
        *rval = urival;
        return JS_TRUE;
    }

    RootedObject obj(cx, NewBuiltinClassInstanceXML(cx, &NamespaceClass));
    if (!obj)
        return JS_FALSE;

    /* Per ECMA-357, 13.2.5, these properties must be "own". */
    if (!JS_DefineProperties(cx, obj, namespace_props))
        return JS_FALSE;

    empty = cx->runtime->emptyString;
    obj->setNamePrefix(empty);
    obj->setNameURI(empty);

    if (argc == 1 || argc == -1) {
        if (isNamespace) {
            obj->setNameURI(uriobj->getNameURI());
            obj->setNamePrefix(uriobj->getNamePrefix());
        } else if (isQName && (uri = uriobj->getNameURI())) {
            obj->setNameURI(uri);
            obj->setNamePrefix(uriobj->getNamePrefix());
        } else {
            JSString *str = ToString(cx, urival);
            if (!str)
                return JS_FALSE;
            uri = str->ensureLinear(cx);
            if (!uri)
                return JS_FALSE;
            obj->setNameURI(uri);
            if (!uri->empty())
                obj->clearNamePrefix();
        }
    } else if (argc == 2) {
        if (!isQName || !(uri = uriobj->getNameURI())) {
            JSString *str = ToString(cx, urival);
            if (!str)
                return JS_FALSE;
            uri = str->ensureLinear(cx);
            if (!uri)
                return JS_FALSE;
        }
        obj->setNameURI(uri);

        prefixval = argv[0];
        if (uri->empty()) {
            if (!JSVAL_IS_VOID(prefixval)) {
                JSString *str = ToString(cx, prefixval);
                if (!str)
                    return JS_FALSE;
                if (!str->empty()) {
                    JSAutoByteString bytes;
                    if (js_ValueToPrintable(cx, StringValue(str), &bytes)) {
                        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                             JSMSG_BAD_XML_NAMESPACE, bytes.ptr());
                    }
                    return JS_FALSE;
                }
            }
        } else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
            obj->clearNamePrefix();
        } else {
            JSString *str = ToString(cx, prefixval);
            if (!str)
                return JS_FALSE;
            prefix = str->ensureLinear(cx);
            if (!prefix)
                return JS_FALSE;
            obj->setNamePrefix(prefix);
        }
    }

    *rval = OBJECT_TO_JSVAL(obj);
    return JS_TRUE;
}

static JSBool
Namespace(JSContext *cx, unsigned argc, Value *vp)
{
    return NamespaceHelper(cx, argc, vp + 2, vp);
}

/*
 * When argc is -1, it indicates argv is empty but the code should behave as
 * if argc is 1 and argv[0] is JSVAL_VOID.
 */
static JSBool
QNameHelper(JSContext *cx, int argc, jsval *argv, jsval *rval)
{
    jsval nameval, nsval;
    JSBool isQName, isNamespace;
    JSObject *qn;
    JSLinearString *uri, *prefix;
    JSObject *obj2;

    JSAtom *name;
    if (argc <= 0) {
        nameval = JSVAL_VOID;
        isQName = JS_FALSE;
    } else {
        nameval = argv[argc > 1];
        isQName =
            !JSVAL_IS_PRIMITIVE(nameval) &&
            JSVAL_TO_OBJECT(nameval)->getClass() == &QNameClass;
    }

    /* QName called as function. */
    if (argc == 1 && isQName) {
        /* QName called with one QName argument is identity. */
        *rval = nameval;
        return JS_TRUE;
    }

        /* Create and return a new QName object exactly as if constructed. */
    RootedObject obj(cx, NewBuiltinClassInstanceXML(cx, &QNameClass));
    if (!obj)
        return JS_FALSE;

    if (isQName) {
        /* If namespace is not specified and name is a QName, clone it. */
        qn = JSVAL_TO_OBJECT(nameval);
        if (argc == 1) {
            uri = qn->getNameURI();
            prefix = qn->getNamePrefix();
            name = qn->getQNameLocalName();
            goto out;
        }

        /* Namespace and qname were passed -- use the qname's localName. */
        nameval = qn->getQNameLocalNameVal();
    }

    if (argc == 0) {
        name = cx->runtime->emptyString;
    } else if (argc < 0) {
        name = cx->names().undefined;
    } else {
        name = ToAtom(cx, nameval);
        if (!name)
            return false;
    }

    if (argc > 1 && !JSVAL_IS_VOID(argv[0])) {
        nsval = argv[0];
    } else if (IS_STAR(name)) {
        nsval = JSVAL_NULL;
    } else {
        if (!js_GetDefaultXMLNamespace(cx, &nsval))
            return JS_FALSE;
        JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
        JS_ASSERT(JSVAL_TO_OBJECT(nsval)->getClass() ==
                  &NamespaceClass);
    }

    if (JSVAL_IS_NULL(nsval)) {
        /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
        prefix = uri = NULL;
    } else {
        /*
         * Inline specialization of the Namespace constructor called with
         * nsval passed as the only argument, to compute the uri and prefix
         * for the constructed namespace, without actually allocating the
         * object or computing other members.  See ECMA-357 13.3.2 6(a) and
         * 13.2.2.
         */
        isNamespace = isQName = JS_FALSE;
        if (!JSVAL_IS_PRIMITIVE(nsval)) {
            obj2 = JSVAL_TO_OBJECT(nsval);
            isNamespace = (obj2->getClass() == &NamespaceClass);
            isQName = (obj2->getClass() == &QNameClass);
        }
#ifdef __GNUC__         /* suppress bogus gcc warnings */
        else obj2 = NULL;
#endif

        if (isNamespace) {
            uri = obj2->getNameURI();
            prefix = obj2->getNamePrefix();
        } else if (isQName && (uri = obj2->getNameURI())) {
            JS_ASSERT(argc > 1);
            prefix = obj2->getNamePrefix();
        } else {
            JS_ASSERT(argc > 1);
            JSString *str = ToString(cx, nsval);
            if (!str)
                return JS_FALSE;
            uri = str->ensureLinear(cx);
            if (!uri)
                return JS_FALSE;
            argv[0] = STRING_TO_JSVAL(uri);     /* local root */

            /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
            prefix = uri->empty() ? cx->runtime->emptyString : NULL;
        }
    }

out:
    *rval = OBJECT_TO_JSVAL(obj);
    return InitXMLQName(cx, obj, uri, prefix, name);
}

static JSBool
QName(JSContext *cx, unsigned argc, Value *vp)
{
    return QNameHelper(cx, argc, vp + 2, vp);
}

/*
 * XMLArray library functions.
 */
static JSBool
namespace_identity(const JSObject *nsa, const JSObject *nsb)
{
    JSLinearString *prefixa = nsa->getNamePrefix();
    JSLinearString *prefixb = nsb->getNamePrefix();

    if (prefixa && prefixb) {
        if (!EqualStrings(prefixa, prefixb))
            return JS_FALSE;
    } else {
        if (prefixa || prefixb)
            return JS_FALSE;
    }
    return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
}

static JSBool
attr_identity(const JSXML *xmla, const JSXML *xmlb)
{
    return qname_identity(xmla->name, xmlb->name);
}

void
js_XMLArrayCursorTrace(JSTracer *trc, JSXMLArrayCursor<JSXML> *cursor)
{
    for (; cursor; cursor = cursor->next) {
        if (cursor->root)
            MarkXML(trc, &(HeapPtr<JSXML> &)cursor->root, "cursor_root");
    }
}

void
js_XMLArrayCursorTrace(JSTracer *trc, JSXMLArrayCursor<JSObject> *cursor)
{
    for (; cursor; cursor = cursor->next) {
        if (cursor->root)
            MarkObject(trc, &(HeapPtr<JSObject> &)cursor->root, "cursor_root");
    }
}

template<class T>
static HeapPtr<T> *
ReallocateVector(HeapPtr<T> *vector, size_t count)
{
#if JS_BITS_PER_WORD == 32
    if (count > ~(size_t)0 / sizeof(HeapPtr<T>))
        return NULL;
#endif

    size_t size = count * sizeof(HeapPtr<T>);
    return (HeapPtr<T> *) js_realloc(vector, size);
}

/* NB: called with null cx from the GC, via xml_trace => JSXMLArray::trim. */
template<class T>
bool
JSXMLArray<T>::setCapacity(JSContext *cx, uint32_t newCapacity)
{
    if (newCapacity == 0) {
        /* We could let realloc(p, 0) free this, but purify gets confused. */
        if (vector) {
            if (cx)
                js_free(vector);
            else
                js_free(vector);
        }
        vector = NULL;
    } else {
        HeapPtr<T> *tmp = ReallocateVector(vector, newCapacity);
        if (!tmp) {
            if (cx)
                JS_ReportOutOfMemory(cx);
            return false;
        }
        vector = tmp;
    }
    capacity = JSXML_PRESET_CAPACITY | newCapacity;
    return true;
}

template<class T>
void
JSXMLArray<T>::trim()
{
    if (capacity & JSXML_PRESET_CAPACITY)
        return;
    if (length < capacity)
        setCapacity(NULL, length);
}

template<class T>
void
JSXMLArray<T>::finish(FreeOp *fop)
{
    if (!fop->runtime()->isHeapBusy()) {
        /* We need to clear these to trigger a write barrier. */
        for (uint32_t i = 0; i < length; i++)
            vector[i].~HeapPtr<T>();
    }

    fop->free_(vector);

    while (JSXMLArrayCursor<T> *cursor = cursors)
        cursor->disconnect();

#ifdef DEBUG
    memset(this, 0xd5, sizeof *this);
#endif
}

#define XML_NOT_FOUND   UINT32_MAX

template<class T, class U>
static uint32_t
XMLArrayFindMember(const JSXMLArray<T> *array, U *elt, typename IdentityOp<T, U>::compare identity)
{
    HeapPtr<T> *vector;
    uint32_t i, n;

    /* The identity op must not reallocate array->vector. */
    vector = array->vector;
    for (i = 0, n = array->length; i < n; i++) {
        if (identity(vector[i].get(), elt))
            return i;
    }
    return XML_NOT_FOUND;
}

/*
 * Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
 * that, grow by LINEAR_INCREMENT.  Both must be powers of two, and threshold
 * should be greater than increment.
 */
#define LINEAR_THRESHOLD        256
#define LINEAR_INCREMENT        32

template<class T>
static JSBool
XMLArrayAddMember(JSContext *cx, JSXMLArray<T> *array, uint32_t index, T *elt)
{
    uint32_t capacity, i;
    int log2;
    HeapPtr<T> *vector;

    if (index >= array->length) {
        if (index >= JSXML_CAPACITY(array)) {
            /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
            capacity = index + 1;
            if (index >= LINEAR_THRESHOLD) {
                capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
            } else {
                JS_CEILING_LOG2(log2, capacity);
                capacity = JS_BIT(log2);
            }
            if (!(vector = ReallocateVector(array->vector, capacity))) {
                JS_ReportOutOfMemory(cx);
                return JS_FALSE;
            }
            array->capacity = capacity;
            array->vector = vector;
            for (i = array->length; i < index; i++)
                vector[i].init(NULL);
        }
        array->vector[index].init(NULL);
        array->length = index + 1;
    }

    array->vector[index] = elt;
    return JS_TRUE;
}

template<class T>
static JSBool
XMLArrayInsert(JSContext *cx, JSXMLArray<T> *array, uint32_t i, uint32_t n)
{
    uint32_t j, k;
    JSXMLArrayCursor<T> *cursor;

    j = array->length;
    JS_ASSERT(i <= j);
    if (!array->setCapacity(cx, j + n))
        return JS_FALSE;

    k = j;
    while (k != j + n) {
        array->vector[k].init(NULL);
        k++;
    }

    array->length = j + n;
    JS_ASSERT(n != (uint32_t)-1);
    while (j != i) {
        --j;
        array->vector[j + n] = array->vector[j];
    }

    for (cursor = array->cursors; cursor; cursor = cursor->next) {
        if (cursor->index > i)
            cursor->index += n;
    }
    return JS_TRUE;
}

template<class T>
static T *
XMLArrayDelete(JSContext *cx, JSXMLArray<T> *array, uint32_t index, JSBool compress)
{
    uint32_t length;
    HeapPtr<T> *vector;
    T *elt;
    JSXMLArrayCursor<T> *cursor;

    length = array->length;
    if (index >= length)
        return NULL;

    vector = array->vector;
    elt = vector[index];
    if (compress) {
        vector[length - 1].~HeapPtr<T>();
        while (++index < length)
            vector[index-1] = vector[index];
        array->length = length - 1;
        array->capacity = JSXML_CAPACITY(array);
    } else {
        vector[index] = NULL;
    }

    for (cursor = array->cursors; cursor; cursor = cursor->next) {
        if (cursor->index > index)
            --cursor->index;
    }
    return elt;
}

template<class T>
static void
XMLArrayTruncate(JSContext *cx, JSXMLArray<T> *array, uint32_t length)
{
    HeapPtr<T> *vector;

    JS_ASSERT(!array->cursors);
    if (length >= array->length)
        return;

    for (uint32_t i = length; i < array->length; i++)
        array->vector[i].~HeapPtr<T>();

    if (length == 0) {
        if (array->vector)
            js_free(array->vector);
        vector = NULL;
    } else {
        vector = ReallocateVector(array->vector, length);
        if (!vector)
            return;
    }

    if (array->length > length)
        array->length = length;
    array->capacity = length;
    array->vector = vector;
}

#define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, e, f)
#define XMLARRAY_HAS_MEMBER(a,e,f)  (XMLArrayFindMember(a, e, f) !=           \
                                     XML_NOT_FOUND)
#define XMLARRAY_MEMBER(a,i,t)      (((i) < (a)->length)                      \
                                     ? (a)->vector[i].get()                   \
                                     : NULL)
#define XMLARRAY_SET_MEMBER(a,i,e)  JS_BEGIN_MACRO                            \
                                        if ((a)->length <= (i)) {             \
                                            (a)->length = (i) + 1;            \
                                            ((a)->vector[i].init(e));         \
                                        } else {                              \
                                            ((a)->vector[i] = e);             \
                                        }                                     \
                                    JS_END_MACRO
#define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, e)
#define XMLARRAY_INSERT(x,a,i,n)    XMLArrayInsert(x, a, i, n)
#define XMLARRAY_APPEND(x,a,e)      XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
#define XMLARRAY_DELETE(x,a,i,c,t)  (XMLArrayDelete<t>(x, a, i, c))
#define XMLARRAY_TRUNCATE(x,a,n)    XMLArrayTruncate(x, a, n)

/*
 * Define XML setting property strings and constants early, so everyone can
 * use the same names.
 */
static const char js_ignoreComments_str[]   = "ignoreComments";
static const char js_ignoreProcessingInstructions_str[]
                                            = "ignoreProcessingInstructions";
static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
static const char js_prettyPrinting_str[]   = "prettyPrinting";
static const char js_prettyIndent_str[]     = "prettyIndent";

#define XSF_IGNORE_COMMENTS                JS_BIT(0)
#define XSF_IGNORE_PROCESSING_INSTRUCTIONS JS_BIT(1)
#define XSF_IGNORE_WHITESPACE              JS_BIT(2)
#define XSF_PRETTY_PRINTING                JS_BIT(3)

static JSPropertySpec xml_static_props[] = {
    {js_ignoreComments_str, 0, JSPROP_PERMANENT, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER},
    {js_ignoreProcessingInstructions_str, 0, JSPROP_PERMANENT, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER},
    {js_ignoreWhitespace_str, 0, JSPROP_PERMANENT, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER},
    {js_prettyPrinting_str, 0, JSPROP_PERMANENT, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER},
    {js_prettyIndent_str, 0, JSPROP_PERMANENT, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER},
    {0,0,0,JSOP_NULLWRAPPER, JSOP_NULLWRAPPER}
};

/* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
#define IS_XML(str)                                                           \
    (str->length() == 3 && IS_XML_CHARS(str->chars()))

#define IS_XMLNS(str)                                                         \
    (str->length() == 5 && IS_XMLNS_CHARS(str->chars()))

static inline bool
IS_XML_CHARS(const jschar *chars)
{
    return (chars[0] == 'x' || chars[0] == 'X') &&
           (chars[1] == 'm' || chars[1] == 'M') &&
           (chars[2] == 'l' || chars[2] == 'L');
}

static inline bool
HAS_NS_AFTER_XML(const jschar *chars)
{
    return (chars[3] == 'n' || chars[3] == 'N') &&
           (chars[4] == 's' || chars[4] == 'S');
}

#define IS_XMLNS_CHARS(chars)                                                 \
    (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))

#define STARTS_WITH_XML(chars,length)                                         \
    (length >= 3 && IS_XML_CHARS(chars))

static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";

void
JSXML::finalize(FreeOp *fop)
{
    if (JSXML_HAS_KIDS(this)) {
        xml_kids.finish(fop);
        if (xml_class == JSXML_CLASS_ELEMENT) {
            xml_namespaces.finish(fop);
            xml_attrs.finish(fop);
        }
    }
#ifdef DEBUG_notme
    JS_REMOVE_LINK(&links);
#endif
}

static JSObject *
ParseNodeToQName(Parser *parser, ParseNode *pn,
                 JSXMLArray<JSObject> *inScopeNSes, JSBool isAttributeName)
{
    JSContext *cx = parser->context;
    JSLinearString *uri, *prefix;
    size_t length, offset;
    const jschar *start, *limit, *colon;
    uint32_t n;
    JSObject *ns;
    JSLinearString *nsprefix;

    JS_ASSERT(pn->isArity(PN_NULLARY));
    JSAtom *atom = pn->pn_atom;
    JSStableString *str = atom->ensureStable(cx);
    if (!str)
        return NULL;
    start = str->chars().get();
    length = str->length();
    JS_ASSERT(length != 0 && *start != '@');
    JS_ASSERT(length != 1 || *start != '*');

    JSAtom *localName;

    uri = cx->runtime->emptyString;
    limit = start + length;
    colon = js_strchr_limit(start, ':', limit);
    if (colon) {
        offset = colon - start;
        prefix = js_NewDependentString(cx, str, 0, offset);
        if (!prefix)
            return NULL;

        if (STARTS_WITH_XML(start, offset)) {
            if (offset == 3) {
                uri = JS_ASSERT_STRING_IS_FLAT(JS_InternString(cx, xml_namespace_str));
                if (!uri)
                    return NULL;
            } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
                uri = JS_ASSERT_STRING_IS_FLAT(JS_InternString(cx, xmlns_namespace_str));
                if (!uri)
                    return NULL;
            } else {
                uri = NULL;
            }
        } else {
            uri = NULL;
            n = inScopeNSes->length;
            while (n != 0) {
                --n;
                ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
                nsprefix = ns->getNamePrefix();
                if (nsprefix && EqualStrings(nsprefix, prefix)) {
                    uri = ns->getNameURI();
                    break;
                }
            }
        }

        if (!uri) {
            Value v = StringValue(prefix);
            JSAutoByteString bytes;
            if (js_ValueToPrintable(parser->context, v, &bytes))
                parser->reportError(pn, JSMSG_BAD_XML_NAMESPACE, bytes.ptr());
            return NULL;
        }

        localName = AtomizeChars(parser->context, colon + 1, length - (offset + 1));
        if (!localName)
            return NULL;
    } else {
        if (isAttributeName) {
            /*
             * An unprefixed attribute is not in any namespace, so set prefix
             * as well as uri to the empty string.
             */
            prefix = uri;
        } else {
            /*
             * Loop from back to front looking for the closest declared default
             * namespace.
             */
            n = inScopeNSes->length;
            while (n != 0) {
                --n;
                ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
                nsprefix = ns->getNamePrefix();
                if (!nsprefix || nsprefix->empty()) {
                    uri = ns->getNameURI();
                    break;
                }
            }
            prefix = uri->empty() ? parser->context->runtime->emptyString : NULL;
        }
        localName = atom;
    }

    return NewXMLQName(parser->context, uri, prefix, localName);
}

static JSString *
ChompXMLWhitespace(JSContext *cx, JSString *str)
{
    size_t length, newlength, offset;
    const jschar *cp, *start, *end;
    jschar c;

    length = str->length();
    start = str->getChars(cx);
    if (!start)
        return NULL;

    for (cp = start, end = cp + length; cp < end; cp++) {
        c = *cp;
        if (!unicode::IsXMLSpace(c))
            break;
    }
    while (end > cp) {
        c = end[-1];
        if (!unicode::IsXMLSpace(c))
            break;
        --end;
    }
    newlength = end - cp;
    if (newlength == length)
        return str;
    offset = cp - start;
    return js_NewDependentString(cx, str, offset, newlength);
}

static JSXML *
ParseNodeToXML(Parser *parser, ParseNode *pn,
               JSXMLArray<JSObject> *inScopeNSes, unsigned flags)
{
    JSContext *cx = parser->context;
    JSXML *xml, *kid, *attr, *attrj;
    JSLinearString *str;
    uint32_t length, n, i, j;
    ParseNode *pn2, *pn3, *head, **pnp;
    JSObject *ns;
    JSObject *qn, *attrjqn;
    JSXMLClass xml_class;
    int stackDummy;

    if (!JS_CHECK_STACK_SIZE(cx->runtime->nativeStackLimit, &stackDummy)) {
        parser->reportError(pn, JSMSG_OVER_RECURSED);
        return NULL;
    }

#define PN2X_SKIP_CHILD ((JSXML *) 1)

    /*
     * Cases return early to avoid common code that gets an outermost xml's
     * object, which protects GC-things owned by xml and its descendants from
     * garbage collection.
     */
    xml = NULL;
    switch (pn->getKind()) {
      case PNK_XMLELEM:
        length = inScopeNSes->length;
        pn2 = pn->pn_head;
        xml = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
        if (!xml)
            goto fail;

        n = pn->pn_count;
        JS_ASSERT(n >= 2);
        n -= 2;
        if (!xml->xml_kids.setCapacity(cx, n))
            goto fail;

        i = 0;
        while ((pn2 = pn2->pn_next) != NULL) {
            if (!pn2->pn_next) {
                /* Don't append the end tag! */
                JS_ASSERT(pn2->isKind(PNK_XMLETAGO));
                break;
            }

            if ((flags & XSF_IGNORE_WHITESPACE) &&
                n > 1 && pn2->isKind(PNK_XMLSPACE)) {
                --n;
                continue;
            }

            kid = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
            if (kid == PN2X_SKIP_CHILD) {
                --n;
                continue;
            }

            if (!kid)
                goto fail;

            /* Store kid in xml right away, to protect it from GC. */
            XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
            kid->parent = xml;
            ++i;

            /* XXX where is this documented in an XML spec, or in E4X? */
            if ((flags & XSF_IGNORE_WHITESPACE) &&
                n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
                JSString *str = ChompXMLWhitespace(cx, kid->xml_value);
                if (!str)
                    goto fail;
                kid->xml_value = str;
            }
        }

        JS_ASSERT(i == n);
        if (n < pn->pn_count - 2)
            xml->xml_kids.trim();
        XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
        break;

      case PNK_XMLLIST:
        xml = js_NewXML(cx, JSXML_CLASS_LIST);
        if (!xml)
            goto fail;

        n = pn->pn_count;
        if (!xml->xml_kids.setCapacity(cx, n))
            goto fail;

        i = 0;
        for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
            /*
             * Always ignore insignificant whitespace in lists -- we shouldn't
             * condition this on an XML.ignoreWhitespace setting when the list
             * constructor is XMLList (note XML/XMLList unification hazard).
             */
            if (pn2->isKind(PNK_XMLSPACE)) {
                --n;
                continue;
            }

            kid = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
            if (kid == PN2X_SKIP_CHILD) {
                --n;
                continue;
            }

            if (!kid)
                goto fail;

            XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
            ++i;
        }

        if (n < pn->pn_count)
            xml->xml_kids.trim();
        break;

      case PNK_XMLSTAGO:
      case PNK_XMLPTAGC:
        length = inScopeNSes->length;
        pn2 = pn->pn_head;
        JS_ASSERT(pn2->isKind(PNK_XMLNAME));
        if (pn2->isArity(PN_LIST))
            goto syntax;

        xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
        if (!xml)
            goto fail;

        /* First pass: check syntax and process namespace declarations. */
        JS_ASSERT(pn->pn_count >= 1);
        n = pn->pn_count - 1;
        pnp = &pn2->pn_next;
        head = *pnp;
        while ((pn2 = *pnp) != NULL) {
            size_t length;
            const jschar *chars;

            if (!pn2->isKind(PNK_XMLNAME) || !pn2->isArity(PN_NULLARY))
                goto syntax;

            /* Enforce "Well-formedness constraint: Unique Att Spec". */
            for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
                if (pn3->pn_atom == pn2->pn_atom) {
                    Value v = StringValue(pn2->pn_atom);
                    JSAutoByteString bytes;
                    if (js_ValueToPrintable(cx, v, &bytes))
                        parser->reportError(pn2, JSMSG_DUPLICATE_XML_ATTR, bytes.ptr());
                    goto fail;
                }
            }

            JSAtom *atom = pn2->pn_atom;
            pn2 = pn2->pn_next;
            JS_ASSERT(pn2);
            if (!pn2->isKind(PNK_XMLATTR))
                goto syntax;

            chars = atom->chars();
            length = atom->length();
            if (length >= 5 &&
                IS_XMLNS_CHARS(chars) &&
                (length == 5 || chars[5] == ':')) {
                JSLinearString *uri, *prefix;

                uri = pn2->pn_atom;
                if (length == 5) {
                    /* 10.3.2.1. Step 6(h)(i)(1)(a). */
                    prefix = cx->runtime->emptyString;
                } else {
                    prefix = js_NewStringCopyN(cx, chars + 6, length - 6);
                    if (!prefix)
                        goto fail;
                }

                /*
                 * Once the new ns is appended to xml->xml_namespaces, it is
                 * protected from GC by the object that owns xml -- which is
                 * either xml->object if outermost, or the object owning xml's
                 * oldest ancestor if !outermost.
                 */
                ns = NewXMLNamespace(cx, prefix, uri, JS_TRUE);
                if (!ns)
                    goto fail;

                /*
                 * Don't add a namespace that's already in scope.  If someone
                 * extracts a child property from its parent via [[Get]], then
                 * we enforce the invariant, noted many times in ECMA-357, that
                 * the child's namespaces form a possibly-improper superset of
                 * its ancestors' namespaces.
                 */
                if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
                    if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
                        !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
                        goto fail;
                    }
                }

                JS_ASSERT(n >= 2);
                n -= 2;
                *pnp = pn2->pn_next;
                /* XXXbe recycle pn2 */
                continue;
            }

            pnp = &pn2->pn_next;
        }

        xml->xml_namespaces.trim();

        /* Second pass: process tag name and attributes, using namespaces. */
        pn2 = pn->pn_head;
        qn = ParseNodeToQName(parser, pn2, inScopeNSes, JS_FALSE);
        if (!qn)
            goto fail;
        xml->name = qn;

        JS_ASSERT((n & 1) == 0);
        n >>= 1;
        if (!xml->xml_attrs.setCapacity(cx, n))
            goto fail;

        for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
            qn = ParseNodeToQName(parser, pn2, inScopeNSes, JS_TRUE);
            if (!qn) {
                xml->xml_attrs.length = i;
                goto fail;
            }

            /*
             * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
             * this time checking local name and namespace URI.
             */
            for (j = 0; j < i; j++) {
                attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
                attrjqn = attrj->name;
                if (EqualStrings(attrjqn->getNameURI(), qn->getNameURI()) &&
                    EqualStrings(attrjqn->getQNameLocalName(), qn->getQNameLocalName())) {
                    Value v = StringValue(pn2->pn_atom);
                    JSAutoByteString bytes;
                    if (js_ValueToPrintable(cx, v, &bytes))
                        parser->reportError(pn2, JSMSG_DUPLICATE_XML_ATTR, bytes.ptr());
                    goto fail;
                }
            }

            pn2 = pn2->pn_next;
            JS_ASSERT(pn2);
            JS_ASSERT(pn2->isKind(PNK_XMLATTR));

            attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
            if (!attr)
                goto fail;

            XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
            attr->parent = xml;
            attr->name = qn;
            attr->xml_value = pn2->pn_atom;
        }

        /* Point tag closes its own namespace scope. */
        if (pn->isKind(PNK_XMLPTAGC))
            XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
        break;

      case PNK_XMLSPACE:
      case PNK_XMLTEXT:
      case PNK_XMLCDATA:
      case PNK_XMLCOMMENT:
      case PNK_XMLPI:
        str = pn->pn_atom;
        qn = NULL;
        if (pn->isKind(PNK_XMLCOMMENT)) {
            if (flags & XSF_IGNORE_COMMENTS)
                goto skip_child;
            xml_class = JSXML_CLASS_COMMENT;
        } else if (pn->isKind(PNK_XMLPI)) {
            XMLProcessingInstruction &pi = pn->as<XMLProcessingInstruction>();
            if (IS_XML(str)) {
                Value v = StringValue(str);
                JSAutoByteString bytes;
                if (js_ValueToPrintable(cx, v, &bytes))
                    parser->reportError(&pi, JSMSG_RESERVED_ID, bytes.ptr());
                goto fail;
            }

            if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
                goto skip_child;

            qn = ParseNodeToQName(parser, &pi, inScopeNSes, JS_FALSE);
            if (!qn)
                goto fail;

            str = pi.data();
            xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
        } else {
            /* CDATA section content, or element text. */
            xml_class = JSXML_CLASS_TEXT;
        }

        xml = js_NewXML(cx, xml_class);
        if (!xml)
            goto fail;
        xml->name = qn;
        if (pn->isKind(PNK_XMLSPACE))
            xml->xml_flags |= XMLF_WHITESPACE_TEXT;
        xml->xml_value = str;
        break;

      default:
        goto syntax;
    }

    return xml;

skip_child:
    return PN2X_SKIP_CHILD;

#undef PN2X_SKIP_CHILD

syntax:
    parser->reportError(pn, JSMSG_BAD_XML_MARKUP);
fail:
    return NULL;
}

/*
 * XML helper, object-ops, and library functions.  We start with the helpers,
 * in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
 */
static JSBool
GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
{
    RootedValue v(cx);
    if (!js_FindClassObject(cx, JSProto_XML, &v))
        return JS_FALSE;
    if (v.get().isPrimitive() || !v.get().toObject().isFunction()) {
        *vp = JSVAL_VOID;
        return JS_TRUE;
    }
    RootedObject obj(cx, &v.get().toObject());
    return JS_GetProperty(cx, obj, name, vp);
}

static JSBool
GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
{
    jsval v;

    return GetXMLSetting(cx, name, &v) && JS_ValueToBoolean(cx, v, bp);
}

static JSBool
GetUint32XMLSetting(JSContext *cx, const char *name, uint32_t *uip)
{
    jsval v;

    return GetXMLSetting(cx, name, &v) && JS_ValueToECMAUint32(cx, v, uip);
}

static JSBool
GetXMLSettingFlags(JSContext *cx, unsigned *flagsp)
{
    JSBool flag[4];

    if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag[0]) ||
        !GetBooleanXMLSetting(cx, js_ignoreProcessingInstructions_str, &flag[1]) ||
        !GetBooleanXMLSetting(cx, js_ignoreWhitespace_str, &flag[2]) ||
        !GetBooleanXMLSetting(cx, js_prettyPrinting_str, &flag[3])) {
        return false;
    }

    *flagsp = 0;
    for (size_t n = 0; n < 4; ++n)
        if (flag[n])
            *flagsp |= JS_BIT(n);
    return true;
}

static JSObject *
GetCurrentScopeChain(JSContext *cx)
{
    if (cx->hasfp() && cx->fp()->scopeChain()->compartment() == cx->compartment)
        return cx->fp()->scopeChain();
    return cx->global();
}

static JSXML *
ParseXMLSource(JSContext *cx, HandleString src)
{
    jsval nsval;
    JSLinearString *uri;
    size_t urilen, srclen, length, offset, dstlen;
    jschar *chars;
    const jschar *srcp, *endp;
    JSXML *xml;
    const char *filename;
    unsigned lineno;
    JSOp op;

    static const char prefix[] = "<parent xmlns=\"";
    static const char middle[] = "\">";
    static const char suffix[] = "</parent>";

#define constrlen(constr)   (sizeof(constr) - 1)

    if (!js_GetDefaultXMLNamespace(cx, &nsval))
        return NULL;
    uri = JSVAL_TO_OBJECT(nsval)->getNameURI();
    uri = js_EscapeAttributeValue(cx, uri, JS_FALSE);
    if (!uri)
        return NULL;

    urilen = uri->length();
    srclen = src->length();
    length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
             constrlen(suffix);

    chars = cx->pod_malloc<jschar>(length + 1);
    if (!chars)
        return NULL;

    dstlen = length;
    InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
    offset = dstlen;
    js_strncpy(chars + offset, uri->chars(), urilen);
    offset += urilen;
    dstlen = length - offset + 1;
    InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset, &dstlen);
    offset += dstlen;
    srcp = src->getChars(cx);
    if (!srcp) {
        js_free(chars);
        return NULL;
    }
    js_strncpy(chars + offset, srcp, srclen);
    offset += srclen;
    dstlen = length - offset + 1;
    InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset, &dstlen);
    chars [offset + dstlen] = 0;

    xml = NULL;
    filename = NULL;
    lineno = 1;
    ScriptFrameIter i(cx);
    if (!i.done()) {
        op = (JSOp) *i.pc();
        if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
            filename = i.script()->filename;
            RootedScript script(cx, i.script());
            lineno = PCToLineNumber(script, i.pc());
            for (endp = srcp + srclen; srcp < endp; srcp++) {
                if (*srcp == '\n')
                    --lineno;
            }
        }
    }

    {
        CompileOptions options(cx);
        options.setFileAndLine(filename, lineno);
        Parser parser(cx, options, chars, length, /* foldConstants = */ true);
        if (parser.init()) {
            JSObject *scopeChain = GetCurrentScopeChain(cx);
            if (!scopeChain) {
                js_free(chars);
                return NULL;
            }

            ParseNode *pn = parser.parseXMLText(scopeChain, false);
            unsigned flags;
            if (pn && GetXMLSettingFlags(cx, &flags)) {
                AutoNamespaceArray namespaces(cx);
                if (namespaces.array.setCapacity(cx, 1))
                    xml = ParseNodeToXML(&parser, pn, &namespaces.array, flags);
            }
        }
    }

    js_free(chars);
    return xml;

#undef constrlen
}

/*
 * Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
 *
 * 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
 * the constraint:
 *
 *     for all x belonging to XML:
 *         x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
 *
 * must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
 * (in new sub-step 6(a), renumbering the others to (b) and (c)).
 *
 * Same goes for 10.4.1 Step 7(a).
 *
 * In order for XML.prototype.namespaceDeclarations() to work correctly, the
 * default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
 * flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
 * undeclared namespaces associated with x not belonging to ancestorNS.
 */
static JSXML *
OrphanXMLChild(JSContext *cx, JSXML *xml, uint32_t i)
{
    JSObject *ns;

    ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSObject);
    xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
    if (!ns || !xml)
        return xml;
    if (xml->xml_class == JSXML_CLASS_ELEMENT) {
        if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
            return NULL;
        ns->setNamespaceDeclared(JSVAL_VOID);
    }
    xml->parent = NULL;
    return xml;
}

static JSObject *
ToXML(JSContext *cx, jsval v)
{
    JSObject *obj;
    JSXML *xml;
    Class *clasp;
    RootedString str(cx);
    uint32_t length;

    if (JSVAL_IS_PRIMITIVE(v)) {
        if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
            goto bad;
    } else {
        obj = JSVAL_TO_OBJECT(v);
        if (obj->isXML()) {
            xml = (JSXML *) obj->getPrivate();
            if (xml->xml_class == JSXML_CLASS_LIST) {
                if (xml->xml_kids.length != 1)
                    goto bad;
                xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
                if (xml) {
                    JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
                    return js_GetXMLObject(cx, xml);
                }
            }
            return obj;
        }

        clasp = obj->getClass();
        if (clasp != &StringClass &&
            clasp != &NumberClass &&
            clasp != &BooleanClass) {
            goto bad;
        }
    }

    str = ToString(cx, v);
    if (!str)
        return NULL;
    if (str->empty()) {
        length = 0;
#ifdef __GNUC__         /* suppress bogus gcc warnings */
        xml = NULL;
#endif
    } else {
        xml = ParseXMLSource(cx, str);
        if (!xml)
            return NULL;
        length = JSXML_LENGTH(xml);
    }

    if (length == 0) {
        obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
        if (!obj)
            return NULL;
    } else if (length == 1) {
        xml = OrphanXMLChild(cx, xml, 0);
        if (!xml)
            return NULL;
        obj = js_GetXMLObject(cx, xml);
        if (!obj)
            return NULL;
    } else {
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
        return NULL;
    }
    return obj;

bad:
    RootedValue val(cx, v);
    js_ReportValueError(cx, JSMSG_BAD_XML_CONVERSION,
                        JSDVG_IGNORE_STACK, val, NullPtr());
    return NULL;
}

static JSBool
Append(JSContext *cx, JSXML *list, JSXML *kid);

static JSObject *
ToXMLList(JSContext *cx, jsval v)
{
    JSObject *obj, *listobj;
    JSXML *xml, *list, *kid;
    Class *clasp;
    RootedString str(cx);
    uint32_t i, length;

    if (JSVAL_IS_PRIMITIVE(v)) {
        if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
            goto bad;
    } else {
        obj = JSVAL_TO_OBJECT(v);
        if (obj->isXML()) {
            xml = (JSXML *) obj->getPrivate();
            if (xml->xml_class != JSXML_CLASS_LIST) {
                listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
                if (!listobj)
                    return NULL;
                list = (JSXML *) listobj->getPrivate();
                if (!Append(cx, list, xml))
                    return NULL;
                return listobj;
            }
            return obj;
        }

        clasp = obj->getClass();
        if (clasp != &StringClass &&
            clasp != &NumberClass &&
            clasp != &BooleanClass) {
            goto bad;
        }
    }

    str = ToString(cx, v);
    if (!str)
        return NULL;
    if (str->empty()) {
        xml = NULL;
        length = 0;
    } else {
        xml = ParseXMLSource(cx, str);
        if (!xml)
            return NULL;
        length = JSXML_LENGTH(xml);
    }

    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
    if (listobj) {
        list = (JSXML *) listobj->getPrivate();
        for (i = 0; i < length; i++) {
            kid = OrphanXMLChild(cx, xml, i);
            if (!kid || !Append(cx, list, kid)) {
                listobj = NULL;
                break;
            }
        }
    }

    return listobj;

bad:
    RootedValue val(cx, v);
    js_ReportValueError(cx, JSMSG_BAD_XMLLIST_CONVERSION,
                        JSDVG_IGNORE_STACK, val, NullPtr());
    return NULL;
}

/*
 * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
 * and their library-public js_* counterparts.  The guts of MakeXMLCDataString,
 * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
 * MakeXMLSpecialString subroutine.
 *
 * These functions mutate sb, leaving it empty.
 */
static JSFlatString *
MakeXMLSpecialString(JSContext *cx, StringBuffer &sb,
                     JSString *str, JSString *str2,
                     const jschar *prefix, size_t prefixlength,
                     const jschar *suffix, size_t suffixlength)
{
    if (!sb.append(prefix, prefixlength) || !sb.append(str))
        return NULL;
    if (str2 && !str2->empty()) {
        if (!sb.append(' ') || !sb.append(str2))
            return NULL;
    }
    if (!sb.append(suffix, suffixlength))
        return NULL;

    return sb.finishString();
}

static JSFlatString *
MakeXMLCDATAString(JSContext *cx, StringBuffer &sb, JSString *str)
{
    static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
                                                 'C', 'D', 'A', 'T', 'A',
                                                 '['};
    static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};

    return MakeXMLSpecialString(cx, sb, str, NULL,
                                cdata_prefix_ucNstr, 9,
                                cdata_suffix_ucNstr, 3);
}

static JSFlatString *
MakeXMLCommentString(JSContext *cx, StringBuffer &sb, JSString *str)
{
    static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
    static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};

    return MakeXMLSpecialString(cx, sb, str, NULL,
                                comment_prefix_ucNstr, 4,
                                comment_suffix_ucNstr, 3);
}

static JSFlatString *
MakeXMLPIString(JSContext *cx, StringBuffer &sb, JSString *name,
                JSString *value)
{
    static const jschar pi_prefix_ucNstr[] = {'<', '?'};
    static const jschar pi_suffix_ucNstr[] = {'?', '>'};

    return MakeXMLSpecialString(cx, sb, name, value,
                                pi_prefix_ucNstr, 2,
                                pi_suffix_ucNstr, 2);
}

/*
 * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
 *
 * This function appends the output into the supplied string buffer.
 */
static bool
EscapeAttributeValueBuffer(JSContext *cx, StringBuffer &sb, JSString *str, JSBool quote)
{
    size_t length = str->length();
    const jschar *start = str->getChars(cx);
    if (!start)
        return false;

    if (quote && !sb.append('"'))
        return false;

    for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
        jschar c = *cp;
        switch (c) {
          case '"':
            if (!sb.append(js_quot_entity_str))
                return false;
            break;
          case '<':
            if (!sb.append(js_lt_entity_str))
                return false;
            break;
          case '&':
            if (!sb.append(js_amp_entity_str))
                return false;
            break;
          case '\n':
            if (!sb.append("&#xA;"))
                return false;
            break;
          case '\r':
            if (!sb.append("&#xD;"))
                return false;
            break;
          case '\t':
            if (!sb.append("&#x9;"))
                return false;
            break;
          default:
            if (!sb.append(c))
                return false;
        }
    }

    if (quote && !sb.append('"'))
        return false;

    return true;
}

/*
 * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
 *
 * This function mutates sb, leaving it empty.
 */
static JSFlatString *
EscapeAttributeValue(JSContext *cx, StringBuffer &sb, JSString *str, JSBool quote)
{
    if (!EscapeAttributeValueBuffer(cx, sb, str, quote))
        return NULL;
    return sb.finishString();
}

/*
 * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
 * equals, a double quote, an attribute value, and a closing double quote.
 */
static bool
AppendAttributeValue(JSContext *cx, StringBuffer &sb, JSString *valstr)
{
    if (!sb.append('='))
        return false;
    return EscapeAttributeValueBuffer(cx, sb, valstr, JS_TRUE);
}

/*
 * ECMA-357 10.2.1.1 EscapeElementValue helper method.

 * These functions mutate sb, leaving it empty.
 */
static JSFlatString *
EscapeElementValue(JSContext *cx, StringBuffer &sb, JSString *str, uint32_t toSourceFlag)
{
    size_t length = str->length();
    const jschar *start = str->getChars(cx);
    if (!start)
        return NULL;

    for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
        jschar c = *cp;
        switch (*cp) {
          case '<':
            if (!sb.append(js_lt_entity_str))
                return NULL;
            break;
          case '>':
            if (!sb.append(js_gt_entity_str))
                return NULL;
            break;
          case '&':
            if (!sb.append(js_amp_entity_str))
                return NULL;
            break;
          case '{':
            /*
             * If EscapeElementValue is called by toSource/uneval, we also need
             * to escape '{'. See bug 463360.
             */
            if (toSourceFlag) {
                if (!sb.append(js_leftcurly_entity_str))
                    return NULL;
                break;
            }
            /* FALL THROUGH */
          default:
            if (!sb.append(c))
                return NULL;
        }
    }
    return sb.finishString();
}

/* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
static JSObject *
GetNamespace(JSContext *cx, JSObject *qn, const JSXMLArray<JSObject> *inScopeNSes)
{
    JSLinearString *uri, *prefix, *nsprefix;
    JSObject *match, *ns;
    uint32_t i, n;
    jsval argv[2];

    uri = qn->getNameURI();
    prefix = qn->getNamePrefix();
    JS_ASSERT(uri);
    if (!uri) {
        JSAutoByteString bytes;
        const char *s = !prefix ?
                        js_undefined_str
                        : js_ValueToPrintable(cx, StringValue(prefix), &bytes);
        if (s)
            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XML_NAMESPACE, s);
        return NULL;
    }

    /* Look for a matching namespace in inScopeNSes, if provided. */
    match = NULL;
    if (inScopeNSes) {
        for (i = 0, n = inScopeNSes->length; i < n; i++) {
            ns = XMLARRAY_MEMBER(inScopeNSes, i, JSObject);
            if (!ns)
                continue;

            /*
             * Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
             * If we preserve prefixes, we must match null prefix against
             * an empty prefix of ns, in order to avoid generating redundant
             * prefixed and default namespaces for cases such as:
             *
             *   x = <t xmlns="http://foo.com"/>
             *   print(x.toXMLString());
             *
             * Per 10.3.2.1, the namespace attribute in t has an empty string
             * prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
             *
             *   1. If the [local name] property of a is "xmlns"
             *      a. Map ns.prefix to the empty string
             *
             * But t's name has a null prefix in this implementation, meaning
             * *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
             * the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
             * saying how "no value" maps to an ECMA-357 value -- but it must
             * map to the *undefined* prefix value).
             *
             * Since "" != undefined (or null, in the current implementation)
             * the ECMA-357 spec will fail to match in [[GetNamespace]] called
             * on t with argument {} U {(prefix="", uri="http://foo.com")}.
             * This spec bug leads to ToXMLString results that duplicate the
             * declared namespace.
             */
            if (EqualStrings(ns->getNameURI(), uri)) {
                nsprefix = ns->getNamePrefix();
                if (nsprefix == prefix ||
                    ((nsprefix && prefix)
                     ? EqualStrings(nsprefix, prefix)
                     : (nsprefix ? nsprefix : prefix)->empty())) {
                    match = ns;
                    break;
                }
            }
        }
    }

    /* If we didn't match, make a new namespace from qn. */
    if (!match) {
        argv[0] = prefix ? STRING_TO_JSVAL(prefix) : JSVAL_VOID;
        argv[1] = STRING_TO_JSVAL(uri);
        ns = ConstructObjectWithArguments(cx, &NamespaceClass, 2, argv);
        if (!ns)
            return NULL;
        match = ns;
    }
    return match;
}

static JSLinearString *
GeneratePrefix(JSContext *cx, JSLinearString *uri, JSXMLArray<JSObject> *decls)
{
    const jschar *cp, *start, *end;
    size_t length, newlength, offset;
    uint32_t i, n, m, serial;
    jschar *bp, *dp;
    JSBool done;
    JSObject *ns;
    JSLinearString *nsprefix, *prefix;

    JS_ASSERT(!uri->empty());

    /*
     * If there are no *declared* namespaces, skip all collision detection and
     * return a short prefix quickly; an example of such a situation:
     *
     *   var x = <f/>;
     *   var n = new Namespace("http://example.com/");
     *   x.@n::att = "val";
     *   x.toXMLString();
     *
     * This is necessary for various log10 uses below to be valid.
     */
    if (decls->length == 0)
        return js_NewStringCopyZ(cx, "a");

    /*
     * Try peeling off the last filename suffix or pathname component till
     * we have a valid XML name.  This heuristic will prefer "xul" given
     * ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
     * likely URI of the form ".../xbl2/2005".
     */
    start = uri->chars();
    end = start + uri->length();
    cp = end;
    while (--cp > start) {
        if (*cp == '.' || *cp == '/' || *cp == ':') {
            ++cp;
            length = end - cp;
            if (IsXMLName(cp, length) && !STARTS_WITH_XML(cp, length))
                break;
            end = --cp;
        }
    }
    length = end - cp;

    /*
     * If the namespace consisted only of non-XML names or names that begin
     * case-insensitively with "xml", arbitrarily create a prefix consisting
     * of 'a's of size length (allowing dp-calculating code to work with or
     * without this branch executing) plus the space for storing a hyphen and
     * the serial number (avoiding reallocation if a collision happens).
     */
    bp = (jschar *) cp;
    newlength = length;
    if (STARTS_WITH_XML(cp, length) || !IsXMLName(cp, length)) {
        newlength = length + 2 + (size_t) log10((double) decls->length);
        bp = cx->pod_malloc<jschar>(newlength + 1);
        if (!bp)
            return NULL;

        bp[newlength] = 0;
        for (i = 0; i < newlength; i++)
             bp[i] = 'a';
    }

    /*
     * Now search through decls looking for a collision.  If we collide with
     * an existing prefix, start tacking on a hyphen and a serial number.
     */
    serial = 0;
    do {
        done = JS_TRUE;
        for (i = 0, n = decls->length; i < n; i++) {
            ns = XMLARRAY_MEMBER(decls, i, JSObject);
            if (ns && (nsprefix = ns->getNamePrefix()) &&
                nsprefix->length() == newlength &&
                !memcmp(nsprefix->chars(), bp,
                        newlength * sizeof(jschar))) {
                if (bp == cp) {
                    newlength = length + 2 + (size_t) log10((double) n);
                    bp = cx->pod_malloc<jschar>(newlength + 1);
                    if (!bp)
                        return NULL;
                    js_strncpy(bp, cp, length);
                }

                ++serial;
                JS_ASSERT(serial <= n);
                dp = bp + length + 2 + (size_t) log10((double) serial);
                *dp = 0;
                for (m = serial; m != 0; m /= 10)
                    *--dp = (jschar)('0' + m % 10);
                *--dp = '-';
                JS_ASSERT(dp == bp + length);

                done = JS_FALSE;
                break;
            }
        }
    } while (!done);

    if (bp == cp) {
        offset = cp - start;
        prefix = js_NewDependentString(cx, uri, offset, length);
    } else {
        prefix = js_NewString(cx, bp, newlength);
        if (!prefix)
            js_free(bp);
    }
    return prefix;
}

static JSBool
namespace_match(const JSObject *nsa, const JSObject *nsb)
{
    JSLinearString *prefixa, *prefixb = nsb->getNamePrefix();

    if (prefixb) {
        prefixa = nsa->getNamePrefix();
        return prefixa && EqualStrings(prefixa, prefixb);
    }
    return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
}

/* ECMA-357 10.2.1 and 10.2.2 */
#define TO_SOURCE_FLAG 0x80000000

static JSString *
XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray<JSObject> *ancestorNSes,
               uint32_t indentLevel, JSBool pretty)
{
    JSBool indentKids;
    StringBuffer sb(cx);
    JSString *str;
    JSLinearString *prefix, *nsuri;
    uint32_t i, n, nextIndentLevel;
    JSObject *ns, *ns2;
    AutoNamespaceArray empty(cx), decls(cx), ancdecls(cx);

    if (pretty) {
        if (!sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
            return NULL;
    }

    str = NULL;

    switch (xml->xml_class) {
      case JSXML_CLASS_TEXT:
        /* Step 4. */
        if (pretty) {
            str = ChompXMLWhitespace(cx, xml->xml_value);
            if (!str)
                return NULL;
        } else {
            str = xml->xml_value;
        }
        return EscapeElementValue(cx, sb, str, indentLevel & TO_SOURCE_FLAG);

      case JSXML_CLASS_ATTRIBUTE:
        /* Step 5. */
        return EscapeAttributeValue(cx, sb, xml->xml_value,
                                    (indentLevel & TO_SOURCE_FLAG) != 0);

      case JSXML_CLASS_COMMENT:
        /* Step 6. */
        return MakeXMLCommentString(cx, sb, xml->xml_value);

      case JSXML_CLASS_PROCESSING_INSTRUCTION:
        /* Step 7. */
        return MakeXMLPIString(cx, sb, xml->name->getQNameLocalName(),
                               xml->xml_value);

      case JSXML_CLASS_LIST:
        /* ECMA-357 10.2.2. */
        {
            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
            i = 0;
            while (JSXML *kid = cursor.getNext()) {
                if (pretty && i != 0) {
                    if (!sb.append('\n'))
                        return NULL;
                }

                JSString *kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel, pretty);
                if (!kidstr || !sb.append(kidstr))
                    return NULL;
                ++i;
            }
        }

        if (sb.empty())
            return cx->runtime->emptyString;
        return sb.finishString();

      default:;
    }

    /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
    if (!ancestorNSes) {
        // Ensure a namespace with empty strings exists in the initial array,
        // otherwise every call to GetNamespace() when running toString() on
        // an XML object with no namespace defined will create a new Namespace
        // object on every call.
        JSObject *emptyns = NewXMLNamespace(cx, cx->runtime->emptyString, cx->runtime->emptyString, JS_FALSE);
        if (!emptyns || !XMLARRAY_APPEND(cx, &empty.array, emptyns))
            goto out;
        ancestorNSes = &empty.array;
    }

    /* Clone in-scope namespaces not in ancestorNSes into decls. */
    {
        JSXMLArrayCursor<JSObject> cursor(&xml->xml_namespaces);
        while ((ns = cursor.getNext()) != NULL) {
            if (!IsDeclared(ns))
                continue;
            if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
                /* NOTE: may want to exclude unused namespaces here. */
                ns2 = NewXMLNamespace(cx, ns->getNamePrefix(), ns->getNameURI(), JS_TRUE);
                if (!ns2 || !XMLARRAY_APPEND(cx, &decls.array, ns2))
                    goto out;
            }
        }
    }

    /*
     * Union ancestorNSes and decls into ancdecls.  Note that ancdecls does
     * not own its member references.  In the spec, ancdecls has no name, but
     * is always written out as (AncestorNamespaces U namespaceDeclarations).
     */

    if (!ancdecls.array.setCapacity(cx, ancestorNSes->length + decls.length()))
        goto out;
    for (i = 0, n = ancestorNSes->length; i < n; i++) {
        ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSObject);
        if (!ns2)
            continue;
        JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls.array, ns2, namespace_identity));
        if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2))
            goto out;
    }
    for (i = 0, n = decls.length(); i < n; i++) {
        ns2 = XMLARRAY_MEMBER(&decls.array, i, JSObject);
        if (!ns2)
            continue;
        JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls.array, ns2, namespace_identity));
        if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2))
            goto out;
    }

    /* Step 11, except we don't clone ns unless its prefix is undefined. */
    ns = GetNamespace(cx, xml->name, &ancdecls.array);
    if (!ns)
        goto out;

    /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
    prefix = ns->getNamePrefix();
    if (!prefix) {
        /*
         * Create a namespace prefix that isn't used by any member of decls.
         * Assign the new prefix to a copy of ns.  Flag this namespace as if
         * it were declared, for assertion-testing's sake later below.
         *
         * Erratum: if prefix and xml->name are both null (*undefined* in
         * ECMA-357), we know that xml was named using the default namespace
         * (proof: see GetNamespace and the Namespace constructor called with
         * two arguments).  So we ought not generate a new prefix here, when
         * we can declare ns as the default namespace for xml.
         *
         * This helps descendants inherit the namespace instead of redundantly
         * redeclaring it with generated prefixes in each descendant.
         */
        nsuri = ns->getNameURI();
        if (!xml->name->getNamePrefix()) {
            prefix = cx->runtime->emptyString;
        } else {
            prefix = GeneratePrefix(cx, nsuri, &ancdecls.array);
            if (!prefix)
                goto out;
        }
        ns = NewXMLNamespace(cx, prefix, nsuri, JS_TRUE);
        if (!ns)
            goto out;

        /*
         * If the xml->name was unprefixed, we must remove any declared default
         * namespace from decls before appending ns.  How can you get a default
         * namespace in decls that doesn't match the one from name?  Apparently
         * by calling x.setNamespace(ns) where ns has no prefix.  The other way
         * to fix this is to update x's in-scope namespaces when setNamespace
         * is called, but that's not specified by ECMA-357.
         *
         * Likely Erratum here, depending on whether the lack of update to x's
         * in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
         * erratum or not.  Note that changing setNamespace to update the list
         * of in-scope namespaces will change x.namespaceDeclarations().
         */
        if (prefix->empty()) {
            i = XMLArrayFindMember(&decls.array, ns, namespace_match);
            if (i != XML_NOT_FOUND)
                XMLArrayDelete(cx, &decls.array, i, JS_TRUE);
        }

        /*
         * In the spec, ancdecls has no name, but is always written out as
         * (AncestorNamespaces U namespaceDeclarations).  Since we compute
         * that union in ancdecls, any time we append a namespace strong
         * ref to decls, we must also append a weak ref to ancdecls.  Order
         * matters here: code at label out: releases strong refs in decls.
         */
        if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns) ||
            !XMLARRAY_APPEND(cx, &decls.array, ns)) {
            goto out;
        }
    }

    /* Format the element or point-tag into sb. */
    if (!sb.append('<'))
        goto out;

    if (!prefix->empty()) {
        if (!sb.append(prefix) || !sb.append(':'))
            goto out;
    }
    if (!sb.append(xml->name->getQNameLocalName()))
        goto out;

    /*
     * Step 16 makes a union to avoid writing two loops in step 17, to share
     * common attribute value appending spec-code.  We prefer two loops for
     * faster code and less data overhead.
     */

    /* Step 17(b): append attributes. */
    {
        JSXMLArrayCursor<JSXML> cursor(&xml->xml_attrs);
        while (JSXML *attr = cursor.getNext()) {
            if (!sb.append(' '))
                goto out;
            ns2 = GetNamespace(cx, attr->name, &ancdecls.array);
            if (!ns2)
                goto out;

            /* 17(b)(ii): NULL means *undefined* here. */
            prefix = ns2->getNamePrefix();
            if (!prefix) {
                prefix = GeneratePrefix(cx, ns2->getNameURI(), &ancdecls.array);
                if (!prefix)
                    goto out;

                /* Again, we avoid copying ns2 until we know it's prefix-less. */
                ns2 = NewXMLNamespace(cx, prefix, ns2->getNameURI(), JS_TRUE);
                if (!ns2)
                    goto out;

                /*
                 * In the spec, ancdecls has no name, but is always written out as
                 * (AncestorNamespaces U namespaceDeclarations).  Since we compute
                 * that union in ancdecls, any time we append a namespace strong
                 * ref to decls, we must also append a weak ref to ancdecls.  Order
                 * matters here: code at label out: releases strong refs in decls.
                 */
                if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2) ||
                    !XMLARRAY_APPEND(cx, &decls.array, ns2)) {
                    goto out;
                }
            }

            /* 17(b)(iii). */
            if (!prefix->empty()) {
                if (!sb.append(prefix) || !sb.append(':'))
                    goto out;
            }

            /* 17(b)(iv). */
            if (!sb.append(attr->name->getQNameLocalName()))
                goto out;

            /* 17(d-g). */
            if (!AppendAttributeValue(cx, sb, attr->xml_value))
                goto out;
        }
    }

    /* Step 17(c): append XML namespace declarations. */
    {
        JSXMLArrayCursor<JSObject> cursor(&decls.array);
        while (JSObject *ns3 = cursor.getNext()) {
            JS_ASSERT(IsDeclared(ns3));

            if (!sb.append(" xmlns"))
                goto out;

            /* 17(c)(ii): NULL means *undefined* here. */
            prefix = ns3->getNamePrefix();
            if (!prefix) {
                prefix = GeneratePrefix(cx, ns3->getNameURI(), &ancdecls.array);
                if (!prefix)
                    goto out;
                ns3->setNamePrefix(prefix);
            }

            /* 17(c)(iii). */
            if (!prefix->empty()) {
                if (!sb.append(':') || !sb.append(prefix))
                    goto out;
            }

            /* 17(d-g). */
            if (!AppendAttributeValue(cx, sb, ns3->getNameURI()))
                goto out;
        }
    }

    /* Step 18: handle point tags. */
    n = xml->xml_kids.length;
    if (n == 0) {
        if (!sb.append("/>"))
            goto out;
    } else {
        /* Steps 19 through 25: handle element content, and open the end-tag. */
        if (!sb.append('>'))
            goto out;
        {
            JSXML *kid;
            indentKids = n > 1 ||
                         (n == 1 &&
                          (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
                          kid->xml_class != JSXML_CLASS_TEXT);
        }

        if (pretty && indentKids) {
            if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
                goto out;
            nextIndentLevel = indentLevel + i;
        } else {
            nextIndentLevel = indentLevel & TO_SOURCE_FLAG;
        }

        {
            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
            while (JSXML *kid = cursor.getNext()) {
                if (pretty && indentKids) {
                    if (!sb.append('\n'))
                        goto out;
                }

                JSString *kidstr = XMLToXMLString(cx, kid, &ancdecls.array, nextIndentLevel, pretty);
                if (!kidstr)
                    goto out;

                if (!sb.append(kidstr))
                    goto out;
            }
        }

        if (pretty && indentKids) {
            if (!sb.append('\n') ||
                !sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
                goto out;
        }
        if (!sb.append("</"))
            goto out;

        /* Step 26. */
        prefix = ns->getNamePrefix();
        if (prefix && !prefix->empty()) {
            if (!sb.append(prefix) || !sb.append(':'))
                goto out;
        }

        /* Step 27. */
        if (!sb.append(xml->name->getQNameLocalName()) || !sb.append('>'))
            goto out;
    }

    str = sb.finishString();
out:
    return str;
}

/* ECMA-357 10.2 */
static JSString *
ToXMLString(JSContext *cx, jsval v, uint32_t toSourceFlag)
{
    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                             JSMSG_BAD_XML_CONVERSION,
                             JSVAL_IS_NULL(v) ? js_null_str : js_undefined_str);
        return NULL;
    }

    if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
        return ToString(cx, v);

    if (JSVAL_IS_STRING(v)) {
        StringBuffer sb(cx);
        return EscapeElementValue(cx, sb, JSVAL_TO_STRING(v), toSourceFlag);
    }

    JSObject *obj = JSVAL_TO_OBJECT(v);
    if (!obj->isXML()) {
        if (!ToPrimitive(cx, JSTYPE_STRING, &v))
            return NULL;
        JSString *str = ToString(cx, v);
        if (!str)
            return NULL;
        StringBuffer sb(cx);
        return EscapeElementValue(cx, sb, str, toSourceFlag);
    }

    JSBool pretty;
    if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
        return NULL;

    /* Handle non-element cases in this switch, returning from each case. */
    JS::Anchor<JSObject *> anch(obj);
    JSXML *xml = reinterpret_cast<JSXML *>(obj->getPrivate());
    return XMLToXMLString(cx, xml, NULL, toSourceFlag | 0, pretty);
}

static JSObject *
ToAttributeName(JSContext *cx, jsval v)
{
    JSLinearString *uri, *prefix;
    JSObject *obj;
    Class *clasp;
    JSObject *qn;

    JSAtom *name;
    if (JSVAL_IS_STRING(v)) {
        name = ToAtom(cx, v);
        if (!name)
            return NULL;
        uri = prefix = cx->runtime->emptyString;
    } else {
        if (JSVAL_IS_PRIMITIVE(v)) {
            RootedValue val(cx, v);
            js_ReportValueError(cx, JSMSG_BAD_XML_ATTR_NAME,
                                JSDVG_IGNORE_STACK, val, NullPtr());
            return NULL;
        }

        obj = JSVAL_TO_OBJECT(v);
        clasp = obj->getClass();
        if (clasp == &AttributeNameClass)
            return obj;

        if (clasp == &QNameClass) {
            qn = obj;
            uri = qn->getNameURI();
            prefix = qn->getNamePrefix();
            name = qn->getQNameLocalName();
        } else {
            if (clasp == &AnyNameClass) {
                name = cx->names().star;
            } else {
                name = ToAtom(cx, v);
                if (!name)
                    return NULL;
            }
            uri = prefix = cx->runtime->emptyString;
        }
    }

    qn = NewXMLAttributeName(cx, uri, prefix, name);
    if (!qn)
        return NULL;
    return qn;
}

static void
ReportBadXMLName(JSContext *cx, const Value &idval)
{
    RootedValue val(cx, idval);
    js_ReportValueError(cx, JSMSG_BAD_XML_NAME, JSDVG_IGNORE_STACK, val, NullPtr());
}

bool
js::GetLocalNameFromFunctionQName(JSObject *qn, JSAtom **namep, JSContext *cx)
{
    JSAtom *atom = cx->names().functionNamespaceURI;
    JSLinearString *uri = qn->getNameURI();
    if (uri && (uri == atom || EqualStrings(uri, atom))) {
        *namep = qn->getQNameLocalName();
        return true;
    }
    return false;
}

bool
js_GetLocalNameFromFunctionQName(JSObject *obj, jsid *funidp, JSContext *cx)
{
    if (!obj->isQName())
        return false;
    JSAtom *name;
    if (GetLocalNameFromFunctionQName(obj, &name, cx)) {
        *funidp = AtomToId(name);
        return true;
    }
    return false;
}

static JSObject *
ToXMLName(JSContext *cx, jsval v, jsid *funidp)
{
    JSAtom *atomizedName;
    JSString *name;
    JSObject *obj;
    Class *clasp;
    uint32_t index;

    if (JSVAL_IS_STRING(v)) {
        name = JSVAL_TO_STRING(v);
    } else {
        if (JSVAL_IS_PRIMITIVE(v)) {
            ReportBadXMLName(cx, v);
            return NULL;
        }

        obj = JSVAL_TO_OBJECT(v);
        clasp = obj->getClass();
        if (clasp == &AttributeNameClass || clasp == &QNameClass)
            goto out;
        if (clasp == &AnyNameClass) {
            name = cx->names().star;
            goto construct;
        }
        name = ToStringSlow(cx, v);
        if (!name)
            return NULL;
    }

    atomizedName = AtomizeString(cx, name);
    if (!atomizedName)
        return NULL;

    /*
     * ECMA-357 10.6.1 step 1 seems to be incorrect.  The spec says:
     *
     * 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
     *
     * First, _P_ should be _s_, to refer to the given string.
     *
     * Second, why does ToXMLName applied to the string type throw TypeError
     * only for numeric literals without any leading or trailing whitespace?
     *
     * If the idea is to reject uint32_t property names, then the check needs to
     * be stricter, to exclude hexadecimal and floating point literals.
     */
    if (js_IdIsIndex(AtomToId(atomizedName), &index))
        goto bad;

    if (*atomizedName->chars() == '@') {
        name = js_NewDependentString(cx, name, 1, name->length() - 1);
        if (!name)
            return NULL;
        *funidp = JSID_VOID;
        return ToAttributeName(cx, STRING_TO_JSVAL(name));
    }

construct:
    v = STRING_TO_JSVAL(name);
    obj = ConstructObjectWithArguments(cx, &QNameClass, 1, &v);
    if (!obj)
        return NULL;

out:
    JSAtom *localName;
    *funidp = GetLocalNameFromFunctionQName(obj, &localName, cx)
              ? AtomToId(localName)
              : JSID_VOID;
    return obj;

bad:
    JSAutoByteString bytes;
    if (js_ValueToPrintable(cx, StringValue(name), &bytes))
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XML_NAME, bytes.ptr());
    return NULL;
}

/* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
static JSBool
AddInScopeNamespace(JSContext *cx, JSXML *xml, JSObject *ns)
{
    JSLinearString *prefix, *prefix2;
    JSObject *match, *ns2;
    uint32_t i, n, m;

    if (xml->xml_class != JSXML_CLASS_ELEMENT)
        return JS_TRUE;

    /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
    prefix = ns->getNamePrefix();
    if (!prefix) {
        match = NULL;
        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
            ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
            if (ns2 && EqualStrings(ns2->getNameURI(), ns->getNameURI())) {
                match = ns2;
                break;
            }
        }
        if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
            return JS_FALSE;
    } else {
        if (prefix->empty() && xml->name->getNameURI()->empty())
            return JS_TRUE;
        match = NULL;
#ifdef __GNUC__         /* suppress bogus gcc warnings */
        m = XML_NOT_FOUND;
#endif
        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
            ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
            if (ns2 && (prefix2 = ns2->getNamePrefix()) &&
                EqualStrings(prefix2, prefix)) {
                match = ns2;
                m = i;
                break;
            }
        }
        if (match && !EqualStrings(match->getNameURI(), ns->getNameURI())) {
            ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
                                  JSObject);
            JS_ASSERT(ns2 == match);
            match->clearNamePrefix();
            if (!AddInScopeNamespace(cx, xml, match))
                return JS_FALSE;
        }
        if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
            return JS_FALSE;
    }

    /* OPTION: enforce that descendants have superset namespaces. */
    return JS_TRUE;
}

/* ECMA-357 9.2.1.6 XMLList [[Append]]. */
static JSBool
Append(JSContext *cx, JSXML *list, JSXML *xml)
{
    JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);

    uint32_t i = list->xml_kids.length;
    if (xml->xml_class == JSXML_CLASS_LIST) {
        list->xml_target = xml->xml_target;
        list->xml_targetprop = xml->xml_targetprop;
        uint32_t n = JSXML_LENGTH(xml);
        if (!list->xml_kids.setCapacity(cx, i + n))
            return JS_FALSE;
        for (uint32_t j = 0; j < n; j++) {
            if (JSXML *kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML))
                XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
        }
        return JS_TRUE;
    }

    list->xml_target = xml->parent;
    if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
        list->xml_targetprop = NULL;
    else
        list->xml_targetprop = xml->name;
    if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
        return JS_FALSE;
    return JS_TRUE;
}

/* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
static JSXML *
DeepCopyInLRS(JSContext *cx, JSXML *xml, unsigned flags);

static JSXML *
DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, unsigned flags)
{
    JSXML *copy;

    copy = DeepCopyInLRS(cx, xml, flags);
    if (copy) {
        if (obj) {
            /* Caller provided the object for this copy, hook 'em up. */
            obj->setPrivateGCThing(copy);
            copy->object = obj;
        } else if (!js_GetXMLObject(cx, copy)) {
            copy = NULL;
        }
    }
    return copy;
}

/*
 * (i) We must be in a local root scope (InLRS).
 * (ii) parent must have a rooted object.
 * (iii) from's owning object must be locked if not thread-local.
 */
static JSBool
DeepCopySetInLRS(JSContext *cx, JSXMLArray<JSXML> *from, JSXMLArray<JSXML> *to, JSXML *parent,
                 unsigned flags)
{
    uint32_t j, n;
    JSXML *kid2;
    JSString *str;

    n = from->length;
    if (!to->setCapacity(cx, n))
        return JS_FALSE;

    JSXMLArrayCursor<JSXML> cursor(from);
    j = 0;
    while (JSXML *kid = cursor.getNext()) {
        if ((flags & XSF_IGNORE_COMMENTS) &&
            kid->xml_class == JSXML_CLASS_COMMENT) {
            continue;
        }
        if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
            kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
            continue;
        }
        if ((flags & XSF_IGNORE_WHITESPACE) &&
            (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
            continue;
        }
        kid2 = DeepCopyInLRS(cx, kid, flags);
        if (!kid2) {
            to->length = j;
            return JS_FALSE;
        }

        if ((flags & XSF_IGNORE_WHITESPACE) &&
            n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
            str = ChompXMLWhitespace(cx, kid2->xml_value);
            if (!str) {
                to->length = j;
                return JS_FALSE;
            }
            kid2->xml_value = str;
        }

        XMLARRAY_SET_MEMBER(to, j, kid2);
        ++j;
        if (parent->xml_class != JSXML_CLASS_LIST)
            kid2->parent = parent;
    }

    if (j < n)
        to->trim();
    return JS_TRUE;
}

static JSXML *
DeepCopyInLRS(JSContext *cx, JSXML *xml, unsigned flags)
{
    JSXML *copy;
    JSObject *qn;
    JSBool ok;
    uint32_t i, n;
    JSObject *ns, *ns2;

    JS_CHECK_RECURSION(cx, return NULL);

    copy = js_NewXML(cx, JSXMLClass(xml->xml_class));
    if (!copy)
        return NULL;
    qn = xml->name;
    if (qn) {
        qn = NewXMLQName(cx, qn->getNameURI(), qn->getNamePrefix(), qn->getQNameLocalName());
        if (!qn) {
            ok = JS_FALSE;
            goto out;
        }
    }
    copy->name = qn;
    copy->xml_flags = xml->xml_flags;

    if (JSXML_HAS_VALUE(xml)) {
        copy->xml_value = xml->xml_value;
        ok = JS_TRUE;
    } else {
        ok = DeepCopySetInLRS(cx, &xml->xml_kids, &copy->xml_kids, copy, flags);
        if (!ok)
            goto out;

        if (xml->xml_class == JSXML_CLASS_LIST) {
            copy->xml_target = xml->xml_target;
            copy->xml_targetprop = xml->xml_targetprop;
        } else {
            n = xml->xml_namespaces.length;
            ok = copy->xml_namespaces.setCapacity(cx, n);
            if (!ok)
                goto out;
            for (i = 0; i < n; i++) {
                ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
                if (!ns)
                    continue;
                ns2 = NewXMLNamespace(cx, ns->getNamePrefix(), ns->getNameURI(),
                                      IsDeclared(ns));
                if (!ns2) {
                    copy->xml_namespaces.length = i;
                    ok = JS_FALSE;
                    goto out;
                }
                XMLARRAY_SET_MEMBER(&copy->xml_namespaces, i, ns2);
            }

            ok = DeepCopySetInLRS(cx, &xml->xml_attrs, &copy->xml_attrs, copy,
                                  0);
            if (!ok)
                goto out;
        }
    }

out:
    if (!ok)
        return NULL;
    return copy;
}

/* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
static void
DeleteByIndex(JSContext *cx, JSXML *xml, uint32_t index)
{
    JSXML *kid;

    if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
        kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
        if (kid)
            kid->parent = NULL;
        XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
    }
}

typedef JSBool (*JSXMLNameMatcher)(JSObject *nameqn, JSXML *xml);

static JSBool
MatchAttrName(JSObject *nameqn, JSXML *attr)
{
    JSObject *attrqn = attr->name;
    JSLinearString *localName = nameqn->getQNameLocalName();
    JSLinearString *uri;

    return (IS_STAR(localName) ||
            EqualStrings(attrqn->getQNameLocalName(), localName)) &&
           (!(uri = nameqn->getNameURI()) ||
            EqualStrings(attrqn->getNameURI(), uri));
}

static JSBool
MatchElemName(JSObject *nameqn, JSXML *elem)
{
    JSLinearString *localName = nameqn->getQNameLocalName();
    JSLinearString *uri;

    return (IS_STAR(localName) ||
            (elem->xml_class == JSXML_CLASS_ELEMENT &&
             EqualStrings(elem->name->getQNameLocalName(), localName))) &&
           (!(uri = nameqn->getNameURI()) ||
            (elem->xml_class == JSXML_CLASS_ELEMENT &&
             EqualStrings(elem->name->getNameURI(), uri)));
}

/* ECMA-357 9.1.1.8 XML [[Descendants]] and 9.2.1.8 XMLList [[Descendants]]. */
static JSBool
DescendantsHelper(JSContext *cx, JSXML *xml, JSObject *nameqn, JSXML *list)
{
    uint32_t i, n;
    JSXML *attr, *kid;

    JS_CHECK_RECURSION(cx, return JS_FALSE);

    if (xml->xml_class == JSXML_CLASS_ELEMENT &&
        nameqn->getClass() == &AttributeNameClass) {
        for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
            attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
            if (attr && MatchAttrName(nameqn, attr)) {
                if (!Append(cx, list, attr))
                    return JS_FALSE;
            }
        }
    }

    for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
        kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
        if (!kid)
            continue;
        if (nameqn->getClass() != &AttributeNameClass &&
            MatchElemName(nameqn, kid)) {
            if (!Append(cx, list, kid))
                return JS_FALSE;
        }
        if (!DescendantsHelper(cx, kid, nameqn, list))
            return JS_FALSE;
    }
    return JS_TRUE;
}

static JSXML *
Descendants(JSContext *cx, JSXML *xml, jsval id)
{
    jsid funid;
    JSObject *nameqn;
    JSObject *listobj;
    JSXML *list, *kid;
    uint32_t i, n;
    JSBool ok;

    nameqn = ToXMLName(cx, id, &funid);
    if (!nameqn)
        return NULL;

    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
    if (!listobj)
        return NULL;
    list = (JSXML *) listobj->getPrivate();
    if (!JSID_IS_VOID(funid))
        return list;

    /*
     * Protect nameqn's object and strings from GC by linking list to it
     * temporarily.  The newborn GC root for the last allocated object
     * protects listobj, which protects list. Any other object allocations
     * occurring beneath DescendantsHelper use local roots.
     */
    list->name = nameqn;
    if (xml->xml_class == JSXML_CLASS_LIST) {
        ok = JS_TRUE;
        for (i = 0, n = xml->xml_kids.length; i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
                ok = DescendantsHelper(cx, kid, nameqn, list);
                if (!ok)
                    break;
            }
        }
    } else {
        ok = DescendantsHelper(cx, xml, nameqn, list);
    }
    if (!ok)
        return NULL;
    list->name = NULL;
    return list;
}

/* Recursive (JSXML *) parameterized version of Equals. */
static JSBool
XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
{
    JSObject *qn, *vqn;
    uint32_t i, j, n;
    JSXML *kid, *vkid, *attr, *vattr;
    JSObject *xobj, *vobj;

retry:
    if (xml->xml_class != vxml->xml_class) {
        if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
            xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
            if (xml)
                goto retry;
        }
        if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
            vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
            if (vxml)
                goto retry;
        }
        *bp = JS_FALSE;
        return JS_TRUE;
    }

    qn = xml->name;
    vqn = vxml->name;
    if (qn) {
        *bp = vqn &&
              EqualStrings(qn->getQNameLocalName(), vqn->getQNameLocalName()) &&
              EqualStrings(qn->getNameURI(), vqn->getNameURI());
    } else {
        *bp = vqn == NULL;
    }
    if (!*bp)
        return JS_TRUE;

    if (JSXML_HAS_VALUE(xml)) {
        bool equal;
        if (!EqualStrings(cx, xml->xml_value, vxml->xml_value, &equal))
            return JS_FALSE;
        *bp = equal;
    } else if (xml->xml_kids.length != vxml->xml_kids.length) {
        *bp = JS_FALSE;
    } else {
        {
            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
            JSXMLArrayCursor<JSXML> vcursor(&vxml->xml_kids);
            for (;;) {
                kid = cursor.getNext();
                vkid = vcursor.getNext();
                if (!kid || !vkid) {
                    *bp = !kid && !vkid;
                    break;
                }
                xobj = js_GetXMLObject(cx, kid);
                vobj = js_GetXMLObject(cx, vkid);
                if (!xobj || !vobj ||
                    !js_TestXMLEquality(cx, ObjectValue(*xobj), ObjectValue(*vobj), bp))
                    return JS_FALSE;
                if (!*bp)
                    break;
            }
        }

        if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
            n = xml->xml_attrs.length;
            if (n != vxml->xml_attrs.length)
                *bp = JS_FALSE;
            for (i = 0; *bp && i < n; i++) {
                attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
                if (!attr)
                    continue;
                j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
                if (j == XML_NOT_FOUND) {
                    *bp = JS_FALSE;
                    break;
                }
                vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
                if (!vattr)
                    continue;
                bool equal;
                if (!EqualStrings(cx, attr->xml_value, vattr->xml_value, &equal))
                    return JS_FALSE;
                *bp = equal;
            }
        }
    }

    return JS_TRUE;
}

/* ECMA-357 9.1.1.9 XML [[Equals]] and 9.2.1.9 XMLList [[Equals]]. */
static JSBool
Equals(JSContext *cx, JSXML *xml, jsval v, JSBool *bp)
{
    JSObject *vobj;
    JSXML *vxml;

    if (JSVAL_IS_PRIMITIVE(v)) {
        *bp = JS_FALSE;
        if (xml->xml_class == JSXML_CLASS_LIST) {
            if (xml->xml_kids.length == 1) {
                vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
                if (!vxml)
                    return JS_TRUE;
                vobj = js_GetXMLObject(cx, vxml);
                if (!vobj)
                    return JS_FALSE;
                return js_TestXMLEquality(cx, ObjectValue(*vobj), v, bp);
            }
            if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
                *bp = JS_TRUE;
        }
    } else {
        vobj = JSVAL_TO_OBJECT(v);
        if (!vobj->isXML()) {
            *bp = JS_FALSE;
        } else {
            vxml = (JSXML *) vobj->getPrivate();
            if (!XMLEquals(cx, xml, vxml, bp))
                return JS_FALSE;
        }
    }
    return JS_TRUE;
}

static JSBool
CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
{
    JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);

    do {
        if (xml == kid) {
            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                 JSMSG_CYCLIC_VALUE, js_XML_str);
            return JS_FALSE;
        }
    } while ((xml = xml->parent) != NULL);

    return JS_TRUE;
}

/* ECMA-357 9.1.1.11 XML [[Insert]]. */
static JSBool
Insert(JSContext *cx, JSXML *xml, uint32_t i, jsval v)
{
    uint32_t j, n;
    JSXML *vxml, *kid;
    JSObject *vobj;
    JSString *str;

    if (!JSXML_HAS_KIDS(xml))
        return JS_TRUE;

    n = 1;
    vxml = NULL;
    if (!JSVAL_IS_PRIMITIVE(v)) {
        vobj = JSVAL_TO_OBJECT(v);
        if (vobj->isXML()) {
            vxml = (JSXML *) vobj->getPrivate();
            if (vxml->xml_class == JSXML_CLASS_LIST) {
                n = vxml->xml_kids.length;
                if (n == 0)
                    return JS_TRUE;
                for (j = 0; j < n; j++) {
                    kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
                    if (!kid)
                        continue;
                    if (!CheckCycle(cx, xml, kid))
                        return JS_FALSE;
                }
            } else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
                /* OPTION: enforce that descendants have superset namespaces. */
                if (!CheckCycle(cx, xml, vxml))
                    return JS_FALSE;
            }
        }
    }
    if (!vxml) {
        str = ToString(cx, v);
        if (!str)
            return JS_FALSE;

        vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
        if (!vxml)
            return JS_FALSE;
        vxml->xml_value = str;
    }

    if (i > xml->xml_kids.length)
        i = xml->xml_kids.length;

    if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
        return JS_FALSE;

    if (vxml->xml_class == JSXML_CLASS_LIST) {
        for (j = 0; j < n; j++) {
            kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
            if (!kid)
                continue;
            kid->parent = xml;
            XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);

            /* OPTION: enforce that descendants have superset namespaces. */
        }
    } else {
        vxml->parent = xml;
        XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
    }
    return JS_TRUE;
}

/* ECMA-357 9.1.1.12 XML [[Replace]]. */
static JSBool
Replace(JSContext *cx, JSXML *xml, uint32_t i, jsval v)
{
    uint32_t n;
    JSXML *vxml, *kid;
    JSObject *vobj;
    JSString *str;

    if (!JSXML_HAS_KIDS(xml))
        return JS_TRUE;

    /*
     * 9.1.1.12
     * [[Replace]] handles _i >= x.[[Length]]_ by incrementing _x.[[Length]_.
     * It should therefore constrain callers to pass in _i <= x.[[Length]]_.
     */
    n = xml->xml_kids.length;
    if (i > n)
        i = n;

    vxml = NULL;
    if (!JSVAL_IS_PRIMITIVE(v)) {
        vobj = JSVAL_TO_OBJECT(v);
        if (vobj->isXML())
            vxml = (JSXML *) vobj->getPrivate();
    }

    switch (vxml ? JSXMLClass(vxml->xml_class) : JSXML_CLASS_LIMIT) {
      case JSXML_CLASS_ELEMENT:
        /* OPTION: enforce that descendants have superset namespaces. */
        if (!CheckCycle(cx, xml, vxml))
            return JS_FALSE;
      case JSXML_CLASS_COMMENT:
      case JSXML_CLASS_PROCESSING_INSTRUCTION:
      case JSXML_CLASS_TEXT:
        goto do_replace;

      case JSXML_CLASS_LIST:
        if (i < n)
            DeleteByIndex(cx, xml, i);
        if (!Insert(cx, xml, i, v))
            return JS_FALSE;
        break;

      default:
        str = ToString(cx, v);
        if (!str)
            return JS_FALSE;

        vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
        if (!vxml)
            return JS_FALSE;
        vxml->xml_value = str;

      do_replace:
        vxml->parent = xml;
        if (i < n) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid)
                kid->parent = NULL;
        }
        if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
            return JS_FALSE;
        break;
    }

    return JS_TRUE;
}

/* ECMA-357 9.1.1.3 XML [[Delete]], 9.2.1.3 XML [[Delete]] qname cases. */
static void
DeleteNamedProperty(JSContext *cx, JSXML *xml, JSObject *nameqn,
                    JSBool attributes)
{
    JSXMLArray<JSXML> *array;
    uint32_t index, deleteCount;
    JSXML *kid;
    JSXMLNameMatcher matcher;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        array = &xml->xml_kids;
        for (index = 0; index < array->length; index++) {
            kid = XMLARRAY_MEMBER(array, index, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT)
                DeleteNamedProperty(cx, kid, nameqn, attributes);
        }
    } else if (xml->xml_class == JSXML_CLASS_ELEMENT) {
        if (attributes) {
            array = &xml->xml_attrs;
            matcher = MatchAttrName;
        } else {
            array = &xml->xml_kids;
            matcher = MatchElemName;
        }
        deleteCount = 0;
        for (index = 0; index < array->length; index++) {
            kid = XMLARRAY_MEMBER(array, index, JSXML);
            if (kid && matcher(nameqn, kid)) {
                kid->parent = NULL;
                XMLArrayDelete(cx, array, index, JS_FALSE);
                ++deleteCount;
            } else if (deleteCount != 0) {
                XMLARRAY_SET_MEMBER(array,
                                    index - deleteCount,
                                    array->vector[index]);
            }
        }
        array->length -= deleteCount;
    }
}

/* ECMA-357 9.2.1.3 index case. */
static void
DeleteListElement(JSContext *cx, JSXML *xml, uint32_t index)
{
    JSXML *kid, *parent;
    uint32_t kidIndex;

    JS_ASSERT(xml->xml_class == JSXML_CLASS_LIST);

    if (index < xml->xml_kids.length) {
        kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
        if (kid) {
            parent = kid->parent;
            if (parent) {
                JS_ASSERT(parent != xml);
                JS_ASSERT(JSXML_HAS_KIDS(parent));

                if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
                    DeleteNamedProperty(cx, parent, kid->name, JS_TRUE);
                } else {
                    kidIndex = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid,
                                                    pointer_match);
                    JS_ASSERT(kidIndex != XML_NOT_FOUND);
                    DeleteByIndex(cx, parent, kidIndex);
                }
            }
            XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
        }
    }
}

static JSBool
SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
{
    JSXMLArray<JSObject> *nsarray;
    uint32_t i, n;
    JSObject *ns;

    nsarray = &xml->xml_namespaces;
    while ((xml = xml->parent) != NULL) {
        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
            ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
            if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
                if (!XMLARRAY_APPEND(cx, nsarray, ns))
                    return JS_FALSE;
            }
        }
    }
    return JS_TRUE;
}

static JSBool
GetNamedProperty(JSContext *cx, JSXML *xml, JSObject* nameqn, JSXML *list)
{
    JSXMLArray<JSXML> *array;
    JSXMLNameMatcher matcher;
    JSBool attrs;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
        while (JSXML *kid = cursor.getNext()) {
            if (kid->xml_class == JSXML_CLASS_ELEMENT &&
                !GetNamedProperty(cx, kid, nameqn, list)) {
                return JS_FALSE;
            }
        }
    } else if (xml->xml_class == JSXML_CLASS_ELEMENT) {
        attrs = (nameqn->getClass() == &AttributeNameClass);
        if (attrs) {
            array = &xml->xml_attrs;
            matcher = MatchAttrName;
        } else {
            array = &xml->xml_kids;
            matcher = MatchElemName;
        }

        JSXMLArrayCursor<JSXML> cursor(array);
        while (JSXML *kid = cursor.getNext()) {
            if (matcher(nameqn, kid)) {
                if (!attrs &&
                    kid->xml_class == JSXML_CLASS_ELEMENT &&
                    !SyncInScopeNamespaces(cx, kid)) {
                    return JS_FALSE;
                }
                if (!Append(cx, list, kid))
                    return JS_FALSE;
            }
        }
    }

    return JS_TRUE;
}

/* ECMA-357 9.1.1.1 XML [[Get]] and 9.2.1.1 XMLList [[Get]]. */
static JSBool
GetProperty(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)
{
    JSXML *xml, *list, *kid;
    uint32_t index;
    JSObject *kidobj, *listobj;
    JSObject *nameqn;

    if (!obj->isXML())
        return true;
    xml = (JSXML *) obj->getPrivate();
    if (!xml)
        return true;

    if (js_IdIsIndex(id, &index)) {
        if (!JSXML_HAS_KIDS(xml)) {
            vp.set((index == 0) ? OBJECT_TO_JSVAL(obj) : JSVAL_VOID);
        } else {
            /*
             * ECMA-357 9.2.1.1 starts here.
             *
             * Erratum: 9.2 is not completely clear that indexed properties
             * correspond to kids, but that's what it seems to say, and it's
             * what any sane user would want.
             */
            if (index < xml->xml_kids.length) {
                kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
                if (!kid) {
                    vp.setUndefined();
                    return true;
                }
                kidobj = js_GetXMLObject(cx, kid);
                if (!kidobj)
                    return false;

                vp.set(OBJECT_TO_JSVAL(kidobj));
            } else {
                vp.setUndefined();
            }
        }
        return true;
    }

    /*
     * ECMA-357 9.2.1.1/9.1.1.1 qname case.
     */
    RootedId funid(cx);
    nameqn = ToXMLName(cx, IdToJsval(id), funid.address());
    if (!nameqn)
        return false;
    if (!JSID_IS_VOID(funid))
        return GetXMLFunction(cx, obj, funid, vp);

    jsval roots[2] = { OBJECT_TO_JSVAL(nameqn), JSVAL_NULL };
    AutoArrayRooter tvr(cx, ArrayLength(roots), roots);

    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
    if (!listobj)
        return false;

    roots[1] = OBJECT_TO_JSVAL(listobj);

    list = (JSXML *) listobj->getPrivate();
    if (!GetNamedProperty(cx, xml, nameqn, list))
        return false;

    /*
     * Erratum: ECMA-357 9.1.1.1 misses that [[Append]] sets the
     * given list's [[TargetProperty]] to the property that is being
     * appended. This means that any use of the internal [[Get]]
     * property returns a list which, when used by e.g. [[Insert]]
     * duplicates the last element matched by id. See bug 336921.
     */
    list->xml_target = xml;
    list->xml_targetprop = nameqn;
    vp.set(OBJECT_TO_JSVAL(listobj));
    return true;
}

static JSXML *
CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
{
    JS_ASSERT(xml->object != obj);

    xml = DeepCopy(cx, xml, obj, 0);
    if (!xml)
        return NULL;

    JS_ASSERT(xml->object == obj);
    return xml;
}

#define CHECK_COPY_ON_WRITE(cx,xml,obj)                                       \
    (xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))

static JSString *
KidToString(JSContext *cx, JSXML *xml, uint32_t index)
{
    JSXML *kid;
    JSObject *kidobj;

    kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
    if (!kid)
        return cx->runtime->emptyString;
    kidobj = js_GetXMLObject(cx, kid);
    if (!kidobj)
        return NULL;
    return ToString(cx, ObjectValue(*kidobj));
}

/* Forward declared -- its implementation uses other statics that call it. */
static JSBool
ResolveValue(JSContext *cx, JSXML *list, JSXML **result);

/* ECMA-357 9.1.1.2 XML [[Put]] and 9.2.1.2 XMLList [[Put]]. */
static JSBool
PutProperty(JSContext *cx, HandleObject obj_, HandleId id_, JSBool strict, MutableHandleValue vp)
{
    JSBool ok, primitiveAssign;
    enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
    JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
    JSObject *vobj, *nameobj, *attrobj, *copyobj;
    JSObject *targetprop, *nameqn, *attrqn;
    uint32_t index, i, j, k, n, q, matchIndex;
    RootedValue attrval(cx), nsval(cx);
    JSObject *ns;

    RootedObject obj(cx, obj_), kidobj(cx);
    RootedId id(cx, id_), funid(cx);

    if (!obj->isXML())
        return JS_TRUE;
    xml = (JSXML *) obj->getPrivate();
    if (!xml)
        return JS_TRUE;

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;

    /* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
    vxml = NULL;
    if (!JSVAL_IS_PRIMITIVE(vp)) {
        vobj = JSVAL_TO_OBJECT(vp);
        if (vobj->isXML())
            vxml = (JSXML *) vobj->getPrivate();
    }

    ok = JS_TRUE;

    jsval roots[3];
    roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
    roots[ID_ROOT] = IdToJsval(id);
    roots[VAL_ROOT] = vp;
    AutoArrayRooter tvr(cx, ArrayLength(roots), roots);

    if (js_IdIsIndex(id, &index)) {
        if (xml->xml_class != JSXML_CLASS_LIST) {
            /* See NOTE in spec: this variation is reserved for future use. */
            ReportBadXMLName(cx, IdToValue(id));
            goto bad;
        }

        /*
         * Step 1 of ECMA-357 9.2.1.2 index case sets i to the property index.
         */
        i = index;

        /* 2(a-b). */
        if (xml->xml_target) {
            ok = ResolveValue(cx, xml->xml_target, &rxml);
            if (!ok)
                goto out;
            if (!rxml)
                goto out;
            JS_ASSERT(rxml->object);
        } else {
            rxml = NULL;
        }

        /* 2(c). */
        if (index >= xml->xml_kids.length) {
            /* 2(c)(i). */
            if (rxml) {
                if (rxml->xml_class == JSXML_CLASS_LIST) {
                    if (rxml->xml_kids.length != 1)
                        goto out;
                    rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
                    if (!rxml)
                        goto out;
                    ok = js_GetXMLObject(cx, rxml) != NULL;
                    if (!ok)
                        goto out;
                }

                /*
                 * Erratum: ECMA-357 9.2.1.2 step 2(c)(ii) sets
                 * _y.[[Parent]] = r_ where _r_ is the result of
                 * [[ResolveValue]] called on _x.[[TargetObject]] in
                 * 2(a)(i).  This can result in text parenting text:
                 *
                 *    var MYXML = new XML();
                 *    MYXML.appendChild(new XML("<TEAM>Giants</TEAM>"));
                 *
                 * (testcase from Werner Sharp <wsharp@macromedia.com>).
                 *
                 * To match insertChildAfter, insertChildBefore,
                 * prependChild, and setChildren, we should silently
                 * do nothing in this case.
                 */
                if (!JSXML_HAS_KIDS(rxml))
                    goto out;
            }

            /* 2(c)(ii) is distributed below as several js_NewXML calls. */
            targetprop = xml->xml_targetprop;
            if (!targetprop || IS_STAR(targetprop->getQNameLocalName())) {
                /* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
                kid = js_NewXML(cx, JSXML_CLASS_TEXT);
                if (!kid)
                    goto bad;
            } else {
                nameobj = targetprop;
                if (nameobj->getClass() == &AttributeNameClass) {
                    /*
                     * 2(c)(iii)(1-3).
                     * Note that rxml can't be null here, because target
                     * and targetprop are non-null.
                     */
                    Rooted<JSObject*> robj(cx, rxml->object);
                    ok = GetProperty(cx, robj, id, &attrval);
                    if (!ok)
                        goto out;
                    if (JSVAL_IS_PRIMITIVE(attrval))    /* no such attribute */
                        goto out;
                    attrobj = JSVAL_TO_OBJECT(attrval);
                    attr = (JSXML *) attrobj->getPrivate();
                    if (JSXML_LENGTH(attr) != 0)
                        goto out;

                    kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
                } else {
                    /* 2(c)(v). */
                    kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
                }
                if (!kid)
                    goto bad;

                /* An important bit of 2(c)(ii). */
                kid->name = targetprop;
            }

            /* Final important bit of 2(c)(ii). */
            kid->parent = rxml;

            /* 2(c)(vi-vii). */
            i = xml->xml_kids.length;
            if (kid->xml_class != JSXML_CLASS_ATTRIBUTE) {
                /*
                 * 2(c)(vii)(1) tests whether _y.[[Parent]]_ is not null.
                 * y.[[Parent]] is here called kid->parent, which we know
                 * from 2(c)(ii) is _r_, here called rxml.  So let's just
                 * test that!  Erratum, the spec should be simpler here.
                 */
                if (rxml) {
                    JS_ASSERT(JSXML_HAS_KIDS(rxml));
                    n = rxml->xml_kids.length;
                    j = n - 1;
                    if (n != 0 && i != 0) {
                        for (n = j, j = 0; j < n; j++) {
                            if (rxml->xml_kids.vector[j] ==
                                xml->xml_kids.vector[i-1]) {
                                break;
                            }
                        }
                    }

                    kidobj = js_GetXMLObject(cx, kid);
                    if (!kidobj)
                        goto bad;
                    ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
                    if (!ok)
                        goto out;
                }

                /*
                 * 2(c)(vii)(2-3).
                 * Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
                 * typo for [[TargetProperty]].
                 */
                if (vxml) {
                    kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
                        ? vxml->xml_targetprop
                        : vxml->name;
                }
            }

            /* 2(c)(viii). */
            ok = Append(cx, xml, kid);
            if (!ok)
                goto out;
        }

        /* 2(d). */
        if (!vxml ||
            vxml->xml_class == JSXML_CLASS_TEXT ||
            vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
            ok = JS_ConvertValue(cx, vp, JSTYPE_STRING, vp.address());
            if (!ok)
                goto out;
            roots[VAL_ROOT] = vp;
        }

        /* 2(e). */
        kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
        if (!kid)
            goto out;
        parent = kid->parent;
        if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
            nameobj = kid->name;
            if (nameobj->getClass() != &AttributeNameClass) {
                nameobj = NewXMLAttributeName(cx, nameobj->getNameURI(), nameobj->getNamePrefix(),
                                              nameobj->getQNameLocalName());
                if (!nameobj)
                    goto bad;
            }
            id = OBJECT_TO_JSID(nameobj);

            if (parent) {
                /* 2(e)(i). */
                RootedObject parentobj(cx, js_GetXMLObject(cx, parent));
                if (!parentobj)
                    goto bad;
                ok = PutProperty(cx, parentobj, id, strict, vp);
                if (!ok)
                    goto out;

                /* 2(e)(ii). */
                ok = GetProperty(cx, parentobj, id, vp);
                if (!ok)
                    goto out;
                attr = (JSXML *) JSVAL_TO_OBJECT(vp)->getPrivate();

                /* 2(e)(iii) - the length check comes from the bug 375406. */
                if (attr->xml_kids.length != 0)
                    xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
            }
        }

        /* 2(f). */
        else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
            /*
             * 2(f)(i)
             *
             * Erratum: the spec says to create a shallow copy _c_ of _V_, but
             * if we do that we never change the parent of each child in the
             * list.  Since [[Put]] when called on an XML object deeply copies
             * the provided list _V_, we also do so here.  Perhaps the shallow
             * copy was a misguided optimization?
             */
            copy = DeepCopyInLRS(cx, vxml, 0);
            if (!copy)
                goto bad;
            copyobj = js_GetXMLObject(cx, copy);
            if (!copyobj)
                goto bad;

            JS_ASSERT(parent != xml);
            if (parent) {
                q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, pointer_match);
                JS_ASSERT(q != XML_NOT_FOUND);
                ok = Replace(cx, parent, q, OBJECT_TO_JSVAL(copyobj));
                if (!ok)
                    goto out;

#ifdef DEBUG
                /* Erratum: this loop in the spec is useless. */
                for (j = 0, n = copy->xml_kids.length; j < n; j++) {
                    kid2 = XMLARRAY_MEMBER(&parent->xml_kids, q + j, JSXML);
                    JS_ASSERT(XMLARRAY_MEMBER(&copy->xml_kids, j, JSXML)
                              == kid2);
                }
#endif
            }

            /*
             * 2(f)(iv-vi).
             * Erratum: notice the unhandled zero-length V basis case and
             * the off-by-one errors for the n != 0 cases in the spec.
             */
            n = copy->xml_kids.length;
            if (n == 0) {
                XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
            } else {
                ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
                if (!ok)
                    goto out;

                for (j = 0; j < n; j++)
                    xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
            }
        }

        /* 2(g). */
        else if (vxml || JSXML_HAS_VALUE(kid)) {
            if (parent) {
                q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, pointer_match);
                JS_ASSERT(q != XML_NOT_FOUND);
                ok = Replace(cx, parent, q, vp);
                if (!ok)
                    goto out;

                vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
                if (!vxml)
                    goto out;
                roots[VAL_ROOT] = OBJECT_TO_JSVAL(vxml->object);
                vp.set(roots[VAL_ROOT]);
            }

            /*
             * 2(g)(iii).
             * Erratum: _V_ may not be of type XML, but all index-named
             * properties _x[i]_ in an XMLList _x_ must be of type XML,
             * according to 9.2.1.1 Overview and other places in the spec.
             *
             * Thanks to 2(d), we know _V_ (*vp here) is either a string
             * or an XML/XMLList object.  If *vp is a string, call ToXML
             * on it to satisfy the constraint.
             */
            if (!vxml) {
                JS_ASSERT(JSVAL_IS_STRING(vp));
                vobj = ToXML(cx, vp);
                if (!vobj)
                    goto bad;
                roots[VAL_ROOT] = OBJECT_TO_JSVAL(vobj);
                vp.set(roots[VAL_ROOT]);
                vxml = (JSXML *) vobj->getPrivate();
            }
            XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
        }

        /* 2(h). */
        else {
            kidobj = js_GetXMLObject(cx, kid);
            if (!kidobj)
                goto bad;
            id = NameToId(cx->names().star);
            ok = PutProperty(cx, kidobj, id, strict, vp);
            if (!ok)
                goto out;
        }
    } else {
        /*
         * ECMA-357 9.2.1.2/9.1.1.2 qname case.
         */
        nameqn = ToXMLName(cx, IdToJsval(id), funid.address());
        if (!nameqn)
            goto bad;
        if (!JSID_IS_VOID(funid)) {
            ok = baseops::SetPropertyHelper(cx, obj, obj, funid, 0, vp, false);
            goto out;
        }
        nameobj = nameqn;
        roots[ID_ROOT] = OBJECT_TO_JSVAL(nameobj);

        if (xml->xml_class == JSXML_CLASS_LIST) {
            /*
             * Step 3 of 9.2.1.2.
             * Erratum: if x.[[Length]] > 1 or [[ResolveValue]] returns null
             * or an r with r.[[Length]] != 1, throw TypeError.
             */
            n = JSXML_LENGTH(xml);
            if (n > 1)
                goto type_error;
            if (n == 0) {
                ok = ResolveValue(cx, xml, &rxml);
                if (!ok)
                    goto out;
                if (!rxml || JSXML_LENGTH(rxml) != 1)
                    goto type_error;
                ok = Append(cx, xml, rxml);
                if (!ok)
                    goto out;
            }
            JS_ASSERT(JSXML_LENGTH(xml) == 1);
            xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
            if (!xml)
                goto out;
            JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
            obj = js_GetXMLObject(cx, xml);
            if (!obj)
                goto bad;
            roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);

            /* FALL THROUGH to non-list case */
        }

        /*
         * ECMA-357 9.1.1.2.
         * Erratum: move steps 3 and 4 to before 1 and 2, to avoid wasted
         * effort in ToString or [[DeepCopy]].
         */

        if (JSXML_HAS_VALUE(xml))
            goto out;

        if (!vxml ||
            vxml->xml_class == JSXML_CLASS_TEXT ||
            vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
            ok = JS_ConvertValue(cx, vp, JSTYPE_STRING, vp.address());
            if (!ok)
                goto out;
        } else {
            rxml = DeepCopyInLRS(cx, vxml, 0);
            if (!rxml || !js_GetXMLObject(cx, rxml))
                goto bad;
            vxml = rxml;
            vp.set(OBJECT_TO_JSVAL(vxml->object));
        }
        roots[VAL_ROOT] = vp;

        /*
         * 6.
         * Erratum: why is this done here, so early? use is way later....
         */
        ok = js_GetDefaultXMLNamespace(cx, nsval.address());
        if (!ok)
            goto out;

        if (nameobj->getClass() == &AttributeNameClass) {
            /* 7(a). */
            if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
                goto out;

            /* 7(b-c). */
            if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
                n = vxml->xml_kids.length;
                if (n == 0) {
                    vp.set(STRING_TO_JSVAL(cx->runtime->emptyString));
                } else {
                    RootedString left(cx, KidToString(cx, vxml, 0));
                    if (!left)
                        goto bad;

                    RootedString space(cx, cx->names().space);
                    for (i = 1; i < n; i++) {
                        left = js_ConcatStrings(cx, left, space);
                        if (!left)
                            goto bad;
                        RootedString right(cx, KidToString(cx, vxml, i));
                        if (!right)
                            goto bad;
                        left = js_ConcatStrings(cx, left, right);
                        if (!left)
                            goto bad;
                    }

                    roots[VAL_ROOT] = STRING_TO_JSVAL(left);
                    vp.set(roots[VAL_ROOT]);
                }
            } else {
                ok = JS_ConvertValue(cx, vp, JSTYPE_STRING, vp.address());
                if (!ok)
                    goto out;
                roots[VAL_ROOT] = vp;
            }

            /* 7(d-e). */
            match = NULL;
            for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
                attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
                if (!attr)
                    continue;
                attrqn = attr->name;
                if (EqualStrings(attrqn->getQNameLocalName(), nameqn->getQNameLocalName())) {
                    JSLinearString *uri = nameqn->getNameURI();
                    if (!uri || EqualStrings(attrqn->getNameURI(), uri)) {
                        if (!match) {
                            match = attr;
                        } else {
                            DeleteNamedProperty(cx, xml, attrqn, JS_TRUE);
                            --i;
                        }
                    }
                }
            }

            /* 7(f). */
            attr = match;
            if (!attr) {
                /* 7(f)(i-ii). */
                JSLinearString *uri = nameqn->getNameURI();
                JSLinearString *left, *right;
                if (!uri) {
                    left = right = cx->runtime->emptyString;
                } else {
                    left = uri;
                    right = nameqn->getNamePrefix();
                }
                nameqn = NewXMLQName(cx, left, right, nameqn->getQNameLocalName());
                if (!nameqn)
                    goto bad;

                /* 7(f)(iii). */
                attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
                if (!attr)
                    goto bad;
                attr->parent = xml;
                attr->name = nameqn;

                /* 7(f)(iv). */
                ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
                if (!ok)
                    goto out;

                /* 7(f)(v-vi). */
                ns = GetNamespace(cx, nameqn, NULL);
                if (!ns)
                    goto bad;
                ok = AddInScopeNamespace(cx, xml, ns);
                if (!ok)
                    goto out;
            }

            /* 7(g). */
            attr->xml_value = JSVAL_TO_STRING(vp);
            goto out;
        }

        /* 8-9. */
        if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
            !IS_STAR(nameqn->getQNameLocalName())) {
            goto out;
        }

        /* 10-11. */
        id = JSID_VOID;
        primitiveAssign = !vxml && !IS_STAR(nameqn->getQNameLocalName());

        /* 12. */
        k = n = xml->xml_kids.length;
        matchIndex = XML_NOT_FOUND;
        kid2 = NULL;
        while (k != 0) {
            --k;
            kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
            if (kid && MatchElemName(nameqn, kid)) {
                if (matchIndex != XML_NOT_FOUND)
                    DeleteByIndex(cx, xml, matchIndex);
                matchIndex = k;
                kid2 = kid;
            }
        }

        /*
         * Erratum: ECMA-357 specified child insertion inconsistently:
         * insertChildBefore and insertChildAfter insert an arbitrary XML
         * instance, and therefore can create cycles, but appendChild as
         * specified by the "Overview" of 13.4.4.3 calls [[DeepCopy]] on
         * its argument.  But the "Semantics" in 13.4.4.3 do not include
         * any [[DeepCopy]] call.
         *
         * Fixing this (https://bugzilla.mozilla.org/show_bug.cgi?id=312692)
         * required adding cycle detection, and allowing duplicate kids to
         * be created (see comment 6 in the bug).  Allowing duplicate kid
         * references means the loop above will delete all but the lowest
         * indexed reference, and each [[DeleteByIndex]] nulls the kid's
         * parent.  Thus the need to restore parent here.  This is covered
         * by https://bugzilla.mozilla.org/show_bug.cgi?id=327564.
         */
        if (kid2) {
            JS_ASSERT(kid2->parent == xml || !kid2->parent);
            if (!kid2->parent)
                kid2->parent = xml;
        }

        /* 13. */
        if (matchIndex == XML_NOT_FOUND) {
            /* 13(a). */
            matchIndex = n;

            /* 13(b). */
            if (primitiveAssign) {
                JSLinearString *uri = nameqn->getNameURI();
                JSLinearString *left, *right;
                if (!uri) {
                    ns = JSVAL_TO_OBJECT(nsval);
                    left = ns->getNameURI();
                    right = ns->getNamePrefix();
                } else {
                    left = uri;
                    right = nameqn->getNamePrefix();
                }
                nameqn = NewXMLQName(cx, left, right, nameqn->getQNameLocalName());
                if (!nameqn)
                    goto bad;

                /* 13(b)(iii). */
                vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
                if (!vobj)
                    goto bad;
                vxml = (JSXML *) vobj->getPrivate();
                vxml->parent = xml;
                vxml->name = nameqn;

                /* 13(b)(iv-vi). */
                ns = GetNamespace(cx, nameqn, NULL);
                if (!ns)
                    goto bad;
                ok = Replace(cx, xml, matchIndex, OBJECT_TO_JSVAL(vobj));
                if (!ok)
                    goto out;
                ok = AddInScopeNamespace(cx, vxml, ns);
                if (!ok)
                    goto out;
            }
        }

        /* 14. */
        if (primitiveAssign) {
            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
            cursor.index = matchIndex;
            kid = cursor.getCurrent();
            if (JSXML_HAS_KIDS(kid)) {
                kid->xml_kids.finish(cx->runtime->defaultFreeOp());
                kid->xml_kids.init();
                ok = kid->xml_kids.setCapacity(cx, 1);
            }

            /* 14(b-c). */
            /* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
            if (ok) {
                ok = JS_ConvertValue(cx, vp, JSTYPE_STRING, vp.address());
                if (ok && !JSVAL_TO_STRING(vp)->empty()) {
                    roots[VAL_ROOT] = vp;
                    if (cursor.getCurrent() == kid)
                        ok = Replace(cx, kid, 0, vp);
                }
            }
        } else {
            /* 15(a). */
            ok = Replace(cx, xml, matchIndex, vp);
        }
    }

out:
    return ok;

type_error:
    {
        JSAutoByteString bytes;
        if (js_ValueToPrintable(cx, IdToValue(id), &bytes))
            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XMLLIST_PUT, bytes.ptr());
    }
bad:
    ok = JS_FALSE;
    goto out;
}

/* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
static JSBool
ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
{
    JSXML *target, *base;
    JSObject *targetprop;
    RootedValue tv(cx);

    if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
        if (!js_GetXMLObject(cx, list))
            return JS_FALSE;
        *result = list;
        return JS_TRUE;
    }

    target = list->xml_target;
    targetprop = list->xml_targetprop;
    if (!target || !targetprop || IS_STAR(targetprop->getQNameLocalName())) {
        *result = NULL;
        return JS_TRUE;
    }

    if (targetprop->getClass() == &AttributeNameClass) {
        *result = NULL;
        return JS_TRUE;
    }

    if (!ResolveValue(cx, target, &base))
        return JS_FALSE;
    if (!base) {
        *result = NULL;
        return JS_TRUE;
    }
    if (!js_GetXMLObject(cx, base))
        return JS_FALSE;

    RootedId id(cx, OBJECT_TO_JSID(targetprop));
    Rooted<JSObject*> baseObj(cx, base->object);
    if (!GetProperty(cx, baseObj, id, &tv))
        return JS_FALSE;
    target = (JSXML *) JSVAL_TO_OBJECT(tv)->getPrivate();

    if (JSXML_LENGTH(target) == 0) {
        if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
            *result = NULL;
            return JS_TRUE;
        }
        tv = STRING_TO_JSVAL(cx->runtime->emptyString);
        if (!PutProperty(cx, baseObj, id, false, &tv))
            return JS_FALSE;
        if (!GetProperty(cx, baseObj, id, &tv))
            return JS_FALSE;
        target = (JSXML *) JSVAL_TO_OBJECT(tv)->getPrivate();
    }

    *result = target;
    return JS_TRUE;
}

static JSBool
HasNamedProperty(JSXML *xml, JSObject *nameqn)
{
    JSBool found;
    JSXMLArray<JSXML> *array;
    JSXMLNameMatcher matcher;
    uint32_t i, n;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        found = JS_FALSE;
        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
        while (JSXML *kid = cursor.getNext()) {
            found = HasNamedProperty(kid, nameqn);
            if (found)
                break;
        }
        return found;
    }

    if (xml->xml_class == JSXML_CLASS_ELEMENT) {
        if (nameqn->getClass() == &AttributeNameClass) {
            array = &xml->xml_attrs;
            matcher = MatchAttrName;
        } else {
            array = &xml->xml_kids;
            matcher = MatchElemName;
        }
        for (i = 0, n = array->length; i < n; i++) {
            JSXML *kid = XMLARRAY_MEMBER(array, i, JSXML);
            if (kid && matcher(nameqn, kid))
                return JS_TRUE;
        }
    }

    return JS_FALSE;
}

static JSBool
HasIndexedProperty(JSXML *xml, uint32_t i)
{
    if (xml->xml_class == JSXML_CLASS_LIST)
        return i < JSXML_LENGTH(xml);

    if (xml->xml_class == JSXML_CLASS_ELEMENT)
        return i == 0;

    return JS_FALSE;
}

static JSBool
HasSimpleContent(JSXML *xml);

static JSBool
HasFunctionProperty(JSContext *cx, JSObject *obj_, jsid funid_, JSBool *found)
{
    JSXML *xml;

    JS_ASSERT(obj_->getClass() == &XMLClass);

    RootedId funid(cx, funid_);

    Rooted<JSObject*> obj(cx, obj_);
    RootedObject pobj(cx);
    RootedShape prop(cx);
    if (!baseops::LookupProperty(cx, obj, funid, &pobj, &prop))
        return false;
    if (!prop) {
        xml = (JSXML *) obj->getPrivate();
        if (HasSimpleContent(xml)) {
            /*
             * Search in String.prototype to set found whenever
             * GetXMLFunction returns existing function.
             */
            RootedObject proto(cx, obj->global().getOrCreateStringPrototype(cx));
            if (!proto)
                return false;

            if (!baseops::LookupProperty(cx, proto, funid, &pobj, &prop))
                return false;
        }
    }
    *found = (prop != NULL);
    return true;
}

static bool
IdValIsIndex(JSContext *cx, jsval id, uint32_t *indexp, bool *isIndex)
{
    if (JSVAL_IS_INT(id)) {
        int32_t i = JSVAL_TO_INT(id);
        if (i < 0) {
            *isIndex = false;
            return true;
        }
        *indexp = (uint32_t)i;
        *isIndex = true;
        return true;
    }

    if (!JSVAL_IS_STRING(id)) {
        *isIndex = false;
        return true;
    }

    JSLinearString *str = JSVAL_TO_STRING(id)->ensureLinear(cx);
    if (!str)
        return false;

    *isIndex = StringIsArrayIndex(str, indexp);
    return true;
}

/* ECMA-357 9.1.1.6 XML [[HasProperty]] and 9.2.1.5 XMLList [[HasProperty]]. */
static JSBool
HasProperty(JSContext *cx, JSObject *obj, jsval id, JSBool *found)
{
    JSXML *xml;
    bool isIndex;
    uint32_t i;
    JSObject *qn;
    jsid funid;

    xml = (JSXML *) obj->getPrivate();
    if (!IdValIsIndex(cx, id, &i, &isIndex))
        return JS_FALSE;

    if (isIndex) {
        *found = HasIndexedProperty(xml, i);
    } else {
        qn = ToXMLName(cx, id, &funid);
        if (!qn)
            return JS_FALSE;
        if (!JSID_IS_VOID(funid)) {
            if (!HasFunctionProperty(cx, obj, funid, found))
                return JS_FALSE;
        } else {
            *found = HasNamedProperty(xml, qn);
        }
    }
    return JS_TRUE;
}

/*
 * XML objects are native. Thus xml_lookupGeneric must return a valid
 * Shape pointer parameter via *propp to signify "property found". Since the
 * only call to xml_lookupGeneric is via JSObject::lookupGeneric, and then
 * only from js_FindProperty (in jsobj.c, called from jsinterp.c) or from
 * JSOP_IN case in the interpreter, the only time we add a Shape here is when
 * an unqualified name is being accessed or when "name in xml" is called.
 *
 * This scope property keeps the JSOP_NAME code in js_Interpret happy by
 * giving it an shape with (getter, setter) == (GetProperty, PutProperty).
 *
 * NB: xml_deleteProperty must take care to remove any property added here.
 *
 * FIXME This clashes with the function namespace implementation which also
 * uses native properties. Effectively after xml_lookupGeneric any property
 * stored previously using assignments to xml.function::name will be removed.
 * We partially workaround the problem in GetXMLFunction. There we take
 * advantage of the fact that typically function:: is used to access the
 * functions from XML.prototype. So when js_GetProperty returns a non-function
 * property, we assume that it represents the result of GetProperty setter
 * hiding the function and use an extra prototype chain lookup to recover it.
 * For a proper solution see bug 355257.
*/
static JSBool
xml_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
                  MutableHandleObject objp, MutableHandleShape propp)
{
    JSBool found;
    JSXML *xml;
    uint32_t i;
    JSObject *qn;

    RootedId funid(cx);

    xml = (JSXML *) obj->getPrivate();
    if (js_IdIsIndex(id, &i)) {
        found = HasIndexedProperty(xml, i);
    } else {
        qn = ToXMLName(cx, IdToJsval(id), funid.address());
        if (!qn)
            return JS_FALSE;
        if (!JSID_IS_VOID(funid))
            return baseops::LookupProperty(cx, obj, funid, objp, propp);
        found = HasNamedProperty(xml, qn);
    }
    if (!found) {
        objp.set(NULL);
        propp.set(NULL);
    } else {
        RootedShape shape(cx, js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
                                                   SHAPE_INVALID_SLOT, JSPROP_ENUMERATE,
                                                   0, 0));
        if (!shape)
            return JS_FALSE;

        objp.set(obj);
        propp.set(shape);
    }
    return JS_TRUE;
}

static JSBool
xml_lookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
                   MutableHandleObject objp, MutableHandleShape propp)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_lookupGeneric(cx, obj, id, objp, propp);
}

static JSBool
xml_lookupElement(JSContext *cx, HandleObject obj, uint32_t index, MutableHandleObject objp,
                  MutableHandleShape propp)
{
    JSXML *xml = reinterpret_cast<JSXML *>(obj->getPrivate());
    if (!HasIndexedProperty(xml, index)) {
        objp.set(NULL);
        propp.set(NULL);
        return true;
    }

    RootedId id(cx);
    if (!IndexToId(cx, index, id.address()))
        return false;

    RootedShape shape(cx, js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
                                               SHAPE_INVALID_SLOT, JSPROP_ENUMERATE, 0, 0));
    if (!shape)
        return false;

    objp.set(obj);
    propp.set(shape);
    return true;
}

static JSBool
xml_lookupSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid,
                  MutableHandleObject objp, MutableHandleShape propp)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_lookupGeneric(cx, obj, id, objp, propp);
}

static JSBool
xml_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
                  PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
    if (IsFunctionObject(v) || getter || setter ||
        (attrs & JSPROP_ENUMERATE) == 0 ||
        (attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
        return baseops::DefineGeneric(cx, obj, id, v, getter, setter, attrs);
    }

    RootedValue tmp(cx, v);
    return PutProperty(cx, obj, id, false, &tmp);
}

static JSBool
xml_defineProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue v,
                   PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_defineGeneric(cx, obj, id, v, getter, setter, attrs);
}

static JSBool
xml_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v,
                  PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
    RootedId id(cx);
    if (!IndexToId(cx, index, id.address()))
        return false;
    return xml_defineGeneric(cx, obj, id, v, getter, setter, attrs);
}

static JSBool
xml_defineSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, HandleValue v,
                  PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_defineGeneric(cx, obj, id, v, getter, setter, attrs);
}

static JSBool
xml_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id,
               MutableHandleValue vp)
{
    if (JSID_IS_DEFAULT_XML_NAMESPACE(id)) {
        vp.setUndefined();
        return JS_TRUE;
    }

    return GetProperty(cx, obj, id, vp);
}

static JSBool
xml_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name,
                MutableHandleValue vp)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_getGeneric(cx, obj, receiver, id, vp);
}

static JSBool
xml_getElement(JSContext *cx, HandleObject obj, HandleObject receiver, uint32_t index, MutableHandleValue vp)
{
    RootedId id(cx);
    if (!IndexToId(cx, index, id.address()))
        return false;
    return xml_getGeneric(cx, obj, receiver, id, vp);
}

static JSBool
xml_getSpecial(JSContext *cx, HandleObject obj, HandleObject receiver, HandleSpecialId sid, MutableHandleValue vp)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_getGeneric(cx, obj, receiver, id, vp);
}

static JSBool
xml_setGeneric(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp, JSBool strict)
{
    return PutProperty(cx, obj, id, strict, vp);
}

static JSBool
xml_setProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, MutableHandleValue vp, JSBool strict)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_setGeneric(cx, obj, id, vp, strict);
}

static JSBool
xml_setElement(JSContext *cx, HandleObject obj, uint32_t index, MutableHandleValue vp, JSBool strict)
{
    RootedId id(cx);
    if (!IndexToId(cx, index, id.address()))
        return false;
    return xml_setGeneric(cx, obj, id, vp, strict);
}

static JSBool
xml_setSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, MutableHandleValue vp, JSBool strict)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_setGeneric(cx, obj, id, vp, strict);
}

static JSBool
xml_getGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp)
{
    JSBool found;
    if (!HasProperty(cx, obj, IdToJsval(id), &found))
        return false;

    *attrsp = found ? JSPROP_ENUMERATE : 0;
    return JS_TRUE;
}

static JSBool
xml_getPropertyAttributes(JSContext *cx, HandleObject obj, HandlePropertyName name, unsigned *attrsp)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_getGenericAttributes(cx, obj, id, attrsp);
}

static JSBool
xml_getElementAttributes(JSContext *cx, HandleObject obj, uint32_t index, unsigned *attrsp)
{
    RootedId id(cx);
    if (!IndexToId(cx, index, id.address()))
        return false;
    return xml_getGenericAttributes(cx, obj, id, attrsp);
}

static JSBool
xml_getSpecialAttributes(JSContext *cx, HandleObject obj, HandleSpecialId sid, unsigned *attrsp)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_getGenericAttributes(cx, obj, id, attrsp);
}

static JSBool
xml_setGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp)
{
    JSBool found;
    if (!HasProperty(cx, obj, IdToJsval(id), &found))
        return false;

    if (found) {
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                             JSMSG_CANT_SET_XML_ATTRS);
        return false;
    }
    return true;
}

static JSBool
xml_setPropertyAttributes(JSContext *cx, HandleObject obj, HandlePropertyName name, unsigned *attrsp)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_setGenericAttributes(cx, obj, id, attrsp);
}

static JSBool
xml_setElementAttributes(JSContext *cx, HandleObject obj, uint32_t index, unsigned *attrsp)
{
    RootedId id(cx);
    if (!IndexToId(cx, index, id.address()))
        return false;
    return xml_setGenericAttributes(cx, obj, id, attrsp);
}

static JSBool
xml_setSpecialAttributes(JSContext *cx, HandleObject obj, HandleSpecialId sid, unsigned *attrsp)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_setGenericAttributes(cx, obj, id, attrsp);
}

static JSBool
xml_deleteGeneric(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue rval, JSBool strict)
{
    uint32_t index;
    JSObject *nameqn;
    RootedId funid(cx);

    Value idval = IdToValue(id);
    JSXML *xml = (JSXML *) obj->getPrivate();
    if (js_IdIsIndex(id, &index)) {
        if (xml->xml_class != JSXML_CLASS_LIST) {
            /* See NOTE in spec: this variation is reserved for future use. */
            ReportBadXMLName(cx, IdToValue(id));
            return false;
        }

        /* ECMA-357 9.2.1.3. */
        DeleteListElement(cx, xml, index);
    } else {
        nameqn = ToXMLName(cx, idval, funid.address());
        if (!nameqn)
            return false;
        if (!JSID_IS_VOID(funid))
            return baseops::DeleteGeneric(cx, obj, funid, rval, false);

        DeleteNamedProperty(cx, xml, nameqn,
                            nameqn->getClass() == &AttributeNameClass);
    }

    /*
     * If this object has its own (mutable) scope,  then we may have added a
     * property to the scope in xml_lookupGeneric for it to return to mean
     * "found" and to provide a handle for access operations to call the
     * property's getter or setter. But now it's time to remove any such
     * property, to purge the property cache and remove the scope entry.
     */
    if (!obj->nativeEmpty() && !baseops::DeleteGeneric(cx, obj, id, rval, false))
        return false;

    rval.setBoolean(true);
    return true;
}

static JSBool
xml_deleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, MutableHandleValue rval, JSBool strict)
{
    Rooted<jsid> id(cx, NameToId(name));
    return xml_deleteGeneric(cx, obj, id, rval, strict);
}

static JSBool
xml_deleteElement(JSContext *cx, HandleObject obj, uint32_t index, MutableHandleValue rval, JSBool strict)
{
    JSXML *xml = reinterpret_cast<JSXML *>(obj->getPrivate());
    if (xml->xml_class != JSXML_CLASS_LIST) {
        /* See NOTE in spec: this variation is reserved for future use. */
        ReportBadXMLName(cx, DoubleValue(index));
        return false;
    }

    /* ECMA-357 9.2.1.3. */
    DeleteListElement(cx, xml, index);

    /*
     * If this object has its own (mutable) scope,  then we may have added a
     * property to the scope in xml_lookupGeneric for it to return to mean
     * "found" and to provide a handle for access operations to call the
     * property's getter or setter. But now it's time to remove any such
     * property, to purge the property cache and remove the scope entry.
     */
    if (!obj->nativeEmpty() && !baseops::DeleteElement(cx, obj, index, rval, false))
        return false;

    rval.setBoolean(true);
    return true;
}

static JSBool
xml_deleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, MutableHandleValue rval, JSBool strict)
{
    Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid));
    return xml_deleteGeneric(cx, obj, id, rval, strict);
}

static JSString *
xml_toString_helper(JSContext *cx, JSXML *xml);

JSBool
xml_convert(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue rval)
{
    JS_CHECK_RECURSION(cx, return false);

    JS_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
    JS_ASSERT(obj->isXML());

    JS::Anchor<JSObject *> anch(obj);
    JSString *str = xml_toString_helper(cx, reinterpret_cast<JSXML *>(obj->getPrivate()));
    if (!str)
        return false;
    rval.setString(str);
    return true;
}

static JSBool
xml_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op,
              MutableHandleValue statep, MutableHandleId idp)
{
    JSXML *xml;
    uint32_t length, index;
    JSXMLArrayCursor<JSXML> *cursor;

    xml = (JSXML *)obj->getPrivate();
    length = JSXML_LENGTH(xml);

    switch (enum_op) {
      case JSENUMERATE_INIT:
      case JSENUMERATE_INIT_ALL:
        if (length == 0) {
            statep.setInt32(0);
        } else {
            cursor = cx->new_< JSXMLArrayCursor<JSXML> >(&xml->xml_kids);
            if (!cursor)
                return JS_FALSE;
            statep.address()->setPrivate(cursor);
        }
        idp.set(INT_TO_JSID(length));
        break;

      case JSENUMERATE_NEXT:
        if (statep.address()->isInt32(0)) {
            statep.setNull();
            break;
        }
        cursor = (JSXMLArrayCursor<JSXML> *) statep.address()->toPrivate();
        if (cursor && cursor->array && (index = cursor->index) < length) {
            idp.set(INT_TO_JSID(index));
            cursor->index = index + 1;
            break;
        }
        /* FALL THROUGH */

      case JSENUMERATE_DESTROY:
        if (!statep.address()->isInt32(0)) {
            cursor = (JSXMLArrayCursor<JSXML> *) statep.address()->toPrivate();
            if (cursor)
                js_delete(cursor);
        }
        statep.setNull();
        break;
    }
    return JS_TRUE;
}

static JSType
xml_typeOf(JSContext *cx, HandleObject obj)
{
    return JSTYPE_XML;
}

static JSBool
xml_hasInstance(JSContext *cx, HandleObject obj, MutableHandleValue v, JSBool *bp)
{
    return JS_TRUE;
}

static void
xml_trace(JSTracer *trc, RawObject obj)
{
    JSXML *xml = (JSXML *) obj->getPrivate();
    /*
     * This is safe to leave Unbarriered for incremental GC, but we'll need
     * to fix somehow for generational.
     */
    if (xml) {
        MarkXMLUnbarriered(trc, &xml, "private");
        JS_ASSERT(xml == obj->getPrivate());
    }
}

static JSBool
HasSimpleContent(JSXML *xml)
{
    JSXML *kid;
    JSBool simple;
    uint32_t i, n;

again:
    switch (xml->xml_class) {
      case JSXML_CLASS_COMMENT:
      case JSXML_CLASS_PROCESSING_INSTRUCTION:
        return JS_FALSE;
      case JSXML_CLASS_LIST:
        if (xml->xml_kids.length == 0)
            return JS_TRUE;
        if (xml->xml_kids.length == 1) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
            if (kid) {
                xml = kid;
                goto again;
            }
        }
        /* FALL THROUGH */
      default:
        simple = JS_TRUE;
        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
                simple = JS_FALSE;
                break;
            }
        }
        return simple;
    }
}

/*
 * 11.2.2.1 Step 3(d) onward.
 */
JSBool
js_GetXMLMethod(JSContext *cx, HandleObject obj, jsid id_, MutableHandleValue vp)
{
    JS_ASSERT(obj->isXML());

    Rooted<jsid> id(cx, id_);
    if (JSID_IS_OBJECT(id))
        js_GetLocalNameFromFunctionQName(JSID_TO_OBJECT(id), id.address(), cx);

    return GetXMLFunction(cx, obj, id, vp);
}

JSBool
js_TestXMLEquality(JSContext *cx, const Value &v1, const Value &v2, JSBool *bp)
{
    JSXML *xml, *vxml;
    JSObject *vobj;
    JSBool ok = JS_TRUE;
    JSString *str, *vstr;
    double d, d2;

    JSObject *obj;
    jsval v;
    if (v1.isObject() && v1.toObject().isXML()) {
        obj = &v1.toObject();
        v = v2;
    } else {
        v = v1;
        obj = &v2.toObject();
    }

    JS_ASSERT(obj->isXML());

    xml = (JSXML *) obj->getPrivate();
    vxml = NULL;
    if (!JSVAL_IS_PRIMITIVE(v)) {
        vobj = JSVAL_TO_OBJECT(v);
        if (vobj->isXML())
            vxml = (JSXML *) vobj->getPrivate();
    }

    if (xml->xml_class == JSXML_CLASS_LIST) {
        ok = Equals(cx, xml, v, bp);
    } else if (vxml) {
        if (vxml->xml_class == JSXML_CLASS_LIST) {
            ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
        } else {
            if (((xml->xml_class == JSXML_CLASS_TEXT ||
                  xml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
                 HasSimpleContent(vxml)) ||
                ((vxml->xml_class == JSXML_CLASS_TEXT ||
                  vxml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
                 HasSimpleContent(xml)))
            {
                ok = (str = ToStringSlow(cx, ObjectValue(*obj))) &&
                     (vstr = ToString(cx, v));
                if (ok) {
                    bool equal;
                    ok = EqualStrings(cx, str, vstr, &equal);
                    *bp = equal;
                }
            } else {
                ok = XMLEquals(cx, xml, vxml, bp);
            }
        }
    } else {
        if (HasSimpleContent(xml)) {
            ok = (str = ToString(cx, ObjectValue(*obj))) &&
                 (vstr = ToString(cx, v));
            if (ok) {
                bool equal;
                ok = EqualStrings(cx, str, vstr, &equal);
                *bp = equal;
            }
        } else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
            str = ToString(cx, ObjectValue(*obj));
            if (!str) {
                ok = JS_FALSE;
            } else if (JSVAL_IS_STRING(v)) {
                bool equal;
                ok = EqualStrings(cx, str, JSVAL_TO_STRING(v), &equal);
                if (ok)
                    *bp = equal;
            } else {
                ok = JS_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
                if (ok) {
                    d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
                                         : JSVAL_TO_DOUBLE(v);
                    *bp = (d == d2);
                }
            }
        } else {
            *bp = JS_FALSE;
        }
    }
    return ok;
}

JSBool
js_ConcatenateXML(JSContext *cx, JSObject *obj, JSObject *robj, Value *vp)
{
    JSBool ok;
    JSObject *listobj;
    JSXML *list, *lxml, *rxml;

    JS_ASSERT(obj->isXML());

    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
    if (!listobj) {
        ok = JS_FALSE;
        goto out;
    }

    list = (JSXML *) listobj->getPrivate();
    lxml = (JSXML *) obj->getPrivate();
    ok = Append(cx, list, lxml);
    if (!ok)
        goto out;

    JS_ASSERT(robj->isXML());
    rxml = (JSXML *) robj->getPrivate();
    ok = Append(cx, list, rxml);
    if (!ok)
        goto out;

    vp->setObject(*listobj);
out:
    return ok;
}

JS_FRIEND_DATA(Class) js::XMLClass = {
    js_XML_str,
    JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
    JSCLASS_HAS_CACHED_PROTO(JSProto_XML),
    JS_PropertyStub,         /* addProperty */
    JS_PropertyStub,         /* delProperty */
    JS_PropertyStub,         /* getProperty */
    JS_StrictPropertyStub,   /* setProperty */
    JS_EnumerateStub,
    JS_ResolveStub,
    xml_convert,
    NULL,                    /* finalize    */
    NULL,                    /* checkAccess */
    NULL,                    /* call        */
    xml_hasInstance,
    NULL,                    /* construct   */
    xml_trace,
    JS_NULL_CLASS_EXT,
    {
        xml_lookupGeneric,
        xml_lookupProperty,
        xml_lookupElement,
        xml_lookupSpecial,
        xml_defineGeneric,
        xml_defineProperty,
        xml_defineElement,
        xml_defineSpecial,
        xml_getGeneric,
        xml_getProperty,
        xml_getElement,
        NULL, /* getElementIfPresent */
        xml_getSpecial,
        xml_setGeneric,
        xml_setProperty,
        xml_setElement,
        xml_setSpecial,
        xml_getGenericAttributes,
        xml_getPropertyAttributes,
        xml_getElementAttributes,
        xml_getSpecialAttributes,
        xml_setGenericAttributes,
        xml_setPropertyAttributes,
        xml_setElementAttributes,
        xml_setSpecialAttributes,
        xml_deleteProperty,
        xml_deleteElement,
        xml_deleteSpecial,
        xml_enumerate,
        xml_typeOf,
        NULL,       /* thisObject     */
    }
};

static JSXML *
StartNonListXMLMethod(JSContext *cx, jsval *vp, MutableHandleObject objp)
{
    JSXML *xml;
    JSFunction *fun;
    char numBuf[12];

    JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
    JS_ASSERT(JSVAL_TO_OBJECT(*vp)->isFunction());

    objp.set(ToObject(cx, HandleValue::fromMarkedLocation(&vp[1])));
    if (!objp)
        return NULL;
    if (!objp->isXML()) {
        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &XMLClass);
        return NULL;
    }
    xml = (JSXML *) objp->getPrivate();
    if (!xml || xml->xml_class != JSXML_CLASS_LIST)
        return xml;

    if (xml->xml_kids.length == 1) {
        xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
        if (xml) {
            objp.set(js_GetXMLObject(cx, xml));
            if (!objp)
                return NULL;
            vp[1] = OBJECT_TO_JSVAL(objp);
            return xml;
        }
    }

    fun = JSVAL_TO_OBJECT(*vp)->toFunction();
    JS_snprintf(numBuf, sizeof numBuf, "%u", xml->xml_kids.length);
    JSAutoByteString funNameBytes;
    if (const char *funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NON_LIST_XML_METHOD,
                             funName, numBuf);
    }
    return NULL;
}

/* Beware: these two are not bracketed by JS_BEGIN/END_MACRO. */
#define XML_METHOD_PROLOG                                                     \
    JSObject *obj = ToObject(cx, HandleValue::fromMarkedLocation(&vp[1]));                                     \
    if (!obj)                                                                 \
        return JS_FALSE;                                                      \
    if (!obj->isXML()) {                                                      \
        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &XMLClass);      \
        return JS_FALSE;                                                      \
    }                                                                         \
    JSXML *xml = (JSXML *)obj->getPrivate();                                  \
    if (!xml)                                                                 \
        return JS_FALSE

#define NON_LIST_XML_METHOD_PROLOG                                            \
    RootedObject obj(cx);                                                     \
    JSXML *xml = StartNonListXMLMethod(cx, vp, &obj);                         \
    if (!xml)                                                                 \
        return JS_FALSE;                                                      \
    JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST)

static JSBool
xml_addNamespace(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *ns;

    NON_LIST_XML_METHOD_PROLOG;
    if (xml->xml_class != JSXML_CLASS_ELEMENT)
        goto done;
    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;

    if (!NamespaceHelper(cx, argc == 0 ? -1 : 1, vp + 2, vp))
        return JS_FALSE;
    JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));

    ns = JSVAL_TO_OBJECT(*vp);
    if (!AddInScopeNamespace(cx, xml, ns))
        return JS_FALSE;
    ns->setNamespaceDeclared(JSVAL_TRUE);

  done:
    *vp = OBJECT_TO_JSVAL(obj);
    return JS_TRUE;
}

static JSBool
xml_appendChild(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;
    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;

    RootedId name(cx);
    if (!js_GetAnyName(cx, name.address()))
        return JS_FALSE;

    RootedValue v(cx);
    if (!GetProperty(cx, obj, name, &v))
        return JS_FALSE;

    JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
    Rooted<JSObject*> vobj(cx, &v.toObject());
    JS_ASSERT(vobj->isXML());
    JSXML *vxml = (JSXML *) vobj->getPrivate();
    JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);

    if (!IndexToId(cx, vxml->xml_kids.length, name.address()))
        return JS_FALSE;
    *vp = (argc != 0) ? vp[2] : JSVAL_VOID;

    if (!PutProperty(cx, vobj, name, false, MutableHandleValue::fromMarkedLocation(vp)))
        return JS_FALSE;

    *vp = OBJECT_TO_JSVAL(obj);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_attribute(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *qn;

    if (argc == 0) {
        RootedValue val(cx, *vp);
        js_ReportMissingArg(cx, val, 0);
        return JS_FALSE;
    }

    qn = ToAttributeName(cx, vp[2]);
    if (!qn)
        return JS_FALSE;
    vp[2] = OBJECT_TO_JSVAL(qn);        /* local root */

    RootedId id(cx, OBJECT_TO_JSID(qn));
    RootedObject obj(cx, ToObject(cx, HandleValue::fromMarkedLocation(&vp[1])));
    if (!obj)
        return JS_FALSE;
    return GetProperty(cx, obj, id, MutableHandleValue::fromMarkedLocation(vp));
}

/* XML and XMLList */
static JSBool
xml_attributes(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval name = STRING_TO_JSVAL(cx->names().star);
    JSObject *qn = ToAttributeName(cx, name);
    if (!qn)
        return JS_FALSE;

    RootedId id(cx, OBJECT_TO_JSID(qn));
    RootedObject obj(cx, ToObject(cx, HandleValue::fromMarkedLocation(&vp[1])));
    if (!obj)
        return JS_FALSE;
    return GetProperty(cx, obj, id, MutableHandleValue::fromMarkedLocation(vp));
}

static JSXML *
xml_list_helper(JSContext *cx, JSXML *xml, jsval *rval)
{
    JSObject *listobj;
    JSXML *list;

    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
    if (!listobj)
        return NULL;

    *rval = OBJECT_TO_JSVAL(listobj);
    list = (JSXML *) listobj->getPrivate();
    list->xml_target = xml;
    return list;
}

static JSBool
ValueToIdForXML(JSContext *cx, jsval v, jsid *idp)
{
    if (JSVAL_IS_INT(v)) {
        int32_t i = JSVAL_TO_INT(v);
        if (INT_FITS_IN_JSID(i))
            *idp = INT_TO_JSID(i);
        else if (!ValueToId(cx, v, idp))
            return JS_FALSE;
    } else if (JSVAL_IS_STRING(v)) {
        JSAtom *atom = AtomizeString(cx, JSVAL_TO_STRING(v));
        if (!atom)
            return JS_FALSE;
        *idp = AtomToId(atom);
    } else if (!JSVAL_IS_PRIMITIVE(v)) {
        *idp = OBJECT_TO_JSID(JSVAL_TO_OBJECT(v));
    } else {
        ReportBadXMLName(cx, v);
        return JS_FALSE;
    }
    return JS_TRUE;
}

static JSBool
xml_child_helper(JSContext *cx, JSObject *obj_, JSXML *xml, jsval name,
                 MutableHandleValue rval)
{
    bool isIndex;
    uint32_t index;
    JSXML *kid;
    JSObject *kidobj;

    /* ECMA-357 13.4.4.6 */
    JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);

    Rooted<JSObject*> obj(cx, obj_);

    if (!IdValIsIndex(cx, name, &index, &isIndex))
        return JS_FALSE;

    if (isIndex) {
        if (index >= JSXML_LENGTH(xml)) {
            rval.setUndefined();
        } else {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
            if (!kid) {
                rval.setUndefined();
            } else {
                kidobj = js_GetXMLObject(cx, kid);
                if (!kidobj)
                    return JS_FALSE;
                rval.set(OBJECT_TO_JSVAL(kidobj));
            }
        }
        return JS_TRUE;
    }

    RootedId id(cx);
    if (!ValueToIdForXML(cx, name, id.address()))
        return JS_FALSE;

    return GetProperty(cx, obj, id, rval);
}

/* XML and XMLList */
static JSBool
xml_child(JSContext *cx, unsigned argc, jsval *vp)
{
    RootedValue v(cx);
    JSXML *list, *vxml;
    JSObject *kidobj;

    XML_METHOD_PROLOG;
    jsval name = argc != 0 ? vp[2] : JSVAL_VOID;
    if (xml->xml_class == JSXML_CLASS_LIST) {
        /* ECMA-357 13.5.4.4 */
        list = xml_list_helper(cx, xml, vp);
        if (!list)
            return JS_FALSE;

        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
        while (JSXML *kid = cursor.getNext()) {
            kidobj = js_GetXMLObject(cx, kid);
            if (!kidobj)
                return JS_FALSE;
            if (!xml_child_helper(cx, kidobj, kid, name, &v))
                return JS_FALSE;
            if (JSVAL_IS_VOID(v)) {
                /* The property didn't exist in this kid. */
                continue;
            }

            JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
            vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
            if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
                !Append(cx, list, vxml)) {
                return JS_FALSE;
            }
        }
        return JS_TRUE;
    }

    /* ECMA-357 Edition 2 13.3.4.6 (note 13.3, not 13.4 as in Edition 1). */
    if (!xml_child_helper(cx, obj, xml, name, MutableHandleValue::fromMarkedLocation(vp)))
        return JS_FALSE;
    if (JSVAL_IS_VOID(*vp) && !xml_list_helper(cx, xml, vp))
        return JS_FALSE;
    return JS_TRUE;
}

static JSBool
xml_childIndex(JSContext *cx, unsigned argc, jsval *vp)
{
    JSXML *parent;
    uint32_t i, n;

    NON_LIST_XML_METHOD_PROLOG;
    parent = xml->parent;
    if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
        *vp = DOUBLE_TO_JSVAL(js_NaN);
        return JS_TRUE;
    }
    for (i = 0, n = JSXML_LENGTH(parent); i < n; i++) {
        if (XMLARRAY_MEMBER(&parent->xml_kids, i, JSXML) == xml)
            break;
    }
    JS_ASSERT(i < n);
    if (i <= JSVAL_INT_MAX)
        *vp = INT_TO_JSVAL(i);
    else
        *vp = DOUBLE_TO_JSVAL(i);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_children(JSContext *cx, unsigned argc, jsval *vp)
{
    RootedObject obj(cx, ToObject(cx, HandleValue::fromMarkedLocation(&vp[1])));
    if (!obj)
        return false;
    RootedId name(cx, NameToId(cx->names().star));
    return GetProperty(cx, obj, name, MutableHandleValue::fromMarkedLocation(vp));
}

/* XML and XMLList */
static JSBool
xml_comments_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval *vp)
{
    JSXML *list, *kid, *vxml;
    JSBool ok;
    uint32_t i, n;
    JSObject *kidobj;
    jsval v;

    list = xml_list_helper(cx, xml, vp);
    if (!list)
        return JS_FALSE;

    ok = JS_TRUE;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        /* 13.5.4.6 Step 2. */
        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
                kidobj = js_GetXMLObject(cx, kid);
                if (kidobj) {
                    ok = xml_comments_helper(cx, kidobj, kid, &v);
                } else {
                    ok = JS_FALSE;
                    v = JSVAL_NULL;
                }
                if (!ok)
                    break;
                vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
                if (JSXML_LENGTH(vxml) != 0) {
                    ok = Append(cx, list, vxml);
                    if (!ok)
                        break;
                }
            }
        }
    } else {
        /* 13.4.4.9 Step 2. */
        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_COMMENT) {
                ok = Append(cx, list, kid);
                if (!ok)
                    break;
            }
        }
    }

    return ok;
}

static JSBool
xml_comments(JSContext *cx, unsigned argc, jsval *vp)
{
    XML_METHOD_PROLOG;
    return xml_comments_helper(cx, obj, xml, vp);
}

/* XML and XMLList */
static JSBool
xml_contains(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval value;
    JSBool eq;
    JSObject *kidobj;

    XML_METHOD_PROLOG;
    value = argc != 0 ? vp[2] : JSVAL_VOID;
    if (xml->xml_class == JSXML_CLASS_LIST) {
        eq = JS_FALSE;
        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
        while (JSXML *kid = cursor.getNext()) {
            kidobj = js_GetXMLObject(cx, kid);
            if (!kidobj || !js_TestXMLEquality(cx, ObjectValue(*kidobj), value, &eq))
                return JS_FALSE;
            if (eq)
                break;
        }
    } else {
        if (!js_TestXMLEquality(cx, ObjectValue(*obj), value, &eq))
            return JS_FALSE;
    }
    *vp = BOOLEAN_TO_JSVAL(eq);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_copy(JSContext *cx, unsigned argc, jsval *vp)
{
    JSXML *copy;

    XML_METHOD_PROLOG;
    copy = DeepCopy(cx, xml, NULL, 0);
    if (!copy)
        return JS_FALSE;
    *vp = OBJECT_TO_JSVAL(copy->object);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_descendants(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval name;
    JSXML *list;

    XML_METHOD_PROLOG;
    name = argc == 0 ? STRING_TO_JSVAL(cx->names().star) : vp[2];
    list = Descendants(cx, xml, name);
    if (!list)
        return JS_FALSE;
    *vp = OBJECT_TO_JSVAL(list->object);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_elements_helper(JSContext *cx, JSObject *obj, JSXML *xml,
                    JSObject *nameqn, jsval *vp)
{
    JSXML *list, *vxml;
    jsval v;
    JSBool ok;
    JSObject *kidobj;
    uint32_t i, n;

    list = xml_list_helper(cx, xml, vp);
    if (!list)
        return JS_FALSE;

    list->xml_targetprop = nameqn;
    ok = JS_TRUE;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        /* 13.5.4.6 */
        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
        while (JSXML *kid = cursor.getNext()) {
            if (kid->xml_class == JSXML_CLASS_ELEMENT) {
                kidobj = js_GetXMLObject(cx, kid);
                if (kidobj) {
                    ok = xml_elements_helper(cx, kidobj, kid, nameqn, &v);
                } else {
                    ok = JS_FALSE;
                    v = JSVAL_NULL;
                }
                if (!ok)
                    break;
                vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
                if (JSXML_LENGTH(vxml) != 0) {
                    ok = Append(cx, list, vxml);
                    if (!ok)
                        break;
                }
            }
        }
    } else {
        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
            JSXML *kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT &&
                MatchElemName(nameqn, kid)) {
                ok = Append(cx, list, kid);
                if (!ok)
                    break;
            }
        }
    }

    return ok;
}

static JSBool
xml_elements(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval name;
    JSObject *nameqn;
    jsid funid;

    XML_METHOD_PROLOG;

    name = (argc == 0) ? STRING_TO_JSVAL(cx->names().star) : vp[2];
    nameqn = ToXMLName(cx, name, &funid);
    if (!nameqn)
        return JS_FALSE;

    if (!JSID_IS_VOID(funid))
        return xml_list_helper(cx, xml, vp) != NULL;

    return xml_elements_helper(cx, obj, xml, nameqn, vp);
}

/* XML and XMLList */
static JSBool
xml_hasOwnProperty(JSContext *cx, unsigned argc, jsval *vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    RootedObject obj(cx, ToObject(cx, args.thisv()));
    if (!obj)
        return false;
    if (!obj->isXML()) {
        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &XMLClass);
        return false;
    }

    Value name = args.length() != 0 ? args[0] : UndefinedValue();
    JSBool found;
    if (!HasProperty(cx, obj, name, &found))
        return false;
    if (found) {
        args.rval().setBoolean(true);
        return true;
    }

    RootedId id(cx);
    if (!ValueToId(cx, name, id.address()))
        return false;

    RootedObject obj2(cx);
    RootedShape prop(cx);
    if (!js_HasOwnProperty(cx, baseops::LookupProperty, obj, id, &obj2, &prop))
        return false;
    args.rval().setBoolean(!!prop);
    return true;
}

/* XML and XMLList */
static JSBool
xml_hasComplexContent(JSContext *cx, unsigned argc, jsval *vp)
{
    JSXML *kid;
    JSObject *kidobj;
    uint32_t i, n;

    XML_METHOD_PROLOG;
again:
    switch (xml->xml_class) {
      case JSXML_CLASS_ATTRIBUTE:
      case JSXML_CLASS_COMMENT:
      case JSXML_CLASS_PROCESSING_INSTRUCTION:
      case JSXML_CLASS_TEXT:
        *vp = JSVAL_FALSE;
        break;
      case JSXML_CLASS_LIST:
        if (xml->xml_kids.length == 0) {
            *vp = JSVAL_TRUE;
        } else if (xml->xml_kids.length == 1) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
            if (kid) {
                kidobj = js_GetXMLObject(cx, kid);
                if (!kidobj)
                    return JS_FALSE;
                obj = kidobj;
                xml = (JSXML *) obj->getPrivate();
                goto again;
            }
        }
        /* FALL THROUGH */
      default:
        *vp = JSVAL_FALSE;
        for (i = 0, n = xml->xml_kids.length; i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
                *vp = JSVAL_TRUE;
                break;
            }
        }
        break;
    }
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_hasSimpleContent(JSContext *cx, unsigned argc, jsval *vp)
{
    XML_METHOD_PROLOG;
    *vp = BOOLEAN_TO_JSVAL(HasSimpleContent(xml));
    return JS_TRUE;
}

static JSBool
FindInScopeNamespaces(JSContext *cx, JSXML *xml, JSXMLArray<JSObject> *nsarray)
{
    uint32_t length, i, j, n;
    JSObject *ns, *ns2;
    JSLinearString *prefix, *prefix2;

    length = nsarray->length;
    do {
        if (xml->xml_class != JSXML_CLASS_ELEMENT)
            continue;
        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
            ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
            if (!ns)
                continue;

            prefix = ns->getNamePrefix();
            for (j = 0; j < length; j++) {
                ns2 = XMLARRAY_MEMBER(nsarray, j, JSObject);
                if (ns2) {
                    prefix2 = ns2->getNamePrefix();
                    if ((prefix2 && prefix)
                        ? EqualStrings(prefix2, prefix)
                        : EqualStrings(ns2->getNameURI(), ns->getNameURI())) {
                        break;
                    }
                }
            }

            if (j == length) {
                if (!XMLARRAY_APPEND(cx, nsarray, ns))
                    return JS_FALSE;
                ++length;
            }
        }
    } while ((xml = xml->parent) != NULL);
    JS_ASSERT(length == nsarray->length);

    return JS_TRUE;
}

/*
 * Populate a new JS array with elements of array and place the result into
 * rval.  rval must point to a rooted location.
 */
static bool
NamespacesToJSArray(JSContext *cx, JSXMLArray<JSObject> *array, jsval *rval)
{
    Rooted<JSObject*> arrayobj(cx, NewDenseEmptyArray(cx));
    if (!arrayobj)
        return false;
    *rval = OBJECT_TO_JSVAL(arrayobj);

    RootedValue v(cx);
    for (uint32_t i = 0, n = array->length; i < n; i++) {
        JSObject *ns = XMLARRAY_MEMBER(array, i, JSObject);
        if (!ns)
            continue;
        v.setObject(*ns);
        if (!JSObject::setElement(cx, arrayobj, arrayobj, i, &v, false))
            return false;
    }
    return true;
}

static JSBool
xml_inScopeNamespaces(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;

    AutoNamespaceArray namespaces(cx);
    return FindInScopeNamespaces(cx, xml, &namespaces.array) &&
           NamespacesToJSArray(cx, &namespaces.array, vp);
}

static JSBool
xml_insertChildAfter(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval arg;
    JSXML *kid;
    uint32_t i;

    NON_LIST_XML_METHOD_PROLOG;
    *vp = OBJECT_TO_JSVAL(obj);
    if (!JSXML_HAS_KIDS(xml) || argc == 0)
        return JS_TRUE;

    arg = vp[2];
    if (JSVAL_IS_NULL(arg)) {
        kid = NULL;
        i = 0;
    } else {
        if (!VALUE_IS_XML(arg))
            return JS_TRUE;
        kid = (JSXML *) JSVAL_TO_OBJECT(arg)->getPrivate();
        i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, pointer_match);
        if (i == XML_NOT_FOUND)
            return JS_TRUE;
        ++i;
    }

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;
    return Insert(cx, xml, i, argc >= 2 ? vp[3] : JSVAL_VOID);
}

static JSBool
xml_insertChildBefore(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval arg;
    JSXML *kid;
    uint32_t i;

    NON_LIST_XML_METHOD_PROLOG;
    *vp = OBJECT_TO_JSVAL(obj);
    if (!JSXML_HAS_KIDS(xml) || argc == 0)
        return JS_TRUE;

    arg = vp[2];
    if (JSVAL_IS_NULL(arg)) {
        kid = NULL;
        i = xml->xml_kids.length;
    } else {
        if (!VALUE_IS_XML(arg))
            return JS_TRUE;
        kid = (JSXML *) JSVAL_TO_OBJECT(arg)->getPrivate();
        i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, pointer_match);
        if (i == XML_NOT_FOUND)
            return JS_TRUE;
    }

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;
    return Insert(cx, xml, i, argc >= 2 ? vp[3] : JSVAL_VOID);
}

/* XML and XMLList */
static JSBool
xml_length(JSContext *cx, unsigned argc, jsval *vp)
{
    XML_METHOD_PROLOG;
    if (xml->xml_class != JSXML_CLASS_LIST) {
        *vp = JSVAL_ONE;
    } else {
        uint32_t l = xml->xml_kids.length;
        if (l <= JSVAL_INT_MAX)
            *vp = INT_TO_JSVAL(l);
        else
            *vp = DOUBLE_TO_JSVAL(l);
    }
    return JS_TRUE;
}

static JSBool
xml_localName(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;
    *vp = xml->name ? xml->name->getQNameLocalNameVal() : JSVAL_NULL;
    return JS_TRUE;
}

static JSBool
xml_name(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;
    *vp = OBJECT_TO_JSVAL(xml->name);
    return JS_TRUE;
}

static JSBool
xml_namespace(JSContext *cx, unsigned argc, jsval *vp)
{
    JSLinearString *prefix, *nsprefix;
    uint32_t i, length;
    JSObject *ns;

    NON_LIST_XML_METHOD_PROLOG;
    if (argc == 0 && !JSXML_HAS_NAME(xml)) {
        *vp = JSVAL_NULL;
        return true;
    }

    if (argc == 0) {
        prefix = NULL;
    } else {
        JSString *str = ToString(cx, vp[2]);
        if (!str)
            return false;
        prefix = str->ensureLinear(cx);
        if (!prefix)
            return false;
        vp[2] = STRING_TO_JSVAL(prefix);      /* local root */
    }

    AutoNamespaceArray inScopeNSes(cx);
    if (!FindInScopeNamespaces(cx, xml, &inScopeNSes.array))
        return false;

    if (!prefix) {
        ns = GetNamespace(cx, xml->name, &inScopeNSes.array);
        if (!ns)
            return false;
    } else {
        ns = NULL;
        for (i = 0, length = inScopeNSes.array.length; i < length; i++) {
            ns = XMLARRAY_MEMBER(&inScopeNSes.array, i, JSObject);
            if (ns) {
                nsprefix = ns->getNamePrefix();
                if (nsprefix && EqualStrings(nsprefix, prefix))
                    break;
                ns = NULL;
            }
        }
    }

    *vp = (!ns) ? JSVAL_VOID : OBJECT_TO_JSVAL(ns);
    return true;
}

static JSBool
xml_namespaceDeclarations(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;
    if (JSXML_HAS_VALUE(xml))
        return true;

    AutoNamespaceArray ancestors(cx);
    AutoNamespaceArray declared(cx);

    JSXML *yml = xml;
    while ((yml = yml->parent) != NULL) {
        JS_ASSERT(yml->xml_class == JSXML_CLASS_ELEMENT);
        for (uint32_t i = 0, n = yml->xml_namespaces.length; i < n; i++) {
            JSObject *ns = XMLARRAY_MEMBER(&yml->xml_namespaces, i, JSObject);
            if (ns && !XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
                if (!XMLARRAY_APPEND(cx, &ancestors.array, ns))
                    return false;
            }
        }
    }

    for (uint32_t i = 0, n = xml->xml_namespaces.length; i < n; i++) {
        JSObject *ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
        if (!ns)
            continue;
        if (!IsDeclared(ns))
            continue;
        if (!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
            if (!XMLARRAY_APPEND(cx, &declared.array, ns))
                return false;
        }
    }

    return NamespacesToJSArray(cx, &declared.array, vp);
}

static const char js_attribute_str[] = "attribute";
static const char js_text_str[]      = "text";

/* Exported to jsgc.c #ifdef DEBUG. */
const char *js_xml_class_str[] = {
    "list",
    "element",
    js_attribute_str,
    "processing-instruction",
    js_text_str,
    "comment"
};

static JSBool
xml_nodeKind(JSContext *cx, unsigned argc, jsval *vp)
{
    JSString *str;

    NON_LIST_XML_METHOD_PROLOG;
    str = JS_InternString(cx, js_xml_class_str[xml->xml_class]);
    if (!str)
        return JS_FALSE;
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

static void
NormalizingDelete(JSContext *cx, JSXML *xml, uint32_t index)
{
    if (xml->xml_class == JSXML_CLASS_LIST)
        DeleteListElement(cx, xml, index);
    else
        DeleteByIndex(cx, xml, index);
}

/* XML and XMLList */
static JSBool
xml_normalize_helper(JSContext *cx, JSObject *obj, JSXML *xml)
{
    JSXML *kid, *kid2;
    uint32_t i, n;
    JSObject *kidobj;
    JSString *str;

    if (!JSXML_HAS_KIDS(xml))
        return JS_TRUE;

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;

    for (i = 0, n = xml->xml_kids.length; i < n; i++) {
        kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
        if (!kid)
            continue;
        if (kid->xml_class == JSXML_CLASS_ELEMENT) {
            kidobj = js_GetXMLObject(cx, kid);
            if (!kidobj || !xml_normalize_helper(cx, kidobj, kid))
                return JS_FALSE;
        } else if (kid->xml_class == JSXML_CLASS_TEXT) {
            while (i + 1 < n &&
                   (kid2 = XMLARRAY_MEMBER(&xml->xml_kids, i + 1, JSXML)) &&
                   kid2->xml_class == JSXML_CLASS_TEXT)
            {
                Rooted<JSString*> lstr(cx, kid->xml_value);
                Rooted<JSString*> rstr(cx, kid2->xml_value);
                str = js_ConcatStrings(cx, lstr, rstr);
                if (!str)
                    return JS_FALSE;
                NormalizingDelete(cx, xml, i + 1);
                n = xml->xml_kids.length;
                kid->xml_value = str;
            }
            if (kid->xml_value->empty()) {
                NormalizingDelete(cx, xml, i);
                n = xml->xml_kids.length;
                --i;
            }
        }
    }

    return JS_TRUE;
}

static JSBool
xml_normalize(JSContext *cx, unsigned argc, jsval *vp)
{
    XML_METHOD_PROLOG;
    *vp = OBJECT_TO_JSVAL(obj);
    return xml_normalize_helper(cx, obj, xml);
}

/* XML and XMLList */
static JSBool
xml_parent(JSContext *cx, unsigned argc, jsval *vp)
{
    JSXML *parent, *kid;
    uint32_t i, n;
    JSObject *parentobj;

    XML_METHOD_PROLOG;
    parent = xml->parent;
    if (xml->xml_class == JSXML_CLASS_LIST) {
        *vp = JSVAL_VOID;
        n = xml->xml_kids.length;
        if (n == 0)
            return JS_TRUE;

        kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
        if (!kid)
            return JS_TRUE;
        parent = kid->parent;
        for (i = 1; i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->parent != parent)
                return JS_TRUE;
        }
    }

    if (!parent) {
        *vp = JSVAL_NULL;
        return JS_TRUE;
    }

    parentobj = js_GetXMLObject(cx, parent);
    if (!parentobj)
        return JS_FALSE;
    *vp = OBJECT_TO_JSVAL(parentobj);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_processingInstructions_helper(JSContext *cx, JSObject *obj, JSXML *xml,
                                  JSObject *nameqn, jsval *vp)
{
    JSXML *list, *vxml;
    JSBool ok;
    JSObject *kidobj;
    jsval v;
    uint32_t i, n;

    list = xml_list_helper(cx, xml, vp);
    if (!list)
        return JS_FALSE;

    list->xml_targetprop = nameqn;
    ok = JS_TRUE;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        /* 13.5.4.17 Step 4 (misnumbered 9 -- Erratum?). */
        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
        while (JSXML *kid = cursor.getNext()) {
            if (kid->xml_class == JSXML_CLASS_ELEMENT) {
                kidobj = js_GetXMLObject(cx, kid);
                if (kidobj) {
                    ok = xml_processingInstructions_helper(cx, kidobj, kid,
                                                           nameqn, &v);
                } else {
                    ok = JS_FALSE;
                    v = JSVAL_NULL;
                }
                if (!ok)
                    break;
                vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
                if (JSXML_LENGTH(vxml) != 0) {
                    ok = Append(cx, list, vxml);
                    if (!ok)
                        break;
                }
            }
        }
    } else {
        /* 13.4.4.28 Step 4. */
        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
            JSXML *kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
                JSLinearString *localName = nameqn->getQNameLocalName();
                if (IS_STAR(localName) ||
                    EqualStrings(localName, kid->name->getQNameLocalName())) {
                    ok = Append(cx, list, kid);
                    if (!ok)
                        break;
                }
            }
        }
    }

    return ok;
}

static JSBool
xml_processingInstructions(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval name;
    JSObject *nameqn;
    jsid funid;

    XML_METHOD_PROLOG;

    name = (argc == 0) ? STRING_TO_JSVAL(cx->names().star) : vp[2];
    nameqn = ToXMLName(cx, name, &funid);
    if (!nameqn)
        return JS_FALSE;
    vp[2] = OBJECT_TO_JSVAL(nameqn);

    if (!JSID_IS_VOID(funid))
        return xml_list_helper(cx, xml, vp) != NULL;

    return xml_processingInstructions_helper(cx, obj, xml, nameqn, vp);
}

static JSBool
xml_prependChild(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;
    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;
    *vp = OBJECT_TO_JSVAL(obj);
    return Insert(cx, xml, 0, argc != 0 ? vp[2] : JSVAL_VOID);
}

/* XML and XMLList */
static JSBool
xml_propertyIsEnumerable(JSContext *cx, unsigned argc, jsval *vp)
{
    bool isIndex;
    uint32_t index;

    XML_METHOD_PROLOG;
    *vp = JSVAL_FALSE;
    if (argc != 0) {
        if (!IdValIsIndex(cx, vp[2], &index, &isIndex))
            return JS_FALSE;

        if (isIndex) {
            if (xml->xml_class == JSXML_CLASS_LIST) {
                /* 13.5.4.18. */
                *vp = BOOLEAN_TO_JSVAL(index < xml->xml_kids.length);
            } else {
                /* 13.4.4.30. */
                *vp = BOOLEAN_TO_JSVAL(index == 0);
            }
        }
    }
    return JS_TRUE;
}

static JSBool
namespace_full_match(const JSObject *nsa, const JSObject *nsb)
{
    JSLinearString *prefixa = nsa->getNamePrefix();
    JSLinearString *prefixb;

    if (prefixa) {
        prefixb = nsb->getNamePrefix();
        if (prefixb && !EqualStrings(prefixa, prefixb))
            return JS_FALSE;
    }
    return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
}

static JSBool
xml_removeNamespace_helper(JSContext *cx, JSXML *xml, JSObject *ns)
{
    JSObject *thisns, *attrns;
    uint32_t i, n;
    JSXML *attr, *kid;

    thisns = GetNamespace(cx, xml->name, &xml->xml_namespaces);
    JS_ASSERT(thisns);
    if (thisns == ns)
        return JS_TRUE;

    for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
        attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
        if (!attr)
            continue;
        attrns = GetNamespace(cx, attr->name, &xml->xml_namespaces);
        JS_ASSERT(attrns);
        if (attrns == ns)
            return JS_TRUE;
    }

    i = XMLARRAY_FIND_MEMBER(&xml->xml_namespaces, ns, namespace_full_match);
    if (i != XML_NOT_FOUND)
        XMLArrayDelete(cx, &xml->xml_namespaces, i, JS_TRUE);

    for (i = 0, n = xml->xml_kids.length; i < n; i++) {
        kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
        if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
            if (!xml_removeNamespace_helper(cx, kid, ns))
                return JS_FALSE;
        }
    }
    return JS_TRUE;
}

static JSBool
xml_removeNamespace(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *ns;

    NON_LIST_XML_METHOD_PROLOG;
    if (xml->xml_class != JSXML_CLASS_ELEMENT)
        goto done;
    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;

    if (!NamespaceHelper(cx, argc == 0 ? -1 : 1, vp + 2, vp))
        return JS_FALSE;
    JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
    ns = JSVAL_TO_OBJECT(*vp);

    /* NOTE: remove ns from each ancestor if not used by that ancestor. */
    if (!xml_removeNamespace_helper(cx, xml, ns))
        return JS_FALSE;
  done:
    *vp = OBJECT_TO_JSVAL(obj);
    return JS_TRUE;
}

static JSBool
xml_replace(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval value;
    JSXML *vxml, *kid;
    uint32_t index, i;
    JSObject *nameqn;

    NON_LIST_XML_METHOD_PROLOG;
    if (xml->xml_class != JSXML_CLASS_ELEMENT)
        goto done;

    if (argc <= 1) {
        value = STRING_TO_JSVAL(cx->names().undefined);
    } else {
        value = vp[3];
        vxml = VALUE_IS_XML(value)
               ? (JSXML *) JSVAL_TO_OBJECT(value)->getPrivate()
               : NULL;
        if (!vxml) {
            if (!JS_ConvertValue(cx, value, JSTYPE_STRING, &vp[3]))
                return JS_FALSE;
            value = vp[3];
        } else {
            vxml = DeepCopy(cx, vxml, NULL, 0);
            if (!vxml)
                return JS_FALSE;
            value = vp[3] = OBJECT_TO_JSVAL(vxml->object);
        }
    }

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;

    bool haveIndex;
    if (argc == 0) {
        haveIndex = false;
    } else {
        if (!IdValIsIndex(cx, vp[2], &index, &haveIndex))
            return JS_FALSE;
    }

    if (!haveIndex) {
        /*
         * Call function QName per spec, not ToXMLName, to avoid attribute
         * names.
         */
        if (!QNameHelper(cx, argc == 0 ? -1 : 1, vp + 2, vp))
            return JS_FALSE;
        JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
        nameqn = JSVAL_TO_OBJECT(*vp);

        i = xml->xml_kids.length;
        index = XML_NOT_FOUND;
        while (i != 0) {
            --i;
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && MatchElemName(nameqn, kid)) {
                if (i != XML_NOT_FOUND)
                    DeleteByIndex(cx, xml, i);
                index = i;
            }
        }

        if (index == XML_NOT_FOUND)
            goto done;
    }

    if (!Replace(cx, xml, index, value))
        return JS_FALSE;

  done:
    *vp = OBJECT_TO_JSVAL(obj);
    return JS_TRUE;
}

static JSBool
xml_setChildren(JSContext *cx, unsigned argc, jsval *vp)
{
    RootedObject obj(cx);

    if (!StartNonListXMLMethod(cx, vp, &obj))
        return JS_FALSE;

    Rooted<jsid> id(cx, NameToId(cx->names().star));
    *vp = argc != 0 ? vp[2] : JSVAL_VOID;     /* local root */
    if (!PutProperty(cx, obj, id, false, MutableHandleValue::fromMarkedLocation(vp)))
        return JS_FALSE;

    *vp = OBJECT_TO_JSVAL(obj);
    return JS_TRUE;
}

static JSBool
xml_setLocalName(JSContext *cx, unsigned argc, jsval *vp)
{
    NON_LIST_XML_METHOD_PROLOG;
    if (!JSXML_HAS_NAME(xml)) {
        vp[0] = JSVAL_VOID;
        return JS_TRUE;
    }

    JSAtom *namestr;
    if (argc == 0) {
        namestr = cx->names().undefined;
    } else {
        jsval name = vp[2];
        if (!JSVAL_IS_PRIMITIVE(name) && JSVAL_TO_OBJECT(name)->isQName()) {
            namestr = JSVAL_TO_OBJECT(name)->getQNameLocalName();
        } else {
            namestr = ToAtom(cx, name);
            if (!namestr)
                return false;
        }
    }

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;
    if (namestr)
        xml->name->setQNameLocalName(namestr);
    vp[0] = JSVAL_VOID;
    return JS_TRUE;
}

static JSBool
xml_setName(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval name;
    JSObject *nameqn;
    JSXML *nsowner;
    JSXMLArray<JSObject> *nsarray;
    uint32_t i, n;
    JSObject *ns;

    NON_LIST_XML_METHOD_PROLOG;
    if (!JSXML_HAS_NAME(xml))
        return JS_TRUE;

    if (argc == 0) {
        name = STRING_TO_JSVAL(cx->names().undefined);
    } else {
        name = vp[2];
        if (!JSVAL_IS_PRIMITIVE(name) &&
            JSVAL_TO_OBJECT(name)->getClass() == &QNameClass &&
            !(nameqn = JSVAL_TO_OBJECT(name))->getNameURI()) {
            name = vp[2] = nameqn->getQNameLocalNameVal();
        }
    }

    nameqn = ConstructObjectWithArguments(cx, &QNameClass, 1, &name);
    if (!nameqn)
        return JS_FALSE;

    /* ECMA-357 13.4.4.35 Step 4. */
    if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
        nameqn->setNameURI(cx->runtime->emptyString);

    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
    if (!xml)
        return JS_FALSE;
    xml->name = nameqn;

    /*
     * Erratum: nothing in 13.4.4.35 talks about making the name match the
     * in-scope namespaces, either by finding an in-scope namespace with a
     * matching uri and setting the new name's prefix to that namespace's
     * prefix, or by extending the in-scope namespaces for xml (which are in
     * xml->parent if xml is an attribute or a PI).
     */
    if (xml->xml_class == JSXML_CLASS_ELEMENT) {
        nsowner = xml;
    } else {
        if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
            return JS_TRUE;
        nsowner = xml->parent;
    }

    if (nameqn->getNamePrefix()) {
        /*
         * The name being set has a prefix, which originally came from some
         * namespace object (which may be the null namespace, where both the
         * prefix and uri are the empty string).  We must go through a full
         * GetNamespace in case that namespace is in-scope in nsowner.
         *
         * If we find such an in-scope namespace, we return true right away,
         * in this block.  Otherwise, we fall through to the final return of
         * AddInScopeNamespace(cx, nsowner, ns).
         */
        ns = GetNamespace(cx, nameqn, &nsowner->xml_namespaces);
        if (!ns)
            return JS_FALSE;

        /* XXXbe have to test membership to see whether GetNamespace added */
        if (XMLARRAY_HAS_MEMBER(&nsowner->xml_namespaces, ns, pointer_match)) {
            vp[0] = JSVAL_VOID;
            return JS_TRUE;
        }
    } else {
        /*
         * At this point, we know prefix of nameqn is null, so its uri can't
         * be the empty string (the null namespace always uses the empty string
         * for both prefix and uri).
         *
         * This means we must inline GetNamespace and specialize it to match
         * uri only, never prefix.  If we find a namespace with nameqn's uri
         * already in nsowner->xml_namespaces, then all that we need do is set
         * prefix of nameqn to that namespace's prefix.
         *
         * If no such namespace exists, we can create one without going through
         * the constructor, because we know uri of nameqn is non-empty (so
         * prefix does not need to be converted from null to empty by QName).
         */
        JS_ASSERT(!nameqn->getNameURI()->empty());

        nsarray = &nsowner->xml_namespaces;
        for (i = 0, n = nsarray->length; i < n; i++) {
            ns = XMLARRAY_MEMBER(nsarray, i, JSObject);
            if (ns && EqualStrings(ns->getNameURI(), nameqn->getNameURI())) {
                nameqn->setNamePrefix(ns->getNamePrefix());
                vp[0] = JSVAL_VOID;
                return JS_TRUE;
            }
        }

        ns = NewXMLNamespace(cx, NULL, nameqn->getNameURI(), JS_TRUE);
        if (!ns)
            return JS_FALSE;
    }

    if (!AddInScopeNamespace(cx, nsowner, ns))
        return JS_FALSE;
    vp[0] = JSVAL_VOID;
    return JS_TRUE;
}

/* Utility function used within xml_setNamespace */
static JSBool qn_match(const JSXML *xml, const JSObject *qn)
{
    return qname_identity(xml->name, qn);
}

/* ECMA-357 13.4.4.36 */
static JSBool
xml_setNamespace(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *qn;
    JSObject *ns;
    jsval qnargv[2];
    JSXML *nsowner;

    NON_LIST_XML_METHOD_PROLOG;
    if (!JSXML_HAS_NAME(xml))
        return JS_TRUE;

    ns = ConstructObjectWithArguments(cx, &NamespaceClass, argc == 0 ? 0 : 1, vp + 2);
    if (!ns)
        return JS_FALSE;
    vp[0] = OBJECT_TO_JSVAL(ns);
    ns->setNamespaceDeclared(JSVAL_TRUE);

    qnargv[0] = OBJECT_TO_JSVAL(ns);
    qnargv[1] = OBJECT_TO_JSVAL(xml->name);
    qn = ConstructObjectWithArguments(cx, &QNameClass, 2, qnargv);
    if (!qn)
        return JS_FALSE;

    /*
     * Erratum: setting the namespace of an attribute may cause it to duplicate
     * an already-existing attribute.  To preserve the invariant that there are
     * not multiple attributes with the same name, we delete the existing
     * attribute so that the mutated attribute will not be a duplicate.
     */
    if (xml->xml_class == JSXML_CLASS_ATTRIBUTE &&
        xml->parent && xml->parent->xml_class == JSXML_CLASS_ELEMENT &&
        !qn_match(xml, qn))
    {
        JSXMLArray<JSXML> *array = &xml->parent->xml_attrs;
        uint32_t i = XMLArrayFindMember(array, qn, qn_match);
        if (i != XML_NOT_FOUND)
            XMLArrayDelete(cx, array, i, JS_TRUE);
    }

    xml->name = qn;

    /*
     * Erratum: the spec fails to update the governing in-scope namespaces.
     * See the erratum noted in xml_setName, above.
     */
    if (xml->xml_class == JSXML_CLASS_ELEMENT) {
        nsowner = xml;
    } else {
        if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
            return JS_TRUE;
        nsowner = xml->parent;
    }
    if (!AddInScopeNamespace(cx, nsowner, ns))
        return JS_FALSE;
    vp[0] = JSVAL_VOID;
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_text_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval *vp)
{
    JSXML *list, *kid, *vxml;
    uint32_t i, n;
    JSObject *kidobj;
    jsval v;

    list = xml_list_helper(cx, xml, vp);
    if (!list)
        return JS_FALSE;

    if (xml->xml_class == JSXML_CLASS_LIST) {
        for (i = 0, n = xml->xml_kids.length; i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
                JSBool ok;
                kidobj = js_GetXMLObject(cx, kid);
                if (kidobj) {
                    ok = xml_text_helper(cx, kidobj, kid, &v);
                } else {
                    ok = JS_FALSE;
                    v = JSVAL_NULL;
                }
                if (!ok)
                    return JS_FALSE;
                vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
                if (JSXML_LENGTH(vxml) != 0 && !Append(cx, list, vxml))
                    return JS_FALSE;
            }
        }
    } else {
        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
            if (kid && kid->xml_class == JSXML_CLASS_TEXT) {
                if (!Append(cx, list, kid))
                    return JS_FALSE;
            }
        }
    }
    return JS_TRUE;
}

static JSBool
xml_text(JSContext *cx, unsigned argc, jsval *vp)
{
    XML_METHOD_PROLOG;
    return xml_text_helper(cx, obj, xml, vp);
}

/* XML and XMLList */
static JSString *
xml_toString_helper(JSContext *cx, JSXML *xml)
{
    if (xml->xml_class == JSXML_CLASS_ATTRIBUTE ||
        xml->xml_class == JSXML_CLASS_TEXT) {
        return xml->xml_value;
    }

    if (!HasSimpleContent(xml))
        return ToXMLString(cx, OBJECT_TO_JSVAL(xml->object), 0);

    RootedString str(cx, cx->runtime->emptyString);
    JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
    while (JSXML *kid = cursor.getNext()) {
        if (kid->xml_class != JSXML_CLASS_COMMENT &&
            kid->xml_class != JSXML_CLASS_PROCESSING_INSTRUCTION) {
            RootedString kidstr(cx, xml_toString_helper(cx, kid));
            if (!kidstr) {
                str = NULL;
                break;
            }
            str = js_ConcatStrings(cx, str, kidstr);
            if (!str)
                break;
        }
    }
    return str;
}

static JSBool
xml_toSource(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *obj = ToObject(cx, HandleValue::fromMarkedLocation(&vp[1]));
    if (!obj)
        return JS_FALSE;
    JSString *str = ToXMLString(cx, OBJECT_TO_JSVAL(obj), TO_SOURCE_FLAG);
    if (!str)
        return JS_FALSE;
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

static JSBool
xml_toString(JSContext *cx, unsigned argc, jsval *vp)
{
    JSString *str;

    XML_METHOD_PROLOG;
    str = xml_toString_helper(cx, xml);
    if (!str)
        return JS_FALSE;
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_toXMLString(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *obj = ToObject(cx, HandleValue::fromMarkedLocation(&vp[1]));
    if (!obj)
        return JS_FALSE;
    JSString *str = ToXMLString(cx, OBJECT_TO_JSVAL(obj), 0);
    if (!str)
        return JS_FALSE;
    *vp = STRING_TO_JSVAL(str);
    return JS_TRUE;
}

/* XML and XMLList */
static JSBool
xml_valueOf(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *obj = ToObject(cx, HandleValue::fromMarkedLocation(&vp[1]));
    if (!obj)
        return false;
    *vp = OBJECT_TO_JSVAL(obj);
    return true;
}

static JSFunctionSpec xml_methods[] = {
    JS_FN("addNamespace",          xml_addNamespace,          1,0),
    JS_FN("appendChild",           xml_appendChild,           1,0),
    JS_FN(js_attribute_str,        xml_attribute,             1,0),
    JS_FN("attributes",            xml_attributes,            0,0),
    JS_FN("child",                 xml_child,                 1,0),
    JS_FN("childIndex",            xml_childIndex,            0,0),
    JS_FN("children",              xml_children,              0,0),
    JS_FN("comments",              xml_comments,              0,0),
    JS_FN("contains",              xml_contains,              1,0),
    JS_FN("copy",                  xml_copy,                  0,0),
    JS_FN("descendants",           xml_descendants,           1,0),
    JS_FN("elements",              xml_elements,              1,0),
    JS_FN("hasOwnProperty",        xml_hasOwnProperty,        1,0),
    JS_FN("hasComplexContent",     xml_hasComplexContent,     1,0),
    JS_FN("hasSimpleContent",      xml_hasSimpleContent,      1,0),
    JS_FN("inScopeNamespaces",     xml_inScopeNamespaces,     0,0),
    JS_FN("insertChildAfter",      xml_insertChildAfter,      2,0),
    JS_FN("insertChildBefore",     xml_insertChildBefore,     2,0),
    JS_FN(js_length_str,           xml_length,                0,0),
    JS_FN(js_localName_str,        xml_localName,             0,0),
    JS_FN(js_name_str,             xml_name,                  0,0),
    JS_FN(js_namespace_str,        xml_namespace,             1,0),
    JS_FN("namespaceDeclarations", xml_namespaceDeclarations, 0,0),
    JS_FN("nodeKind",              xml_nodeKind,              0,0),
    JS_FN("normalize",             xml_normalize,             0,0),
    JS_FN(js_xml_parent_str,       xml_parent,                0,0),
    JS_FN("processingInstructions",xml_processingInstructions,1,0),
    JS_FN("prependChild",          xml_prependChild,          1,0),
    JS_FN("propertyIsEnumerable",  xml_propertyIsEnumerable,  1,0),
    JS_FN("removeNamespace",       xml_removeNamespace,       1,0),
    JS_FN("replace",               xml_replace,               2,0),
    JS_FN("setChildren",           xml_setChildren,           1,0),
    JS_FN("setLocalName",          xml_setLocalName,          1,0),
    JS_FN("setName",               xml_setName,               1,0),
    JS_FN("setNamespace",          xml_setNamespace,          1,0),
    JS_FN(js_text_str,             xml_text,                  0,0),
    JS_FN(js_toSource_str,         xml_toSource,              0,0),
    JS_FN(js_toString_str,         xml_toString,              0,0),
    JS_FN(js_toXMLString_str,      xml_toXMLString,           0,0),
    JS_FN(js_valueOf_str,          xml_valueOf,               0,0),
    JS_FS_END
};

static JSBool
CopyXMLSettings(JSContext *cx, HandleObject from, HandleObject to)
{
    int i;
    const char *name;
    jsval v;

    /* Note: PRETTY_INDENT is not a boolean setting. */
    for (i = 0; xml_static_props[i].name; i++) {
        name = xml_static_props[i].name;
        if (!JS_GetProperty(cx, from, name, &v))
            return false;
        if (name == js_prettyIndent_str) {
            if (!JSVAL_IS_NUMBER(v))
                continue;
        } else {
            if (!JSVAL_IS_BOOLEAN(v))
                continue;
        }
        if (!JS_SetProperty(cx, to, name, &v))
            return false;
    }

    return true;
}

static JSBool
SetDefaultXMLSettings(JSContext *cx, HandleObject obj)
{
    int i;
    jsval v;

    /* Note: PRETTY_INDENT is not a boolean setting. */
    for (i = 0; xml_static_props[i].name; i++) {
        v = (xml_static_props[i].name != js_prettyIndent_str)
            ? JSVAL_TRUE : INT_TO_JSVAL(2);
        if (!JS_SetProperty(cx, obj, xml_static_props[i].name, &v))
            return JS_FALSE;
    }
    return true;
}

static JSBool
xml_settings(JSContext *cx, unsigned argc, jsval *vp)
{
    RootedObject settings(cx, JS_NewObject(cx, NULL, NULL, NULL));
    if (!settings)
        return false;
    *vp = OBJECT_TO_JSVAL(settings);
    RootedObject obj(cx, ToObject(cx, HandleValue::fromMarkedLocation(&vp[1])));
    if (!obj)
        return false;
    return CopyXMLSettings(cx, obj, settings);
}

static JSBool
xml_setSettings(JSContext *cx, unsigned argc, jsval *vp)
{
    jsval v;
    JSBool ok;

    RootedObject obj(cx, ToObject(cx, HandleValue::fromMarkedLocation(&vp[1])));
    if (!obj)
        return JS_FALSE;
    v = (argc == 0) ? JSVAL_VOID : vp[2];
    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
        ok = SetDefaultXMLSettings(cx, obj);
    } else {
        if (JSVAL_IS_PRIMITIVE(v)) {
            vp[0] = JSVAL_VOID;
            return JS_TRUE;
        }
        RootedObject settings(cx, JSVAL_TO_OBJECT(v));
        ok = CopyXMLSettings(cx, settings, obj);
    }
    vp[0] = JSVAL_VOID;
    return ok;
}

static JSBool
xml_defaultSettings(JSContext *cx, unsigned argc, jsval *vp)
{
    RootedObject settings(cx, JS_NewObject(cx, NULL, NULL, NULL));
    if (!settings)
        return JS_FALSE;
    *vp = OBJECT_TO_JSVAL(settings);
    return SetDefaultXMLSettings(cx, settings);
}

static JSFunctionSpec xml_static_methods[] = {
    JS_FN("settings",         xml_settings,          0,0),
    JS_FN("setSettings",      xml_setSettings,       1,0),
    JS_FN("defaultSettings",  xml_defaultSettings,   0,0),
    JS_FS_END
};

static JSBool
XML(JSContext *cx, unsigned argc, Value *vp)
{
    JSXML *xml, *copy;
    JSObject *xobj, *vobj;
    Class *clasp;

    jsval v = argc ? vp[2] : JSVAL_VOID;

    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
        v = STRING_TO_JSVAL(cx->runtime->emptyString);

    xobj = ToXML(cx, v);
    if (!xobj)
        return JS_FALSE;
    xml = (JSXML *) xobj->getPrivate();

    if (IsConstructing(vp) && !JSVAL_IS_PRIMITIVE(v)) {
        vobj = JSVAL_TO_OBJECT(v);
        clasp = vobj->getClass();
        if (clasp == &XMLClass) {
            copy = DeepCopy(cx, xml, NULL, 0);
            if (!copy)
                return JS_FALSE;
            vp->setObject(*copy->object);
            return JS_TRUE;
        }
    }

    vp->setObject(*xobj);
    return JS_TRUE;
}

static JSBool
XMLList(JSContext *cx, unsigned argc, jsval *vp)
{
    JSObject *vobj, *listobj;
    JSXML *xml, *list;

    jsval v = argc ? vp[2] : JSVAL_VOID;

    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
        v = STRING_TO_JSVAL(cx->runtime->emptyString);

    if (IsConstructing(vp) && !JSVAL_IS_PRIMITIVE(v)) {
        vobj = JSVAL_TO_OBJECT(v);
        if (vobj->isXML()) {
            xml = (JSXML *) vobj->getPrivate();
            if (xml->xml_class == JSXML_CLASS_LIST) {
                listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
                if (!listobj)
                    return JS_FALSE;
                *vp = OBJECT_TO_JSVAL(listobj);

                list = (JSXML *) listobj->getPrivate();
                if (!Append(cx, list, xml))
                    return JS_FALSE;
                return JS_TRUE;
            }
        }
    }

    /* Toggle on XML support since the script has explicitly requested it. */
    listobj = ToXMLList(cx, v);
    if (!listobj)
        return JS_FALSE;

    *vp = OBJECT_TO_JSVAL(listobj);
    return JS_TRUE;
}

#ifdef DEBUG_notme
JSCList xml_leaks = JS_INIT_STATIC_CLIST(&xml_leaks);
uint32_t  xml_serial;
#endif

JSXML *
js_NewXML(JSContext *cx, JSXMLClass xml_class)
{
    cx->runtime->gcExactScanningEnabled = false;

    JSXML *xml = js_NewGCXML(cx);
    if (!xml)
        return NULL;

    xml->object.init(NULL);
    xml->domnode = NULL;
    xml->parent.init(NULL);
    xml->name.init(NULL);
    xml->xml_class = xml_class;
    xml->xml_flags = 0;
    if (JSXML_CLASS_HAS_VALUE(xml_class)) {
        xml->xml_value.init(cx->runtime->emptyString);
    } else {
        xml->xml_value.init(NULL);
        xml->xml_kids.init();
        if (xml_class == JSXML_CLASS_LIST) {
            xml->xml_target.init(NULL);
            xml->xml_targetprop.init(NULL);
        } else {
            xml->xml_namespaces.init();
            xml->xml_attrs.init();
        }
    }

#ifdef DEBUG_notme
    JS_APPEND_LINK(&xml->links, &xml_leaks);
    xml->serial = xml_serial++;
#endif
    return xml;
}

void
JSXML::writeBarrierPre(JSXML *xml)
{
#ifdef JSGC_INCREMENTAL
    if (!xml)
        return;

    JSCompartment *comp = xml->compartment();
    if (comp->needsBarrier()) {
        JSXML *tmp = xml;
        MarkXMLUnbarriered(comp->barrierTracer(), &tmp, "write barrier");
        JS_ASSERT(tmp == xml);
    }
#endif
}

void
JSXML::writeBarrierPost(JSXML *xml, void *addr)
{
}

void
js_TraceXML(JSTracer *trc, JSXML *xml)
{
    if (xml->object)
        MarkObject(trc, &xml->object, "object");
    if (xml->name)
        MarkObject(trc, &xml->name, "name");
    if (xml->parent)
        MarkXML(trc, &xml->parent, "xml_parent");

    if (JSXML_HAS_VALUE(xml)) {
        if (xml->xml_value)
            MarkString(trc, &xml->xml_value, "value");
        return;
    }

    MarkXMLRange(trc, xml->xml_kids.length, xml->xml_kids.vector, "xml_kids");
    js_XMLArrayCursorTrace(trc, xml->xml_kids.cursors);

    if (xml->xml_class == JSXML_CLASS_LIST) {
        if (xml->xml_target)
            MarkXML(trc, &xml->xml_target, "target");
        if (xml->xml_targetprop)
            MarkObject(trc, &xml->xml_targetprop, "targetprop");
    } else {
        MarkObjectRange(trc, xml->xml_namespaces.length,
                        xml->xml_namespaces.vector,
                        "xml_namespaces");
        js_XMLArrayCursorTrace(trc, xml->xml_namespaces.cursors);

        MarkXMLRange(trc, xml->xml_attrs.length, xml->xml_attrs.vector, "xml_attrs");
        js_XMLArrayCursorTrace(trc, xml->xml_attrs.cursors);
    }
}

JSObject *
js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
{
    JSXML *xml = js_NewXML(cx, xml_class);
    if (!xml)
        return NULL;

    AutoXMLRooter root(cx, xml);
    return js_GetXMLObject(cx, xml);
}

static JSObject *
NewXMLObject(JSContext *cx, JSXML *xml)
{
    JSObject *obj;

    obj = NewObjectWithClassProto(cx, &XMLClass, NULL, cx->global());
    if (!obj)
        return NULL;
    obj->setPrivateGCThing(xml);
    return obj;
}

JSObject *
js_GetXMLObject(JSContext *cx, JSXML *xmlArg)
{
    Rooted<JSXML*> xml(cx, xmlArg);
    JSObject *obj;

    obj = xml->object;
    if (obj) {
        JS_ASSERT(obj->getPrivate() == xml);
        return obj;
    }

    obj = NewXMLObject(cx, xml);
    if (!obj)
        return NULL;
    xml->object = obj;
    return obj;
}

JSObject *
js_InitNamespaceClass(JSContext *cx, HandleObject obj)
{
    cx->runtime->gcExactScanningEnabled = false;

    JS_ASSERT(obj->isNative());
    Rooted<GlobalObject*> global(cx, &obj->asGlobal());

    JSObject *namespaceProto = global->createBlankPrototype(cx, &NamespaceClass);
    if (!namespaceProto)
        return NULL;
    JSFlatString *empty = cx->runtime->emptyString;
    namespaceProto->setNamePrefix(empty);
    namespaceProto->setNameURI(empty);

    const unsigned NAMESPACE_CTOR_LENGTH = 2;
    RootedFunction ctor(cx);
    ctor = global->createConstructor(cx, Namespace, cx->names().Namespace,
                                     NAMESPACE_CTOR_LENGTH);
    if (!ctor)
        return NULL;

    if (!LinkConstructorAndPrototype(cx, ctor, namespaceProto))
        return NULL;

    if (!DefinePropertiesAndBrand(cx, namespaceProto, namespace_props, namespace_methods))
        return NULL;

    if (!DefineConstructorAndPrototype(cx, global, JSProto_Namespace, ctor, namespaceProto))
        return NULL;

    return namespaceProto;
}

JSObject *
js_InitQNameClass(JSContext *cx, HandleObject obj)
{
    cx->runtime->gcExactScanningEnabled = false;

    JS_ASSERT(obj->isNative());
    Rooted<GlobalObject*> global(cx, &obj->asGlobal());

    RootedObject qnameProto(cx, global->createBlankPrototype(cx, &QNameClass));
    if (!qnameProto)
        return NULL;
    JSAtom *empty = cx->runtime->emptyString;
    if (!InitXMLQName(cx, qnameProto, empty, empty, empty))
        return NULL;

    const unsigned QNAME_CTOR_LENGTH = 2;
    RootedFunction ctor(cx, global->createConstructor(cx, QName, cx->names().QName,
                                                      QNAME_CTOR_LENGTH));
    if (!ctor)
        return NULL;

    if (!LinkConstructorAndPrototype(cx, ctor, qnameProto))
        return NULL;

    if (!DefinePropertiesAndBrand(cx, qnameProto, NULL, qname_methods))
        return NULL;

    if (!DefineConstructorAndPrototype(cx, global, JSProto_QName, ctor, qnameProto))
        return NULL;

    return qnameProto;
}

JSObject *
js_InitXMLClass(JSContext *cx, HandleObject obj)
{
    cx->runtime->gcExactScanningEnabled = false;
    Rooted<GlobalObject *> global(cx, &obj->asGlobal());
    JS_ASSERT(obj->isNative());

    RootedObject xmlProto(cx, global->createBlankPrototype(cx, &XMLClass));
    if (!xmlProto)
        return NULL;
    Rooted<JSXML*> xml(cx, js_NewXML(cx, JSXML_CLASS_TEXT));
    if (!xml)
        return NULL;
    xmlProto->setPrivateGCThing(xml);
    xml->object = xmlProto;

    /* Don't count this as a real content-created XML object. */
    if (!cx->runningWithTrustedPrincipals()) {
        JS_ASSERT(sE4XObjectsCreated > 0);
        --sE4XObjectsCreated;
    }

    const unsigned XML_CTOR_LENGTH = 1;
    RootedFunction ctor(cx);
    ctor = global->createConstructor(cx, XML, cx->names().XML, XML_CTOR_LENGTH);
    if (!ctor)
        return NULL;

    if (!LinkConstructorAndPrototype(cx, ctor, xmlProto))
        return NULL;

    if (!DefinePropertiesAndBrand(cx, xmlProto, NULL, xml_methods) ||
        !DefinePropertiesAndBrand(cx, ctor, xml_static_props, xml_static_methods))
    {
        return NULL;
    }

    if (!SetDefaultXMLSettings(cx, ctor))
        return NULL;

    /* Define the XMLList function, and give it the same .prototype as XML. */
    RootedFunction xmllist(cx, JS_DefineFunction(cx, global, js_XMLList_str, XMLList, 1, JSFUN_CONSTRUCTOR));
    if (!xmllist)
        return NULL;
    RootedValue value(cx, ObjectValue(*xmlProto));
    if (!JSObject::defineProperty(cx, xmllist, cx->names().classPrototype,
                                  value, JS_PropertyStub, JS_StrictPropertyStub,
                                  JSPROP_PERMANENT | JSPROP_READONLY))
    {
        return NULL;
    }

    if (!DefineConstructorAndPrototype(cx, global, JSProto_XML, ctor, xmlProto))
        return NULL;

    /* Define the isXMLName function. */
    if (!JS_DefineFunction(cx, global, js_isXMLName_str, xml_isXMLName, 1, 0))
        return NULL;

    return xmlProto;
}

JSObject *
js_InitXMLClasses(JSContext *cx, HandleObject obj)
{
    if (!js_InitNamespaceClass(cx, obj))
        return NULL;
    if (!js_InitQNameClass(cx, obj))
        return NULL;
    return js_InitXMLClass(cx, obj);
}

bool
GlobalObject::getFunctionNamespace(JSContext *cx, Value *vp)
{
    Value v = getSlot(FUNCTION_NS);
    if (v.isUndefined()) {
        HandlePropertyName prefix = cx->names().function;
        HandlePropertyName uri = cx->names().functionNamespaceURI;
        RootedObject obj(cx, NewXMLNamespace(cx, prefix, uri, JS_FALSE));
        if (!obj)
            return false;

        /*
         * Avoid entraining any in-scope Object.prototype.  The loss of
         * Namespace.prototype is not detectable, as there is no way to
         * refer to this instance in scripts.  When used to qualify method
         * names, its prefix and uri references are copied to the QName.
         * The parent remains set and links back to global.
         */
        if (!JSObject::clearType(cx, obj))
            return false;

        v = ObjectValue(*obj);
        setSlot(FUNCTION_NS, v);
    }

    *vp = v;
    return true;
}

/*
 * Note the asymmetry between js_GetDefaultXMLNamespace and js_SetDefaultXML-
 * Namespace.  Get searches fp->scopeChain for JS_DEFAULT_XML_NAMESPACE_ID,
 * while Set sets JS_DEFAULT_XML_NAMESPACE_ID in fp->varobj. There's no
 * requirement that fp->varobj lie directly on fp->scopeChain, although
 * it should be reachable using the prototype chain from a scope object (cf.
 * JSOPTION_VAROBJFIX in jsapi.h).
 *
 * If Get can't find JS_DEFAULT_XML_NAMESPACE_ID along the scope chain, it
 * creates a default namespace via 'new Namespace()'.  In contrast, Set uses
 * its v argument as the uri of a new Namespace, with "" as the prefix.  See
 * ECMA-357 12.1 and 12.1.1.  Note that if Set is called with a Namespace n,
 * the default XML namespace will be set to ("", n.uri).  So the uri string
 * is really the only usefully stored value of the default namespace.
 */
JSBool
js_GetDefaultXMLNamespace(JSContext *cx, jsval *vp)
{
    JSObject *ns;
    RootedObject obj(cx);
    RootedValue v(cx);

    RootedObject tmp(cx);

    JSObject *scopeChain = GetCurrentScopeChain(cx);
    if (!scopeChain)
        return false;

    obj = NULL;
    for (tmp = scopeChain; tmp; tmp = tmp->enclosingScope()) {
        if (tmp->isBlock() || tmp->isWith())
            continue;
        if (!JSObject::getSpecial(cx, tmp, tmp, SpecialId::defaultXMLNamespace(), &v))
            return JS_FALSE;
        if (!JSVAL_IS_PRIMITIVE(v)) {
            *vp = v;
            return JS_TRUE;
        }
        obj = tmp;
    }

    ns = ConstructObjectWithArguments(cx, &NamespaceClass, 0, NULL);
    if (!ns)
        return JS_FALSE;
    v = OBJECT_TO_JSVAL(ns);
    if (!JSObject::defineSpecial(cx, obj, SpecialId::defaultXMLNamespace(), v,
                                 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_PERMANENT)) {
        return JS_FALSE;
    }
    *vp = v;
    return JS_TRUE;
}

JSBool
js_SetDefaultXMLNamespace(JSContext *cx, const Value &v)
{
    Value argv[2];
    argv[0].setString(cx->runtime->emptyString);
    argv[1] = v;
    JSObject *ns = ConstructObjectWithArguments(cx, &NamespaceClass, 2, argv);
    if (!ns)
        return JS_FALSE;

    RootedObject varobj(cx, &cx->fp()->varObj());
    RootedValue value(cx, ObjectValue(*ns));
    if (!JSObject::defineSpecial(cx, varobj, SpecialId::defaultXMLNamespace(), value,
                                 JS_PropertyStub, JS_StrictPropertyStub, JSPROP_PERMANENT)) {
        return JS_FALSE;
    }
    return JS_TRUE;
}

JSBool
js_ToAttributeName(JSContext *cx, Value *vp)
{
    JSObject *qn;

    qn = ToAttributeName(cx, *vp);
    if (!qn)
        return JS_FALSE;
    vp->setObject(*qn);
    return JS_TRUE;
}

JSFlatString *
js_EscapeAttributeValue(JSContext *cx, JSString *str, JSBool quote)
{
    StringBuffer sb(cx);
    return EscapeAttributeValue(cx, sb, str, quote);
}

JSString *
js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
{
    size_t len = str->length();
    const jschar *chars = str->getChars(cx);
    if (!chars)
        return NULL;

    size_t len2 = str2->length();
    const jschar *chars2 = str2->getChars(cx);
    if (!chars2)
        return NULL;

    size_t newlen = (isName) ? len + 1 + len2 : len + 2 + len2 + 1;
    jschar *newchars = cx->pod_malloc<jschar>(newlen+1);
    if (!newchars)
        return NULL;

    js_strncpy(newchars, chars, len);
    newchars += len;
    if (isName) {
        *newchars++ = ' ';
        js_strncpy(newchars, chars2, len2);
        newchars += len2;
    } else {
        *newchars++ = '=';
        *newchars++ = '"';
        js_strncpy(newchars, chars2, len2);
        newchars += len2;
        *newchars++ = '"';
    }
    *newchars = 0;
    return js_NewString(cx, newchars - newlen, newlen);
}

JSFlatString *
js_EscapeElementValue(JSContext *cx, JSString *str)
{
    StringBuffer sb(cx);
    return EscapeElementValue(cx, sb, str, 0);
}

JSString *
js_ValueToXMLString(JSContext *cx, const Value &v)
{
    return ToXMLString(cx, v, 0);
}

JSBool
js_GetAnyName(JSContext *cx, jsid *idp)
{
    JSObject *global = cx->global();
    Value v = global->getReservedSlot(JSProto_AnyName);
    if (v.isUndefined()) {
        RootedObject obj(cx, NewObjectWithGivenProto(cx, &AnyNameClass, NULL, global));
        if (!obj)
            return false;

        JS_ASSERT(!obj->getProto());

        JSRuntime *rt = cx->runtime;
        if (!InitXMLQName(cx, obj, rt->emptyString, rt->emptyString, rt->atomState.star))
            return false;

        v.setObject(*obj);
        SetReservedSlot(global, JSProto_AnyName, v);
    }
    *idp = OBJECT_TO_JSID(&v.toObject());
    return true;
}

JSBool
js_FindXMLProperty(JSContext *cx, const Value &nameval, MutableHandleObject objp, jsid *idp)
{
    JSObject *nameobj;
    jsval v;
    JSObject *qn;
    RootedId funid(cx);
    RootedObject target(cx);
    JSObject *obj, *proto;
    JSXML *xml;
    JSBool found;

    JS_ASSERT(nameval.isObject());
    nameobj = &nameval.toObject();
    if (nameobj->getClass() == &AnyNameClass) {
        v = STRING_TO_JSVAL(cx->names().star);
        nameobj = ConstructObjectWithArguments(cx, &QNameClass, 1, &v);
        if (!nameobj)
            return JS_FALSE;
    } else {
        JS_ASSERT(nameobj->getClass() == &AttributeNameClass ||
                  nameobj->getClass() == &QNameClass);
    }

    qn = nameobj;

    JSAtom *name;
    funid = GetLocalNameFromFunctionQName(qn, &name, cx)
            ? AtomToId(name)
            : JSID_VOID;

    obj = cx->stack.currentScriptedScopeChain();
    do {
        /* Skip any With object that can wrap XML. */
        target = obj;
        while (target->getClass() == &WithClass) {
             proto = target->getProto();
             if (!proto)
                 break;
             target = proto;
        }

        if (target->isXML()) {
            if (JSID_IS_VOID(funid)) {
                xml = (JSXML *) target->getPrivate();
                found = HasNamedProperty(xml, qn);
            } else {
                if (!HasFunctionProperty(cx, target, funid, &found))
                    return JS_FALSE;
            }
            if (found) {
                *idp = OBJECT_TO_JSID(nameobj);
                objp.set(target);
                return JS_TRUE;
            }
        } else if (!JSID_IS_VOID(funid)) {
            RootedObject pobj(cx);
            RootedShape prop(cx);
            if (!JSObject::lookupGeneric(cx, target, funid, &pobj, &prop))
                return JS_FALSE;
            if (prop) {
                *idp = funid;
                objp.set(target);
                return JS_TRUE;
            }
        }
    } while ((obj = obj->enclosingScope()) != NULL);

    JSAutoByteString printable;
    JSString *str = ConvertQNameToString(cx, nameobj);
    if (str && js_ValueToPrintable(cx, StringValue(str), &printable)) {
        JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
                                     JSMSG_UNDEFINED_XML_NAME, printable.ptr());
    }
    return JS_FALSE;
}

static JSBool
GetXMLFunction(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp)
{
    JS_ASSERT(obj->isXML());

    /*
     * See comments before xml_lookupGeneric about the need for the proto
     * chain lookup.
     */
    RootedObject target(cx, obj);
    for (;;) {
        if (!baseops::GetProperty(cx, target, id, vp))
            return false;
        if (!JSVAL_IS_PRIMITIVE(vp) && JSVAL_TO_OBJECT(vp)->isFunction())
            return true;
        target = target->getProto();
        if (target == NULL || !target->isNative())
            break;
    }

    JSXML *xml = (JSXML *) obj->getPrivate();
    if (!HasSimpleContent(xml))
        return true;

    /* Search in String.prototype to implement 11.2.2.1 Step 3(f). */
    RootedObject proto(cx, obj->global().getOrCreateStringPrototype(cx));
    if (!proto)
        return false;

    return JSObject::getGeneric(cx, proto, proto, id, vp);
}

static JSXML *
GetPrivate(JSContext *cx, JSObject *obj, const char *method)
{
    if (!obj->isXML()) {
        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                             JSMSG_INCOMPATIBLE_METHOD,
                             js_XML_str, method, obj->getClass()->name);
        return NULL;
    }
    return (JSXML *)obj->getPrivate();
}

JSBool
js_GetXMLDescendants(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    JSXML *xml, *list;

    xml = GetPrivate(cx, obj, "descendants internal method");
    if (!xml)
        return JS_FALSE;

    list = Descendants(cx, xml, id);
    if (!list)
        return JS_FALSE;
    *vp = OBJECT_TO_JSVAL(list->object);
    return JS_TRUE;
}

JSBool
js_DeleteXMLListElements(JSContext *cx, JSObject *listobj)
{
    JSXML *list;
    uint32_t n;

    list = (JSXML *) listobj->getPrivate();
    for (n = list->xml_kids.length; n != 0; --n)
        DeleteListElement(cx, list, 0);

    return JS_TRUE;
}

struct JSXMLFilter
{
    HeapPtr<JSXML>             list;
    HeapPtr<JSXML>             result;
    HeapPtr<JSXML>             kid;
    JSXMLArrayCursor<JSXML>    cursor;

    JSXMLFilter(JSXML *list, JSXMLArray<JSXML> *array)
      : list(list), result(NULL), kid(NULL), cursor(array) {}

    ~JSXMLFilter() {}
};

static void
xmlfilter_trace(JSTracer *trc, RawObject obj)
{
    JSXMLFilter *filter = (JSXMLFilter *) obj->getPrivate();
    if (!filter)
        return;

    JS_ASSERT(filter->list);
    MarkXML(trc, &filter->list, "list");
    if (filter->result)
        MarkXML(trc, &filter->result, "result");
    if (filter->kid)
        MarkXML(trc, &filter->kid, "kid");

    /*
     * We do not need to trace the cursor as that would be done when
     * tracing the filter->list.
     */
}

static void
xmlfilter_finalize(FreeOp *fop, RawObject obj)
{
    JSXMLFilter *filter = (JSXMLFilter *) obj->getPrivate();
    if (!filter)
        return;

    fop->delete_(filter);
}

Class js_XMLFilterClass = {
    "XMLFilter",
    JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_IS_ANONYMOUS,
    JS_PropertyStub,         /* addProperty */
    JS_PropertyStub,         /* delProperty */
    JS_PropertyStub,         /* getProperty */
    JS_StrictPropertyStub,   /* setProperty */
    JS_EnumerateStub,
    JS_ResolveStub,
    JS_ConvertStub,
    xmlfilter_finalize,
    NULL,                    /* checkAccess */
    NULL,                    /* call        */
    NULL,                    /* construct   */
    NULL,                    /* hasInstance */
    xmlfilter_trace
};

JSBool
js_StepXMLListFilter(JSContext *cx, JSBool initialized)
{
    jsval *sp;
    JSObject *obj, *filterobj, *resobj, *kidobj;
    JSXML *xml, *list;
    JSXMLFilter *filter;

    sp = cx->regs().sp;
    if (!initialized) {
        /*
         * We haven't iterated yet, so initialize the filter based on the
         * value stored in sp[-2].
         */
        if (!VALUE_IS_XML(sp[-2])) {
            RootedValue val(cx, sp[-2]);
            js_ReportValueError(cx, JSMSG_NON_XML_FILTER, -2, val, NullPtr());
            return JS_FALSE;
        }
        obj = JSVAL_TO_OBJECT(sp[-2]);
        xml = (JSXML *) obj->getPrivate();

        if (xml->xml_class == JSXML_CLASS_LIST) {
            list = xml;
        } else {
            obj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
            if (!obj)
                return JS_FALSE;

            /*
             * Root just-created obj. sp[-2] cannot be used yet for rooting
             * as it may be the only root holding xml.
             */
            sp[-1] = OBJECT_TO_JSVAL(obj);
            list = (JSXML *) obj->getPrivate();
            if (!Append(cx, list, xml))
                return JS_FALSE;
        }

        filterobj = NewObjectWithGivenProto(cx, &js_XMLFilterClass, NULL, cx->global());
        if (!filterobj)
            return JS_FALSE;

        /*
         * Init all filter fields before setPrivate exposes it to
         * xmlfilter_trace or xmlfilter_finalize.
         */
        filter = cx->new_<JSXMLFilter>(list, &list->xml_kids);
        if (!filter)
            return JS_FALSE;
        filterobj->setPrivate(filter);

        /* Store filterobj to use in the later iterations. */
        sp[-2] = OBJECT_TO_JSVAL(filterobj);

        resobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
        if (!resobj)
            return JS_FALSE;

        /* This also roots resobj. */
        filter->result = (JSXML *) resobj->getPrivate();
    } else {
        /* We have iterated at least once. */
        JS_ASSERT(!JSVAL_IS_PRIMITIVE(sp[-2]));
        JS_ASSERT(JSVAL_TO_OBJECT(sp[-2])->getClass() == &js_XMLFilterClass);
        filter = (JSXMLFilter *) JSVAL_TO_OBJECT(sp[-2])->getPrivate();
        JS_ASSERT(filter->kid);

        /* Check if the filter expression wants to append the element. */
        if (ToBoolean(sp[-1]) &&
            !Append(cx, filter->result, filter->kid)) {
            return JS_FALSE;
        }
    }

    /* Do the iteration. */
    filter->kid = filter->cursor.getNext();
    if (!filter->kid) {
        /*
         * Do not defer finishing the cursor until the next GC cycle to avoid
         * accumulation of dead cursors associated with filter->list.
         */
        filter->cursor.disconnect();
        JS_ASSERT(filter->result->object);
        sp[-2] = OBJECT_TO_JSVAL(filter->result->object);
        kidobj = NULL;
    } else {
        kidobj = js_GetXMLObject(cx, filter->kid);
        if (!kidobj)
            return JS_FALSE;
    }

    /* Null as kidobj at sp[-1] signals filter termination. */
    sp[-1] = OBJECT_TO_JSVAL(kidobj);
    return JS_TRUE;
}

JSObject *
js_ValueToXMLObject(JSContext *cx, const Value &v)
{
    return ToXML(cx, v);
}

JSObject *
js_ValueToXMLListObject(JSContext *cx, const Value &v)
{
    return ToXMLList(cx, v);
}

JSObject *
js_NewXMLSpecialObject(JSContext *cx, JSXMLClass xml_class, JSString *name,
                       JSString *value)
{
    unsigned flags;
    JSObject *obj;
    JSXML *xml;
    JSObject *qn;

    if (!GetXMLSettingFlags(cx, &flags))
        return NULL;

    if ((xml_class == JSXML_CLASS_COMMENT &&
         (flags & XSF_IGNORE_COMMENTS)) ||
        (xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
         (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS))) {
        return js_NewXMLObject(cx, JSXML_CLASS_TEXT);
    }

    obj = js_NewXMLObject(cx, xml_class);
    if (!obj)
        return NULL;
    xml = (JSXML *) obj->getPrivate();
    if (name) {
        JSAtom *atomName = AtomizeString(cx, name);
        if (!atomName)
            return NULL;
        qn = NewXMLQName(cx, cx->runtime->emptyString, NULL, atomName);
        if (!qn)
            return NULL;
        xml->name = qn;
    }
    xml->xml_value = value;
    return obj;
}

JSString *
js_MakeXMLCDATAString(JSContext *cx, JSString *str)
{
    StringBuffer sb(cx);
    return MakeXMLCDATAString(cx, sb, str);
}

JSString *
js_MakeXMLCommentString(JSContext *cx, JSString *str)
{
    StringBuffer sb(cx);
    return MakeXMLCommentString(cx, sb, str);
}

JSString *
js_MakeXMLPIString(JSContext *cx, JSString *name, JSString *str)
{
    StringBuffer sb(cx);
    return MakeXMLPIString(cx, sb, name, str);
}

#endif /* JS_HAS_XML_SUPPORT */
