PageRenderTime 588ms CodeModel.GetById 27ms app.highlight 510ms RepoModel.GetById 1ms app.codeStats 2ms

/js/src/jsxml.cpp

http://github.com/zpao/v8monkey
C++ | 8109 lines | 6438 code | 936 blank | 735 comment | 1808 complexity | e16b88c7531ad81e8cde99e3c49fc788 MD5 | raw file
   1/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
   2 * vim: set ts=4 sw=4 et tw=78:
   3 *
   4 * ***** BEGIN LICENSE BLOCK *****
   5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
   6 *
   7 * The contents of this file are subject to the Mozilla Public License Version
   8 * 1.1 (the "License"); you may not use this file except in compliance with
   9 * the License. You may obtain a copy of the License at
  10 * http://www.mozilla.org/MPL/
  11 *
  12 * Software distributed under the License is distributed on an "AS IS" basis,
  13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14 * for the specific language governing rights and limitations under the
  15 * License.
  16 *
  17 * The Original Code is SpiderMonkey E4X code, released August, 2004.
  18 *
  19 * The Initial Developer of the Original Code is
  20 * Netscape Communications Corporation.
  21 * Portions created by the Initial Developer are Copyright (C) 1998
  22 * the Initial Developer. All Rights Reserved.
  23 *
  24 * Contributor(s):
  25 *
  26 * Alternatively, the contents of this file may be used under the terms of
  27 * either of the GNU General Public License Version 2 or later (the "GPL"),
  28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29 * in which case the provisions of the GPL or the LGPL are applicable instead
  30 * of those above. If you wish to allow use of your version of this file only
  31 * under the terms of either the GPL or the LGPL, and not to allow others to
  32 * use your version of this file under the terms of the MPL, indicate your
  33 * decision by deleting the provisions above and replace them with the notice
  34 * and other provisions required by the GPL or the LGPL. If you do not delete
  35 * the provisions above, a recipient may use your version of this file under
  36 * the terms of any one of the MPL, the GPL or the LGPL.
  37 *
  38 * ***** END LICENSE BLOCK ***** */
  39
  40#include "jsversion.h"
  41
  42#if JS_HAS_XML_SUPPORT
  43
  44#include <math.h>
  45#include <stdlib.h>
  46#include <string.h>
  47
  48#include "mozilla/Util.h"
  49
  50#include "jstypes.h"
  51#include "jsprf.h"
  52#include "jsutil.h"
  53#include "jsapi.h"
  54#include "jsarray.h"
  55#include "jsatom.h"
  56#include "jsbool.h"
  57#include "jscntxt.h"
  58#include "jsfun.h"
  59#include "jsgc.h"
  60#include "jsgcmark.h"
  61#include "jslock.h"
  62#include "jsnum.h"
  63#include "jsobj.h"
  64#include "jsopcode.h"
  65#include "jsscope.h"
  66#include "jsscript.h"
  67#include "jsstr.h"
  68#include "jsxml.h"
  69
  70#include "frontend/Parser.h"
  71#include "frontend/TokenStream.h"
  72#include "vm/GlobalObject.h"
  73
  74#include "jsatominlines.h"
  75#include "jsinferinlines.h"
  76#include "jsobjinlines.h"
  77#include "jsstrinlines.h"
  78
  79#include "vm/Stack-inl.h"
  80#include "vm/String-inl.h"
  81
  82#ifdef DEBUG
  83#include <string.h>     /* for #ifdef DEBUG memset calls */
  84#endif
  85
  86using namespace mozilla;
  87using namespace js;
  88using namespace js::gc;
  89using namespace js::types;
  90
  91template<class T, class U>
  92struct IdentityOp
  93{
  94    typedef JSBool (* compare)(const T *a, const U *b);
  95};
  96
  97template<class T>
  98static JSBool
  99pointer_match(const T *a, const T *b)
 100{
 101    return a == b;
 102}
 103
 104/*
 105 * NOTES
 106 * - in the js shell, you must use the -x command line option, or call
 107 *   options('xml') before compiling anything that uses XML literals
 108 *
 109 * TODO
 110 * - XXXbe patrol
 111 * - Fuse objects and their JSXML* private data into single GC-things
 112 * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
 113 * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
 114 */
 115
 116static inline bool
 117js_EnterLocalRootScope(JSContext *cx)
 118{
 119    return true;
 120}
 121
 122static inline void
 123js_LeaveLocalRootScope(JSContext *cx)
 124{
 125}
 126
 127static inline void
 128js_LeaveLocalRootScopeWithResult(JSContext *cx, Value rval)
 129{
 130}
 131
 132static inline void
 133js_LeaveLocalRootScopeWithResult(JSContext *cx, void *rval)
 134{
 135}
 136
 137/*
 138 * Random utilities and global functions.
 139 */
 140const char js_AttributeName_str[] = "AttributeName";
 141const char js_isXMLName_str[]     = "isXMLName";
 142const char js_XMLList_str[]       = "XMLList";
 143const char js_localName_str[]     = "localName";
 144const char js_xml_parent_str[]    = "parent";
 145const char js_prefix_str[]        = "prefix";
 146const char js_toXMLString_str[]   = "toXMLString";
 147const char js_uri_str[]           = "uri";
 148
 149const char js_amp_entity_str[]    = "&amp;";
 150const char js_gt_entity_str[]     = "&gt;";
 151const char js_lt_entity_str[]     = "&lt;";
 152const char js_quot_entity_str[]   = "&quot;";
 153const char js_leftcurly_entity_str[]   = "&#123;";
 154
 155#define IS_STAR(str)  ((str)->length() == 1 && *(str)->chars() == '*')
 156
 157static JSBool
 158GetXMLFunction(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
 159
 160static JSBool
 161IsDeclared(const JSObject *obj)
 162{
 163    jsval v;
 164
 165    JS_ASSERT(obj->getClass() == &NamespaceClass);
 166    v = obj->getNamespaceDeclared();
 167    JS_ASSERT(JSVAL_IS_VOID(v) || v == JSVAL_TRUE);
 168    return v == JSVAL_TRUE;
 169}
 170
 171static JSBool
 172xml_isXMLName(JSContext *cx, uintN argc, jsval *vp)
 173{
 174    *vp = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argc ? vp[2] : JSVAL_VOID));
 175    return JS_TRUE;
 176}
 177
 178size_t sE4XObjectsCreated = 0;
 179
 180/*
 181 * This wrapper is needed because NewBuiltinClassInstance doesn't
 182 * call the constructor, and we need a place to set the
 183 * HAS_EQUALITY bit.
 184 */
 185static inline JSObject *
 186NewBuiltinClassInstanceXML(JSContext *cx, Class *clasp)
 187{
 188    if (!cx->runningWithTrustedPrincipals())
 189        ++sE4XObjectsCreated;
 190
 191    return NewBuiltinClassInstance(cx, clasp);
 192}
 193
 194#define DEFINE_GETTER(name,code)                                               \
 195    static JSBool                                                              \
 196    name(JSContext *cx, JSObject *obj, jsid id, jsval *vp)                     \
 197    {                                                                          \
 198        code;                                                                  \
 199        return true;                                                           \
 200    }
 201
 202/*
 203 * Namespace class and library functions.
 204 */
 205DEFINE_GETTER(NamePrefix_getter,
 206              if (obj->getClass() == &NamespaceClass) *vp = obj->getNamePrefixVal())
 207DEFINE_GETTER(NameURI_getter,
 208              if (obj->getClass() == &NamespaceClass) *vp = obj->getNameURIVal())
 209
 210static JSBool
 211namespace_equality(JSContext *cx, JSObject *obj, const Value *v, JSBool *bp)
 212{
 213    JSObject *obj2;
 214
 215    JS_ASSERT(v->isObjectOrNull());
 216    obj2 = v->toObjectOrNull();
 217    *bp = (!obj2 || obj2->getClass() != &NamespaceClass)
 218          ? JS_FALSE
 219          : EqualStrings(obj->getNameURI(), obj2->getNameURI());
 220    return JS_TRUE;
 221}
 222
 223JS_FRIEND_DATA(Class) js::NamespaceClass = {
 224    "Namespace",
 225    JSCLASS_HAS_RESERVED_SLOTS(JSObject::NAMESPACE_CLASS_RESERVED_SLOTS) |
 226    JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
 227    JS_PropertyStub,         /* addProperty */
 228    JS_PropertyStub,         /* delProperty */
 229    JS_PropertyStub,         /* getProperty */
 230    JS_StrictPropertyStub,   /* setProperty */
 231    JS_EnumerateStub,
 232    JS_ResolveStub,
 233    JS_ConvertStub,
 234    JS_FinalizeStub,
 235    NULL,                    /* reserved0   */
 236    NULL,                    /* checkAccess */
 237    NULL,                    /* call        */
 238    NULL,                    /* construct   */
 239    NULL,                    /* xdrObject   */
 240    NULL,                    /* hasInstance */
 241    NULL,                    /* mark        */
 242    {
 243        namespace_equality,
 244        NULL,                /* outerObject    */
 245        NULL,                /* innerObject    */
 246        NULL,                /* iteratorObject */
 247        NULL,                /* wrappedObject  */
 248    }
 249};
 250
 251#define NAMESPACE_ATTRS                                                       \
 252    (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
 253
 254static JSPropertySpec namespace_props[] = {
 255    {js_prefix_str, 0, NAMESPACE_ATTRS, NamePrefix_getter, 0},
 256    {js_uri_str,    0, NAMESPACE_ATTRS, NameURI_getter,    0},
 257    {0,0,0,0,0}
 258};
 259
 260static JSBool
 261namespace_toString(JSContext *cx, uintN argc, Value *vp)
 262{
 263    JSObject *obj = ToObject(cx, &vp[1]);
 264    if (!obj)
 265        return JS_FALSE;
 266    if (!obj->isNamespace()) {
 267        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &NamespaceClass);
 268        return JS_FALSE;
 269    }
 270    *vp = obj->getNameURIVal();
 271    return JS_TRUE;
 272}
 273
 274static JSFunctionSpec namespace_methods[] = {
 275    JS_FN(js_toString_str,  namespace_toString,        0,0),
 276    JS_FS_END
 277};
 278
 279static JSObject *
 280NewXMLNamespace(JSContext *cx, JSLinearString *prefix, JSLinearString *uri, JSBool declared)
 281{
 282    JSObject *obj;
 283
 284    obj = NewBuiltinClassInstanceXML(cx, &NamespaceClass);
 285    if (!obj)
 286        return NULL;
 287    JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefixVal()));
 288    JS_ASSERT(JSVAL_IS_VOID(obj->getNameURIVal()));
 289    JS_ASSERT(JSVAL_IS_VOID(obj->getNamespaceDeclared()));
 290
 291    /* Per ECMA-357, 13.2.5, these properties must be "own". */
 292    if (!JS_DefineProperties(cx, obj, namespace_props))
 293        return NULL;
 294
 295    if (prefix)
 296        obj->setNamePrefix(prefix);
 297    if (uri)
 298        obj->setNameURI(uri);
 299    if (declared)
 300        obj->setNamespaceDeclared(JSVAL_TRUE);
 301    return obj;
 302}
 303
 304/*
 305 * QName class and library functions.
 306 */
 307DEFINE_GETTER(QNameNameURI_getter,
 308              if (obj->getClass() == &QNameClass)
 309                  *vp = JSVAL_IS_VOID(obj->getNameURIVal()) ? JSVAL_NULL : obj->getNameURIVal())
 310DEFINE_GETTER(QNameLocalName_getter,
 311              if (obj->getClass() == &QNameClass)
 312                  *vp = obj->getQNameLocalNameVal())
 313
 314static JSBool
 315qname_identity(JSObject *qna, const JSObject *qnb)
 316{
 317    JSLinearString *uri1 = qna->getNameURI();
 318    JSLinearString *uri2 = qnb->getNameURI();
 319
 320    if (!uri1 ^ !uri2)
 321        return JS_FALSE;
 322    if (uri1 && !EqualStrings(uri1, uri2))
 323        return JS_FALSE;
 324    return EqualStrings(qna->getQNameLocalName(), qnb->getQNameLocalName());
 325}
 326
 327static JSBool
 328qname_equality(JSContext *cx, JSObject *qn, const Value *v, JSBool *bp)
 329{
 330    JSObject *obj2;
 331
 332    obj2 = v->toObjectOrNull();
 333    *bp = (!obj2 || obj2->getClass() != &QNameClass)
 334          ? JS_FALSE
 335          : qname_identity(qn, obj2);
 336    return JS_TRUE;
 337}
 338
 339JS_FRIEND_DATA(Class) js::QNameClass = {
 340    "QName",
 341    JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
 342    JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
 343    JS_PropertyStub,         /* addProperty */
 344    JS_PropertyStub,         /* delProperty */
 345    JS_PropertyStub,         /* getProperty */
 346    JS_StrictPropertyStub,   /* setProperty */
 347    JS_EnumerateStub,
 348    JS_ResolveStub,
 349    JS_ConvertStub,
 350    JS_FinalizeStub,
 351    NULL,                    /* reserved0   */
 352    NULL,                    /* checkAccess */
 353    NULL,                    /* call        */
 354    NULL,                    /* construct   */
 355    NULL,                    /* xdrObject   */
 356    NULL,                    /* hasInstance */
 357    NULL,                    /* mark        */
 358    {
 359        qname_equality,
 360        NULL,                /* outerObject    */
 361        NULL,                /* innerObject    */
 362        NULL,                /* iteratorObject */
 363        NULL,                /* wrappedObject  */
 364    }
 365};
 366
 367/*
 368 * Classes for the ECMA-357-internal types AttributeName and AnyName, which
 369 * are like QName, except that they have no property getters.  They share the
 370 * qname_toString method, and therefore are exposed as constructable objects
 371 * in this implementation.
 372 */
 373JS_FRIEND_DATA(Class) js::AttributeNameClass = {
 374    js_AttributeName_str,
 375    JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
 376    JSCLASS_IS_ANONYMOUS,
 377    JS_PropertyStub,         /* addProperty */
 378    JS_PropertyStub,         /* delProperty */
 379    JS_PropertyStub,         /* getProperty */
 380    JS_StrictPropertyStub,   /* setProperty */
 381    JS_EnumerateStub,
 382    JS_ResolveStub,
 383    JS_ConvertStub,
 384    JS_FinalizeStub
 385};
 386
 387JS_FRIEND_DATA(Class) js::AnyNameClass = {
 388    js_AnyName_str,
 389    JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
 390    JSCLASS_IS_ANONYMOUS,
 391    JS_PropertyStub,         /* addProperty */
 392    JS_PropertyStub,         /* delProperty */
 393    JS_PropertyStub,         /* getProperty */
 394    JS_StrictPropertyStub,   /* setProperty */
 395    JS_EnumerateStub,
 396    JS_ResolveStub,
 397    JS_ConvertStub,
 398    JS_FinalizeStub
 399};
 400
 401#define QNAME_ATTRS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
 402
 403static JSPropertySpec qname_props[] = {
 404    {js_uri_str,       0, QNAME_ATTRS, QNameNameURI_getter,   0},
 405    {js_localName_str, 0, QNAME_ATTRS, QNameLocalName_getter, 0},
 406    {0,0,0,0,0}
 407};
 408
 409static JSString *
 410ConvertQNameToString(JSContext *cx, JSObject *obj)
 411{
 412    JS_ASSERT(obj->isQName());
 413    JSString *uri = obj->getNameURI();
 414    JSString *str;
 415    if (!uri) {
 416        /* No uri means wildcard qualifier. */
 417        str = cx->runtime->atomState.starQualifierAtom;
 418    } else if (uri->empty()) {
 419        /* Empty string for uri means localName is in no namespace. */
 420        str = cx->runtime->emptyString;
 421    } else {
 422        JSString *qualstr = cx->runtime->atomState.qualifierAtom;
 423        str = js_ConcatStrings(cx, uri, qualstr);
 424        if (!str)
 425            return NULL;
 426    }
 427    str = js_ConcatStrings(cx, str, obj->getQNameLocalName());
 428    if (!str)
 429        return NULL;
 430
 431    if (obj->getClass() == &AttributeNameClass) {
 432        JS::Anchor<JSString *> anchor(str);
 433        size_t length = str->length();
 434        jschar *chars = (jschar *) cx->malloc_((length + 2) * sizeof(jschar));
 435        if (!chars)
 436            return JS_FALSE;
 437        *chars = '@';
 438        const jschar *strChars = str->getChars(cx);
 439        if (!strChars) {
 440            cx->free_(chars);
 441            return NULL;
 442        }
 443        js_strncpy(chars + 1, strChars, length);
 444        chars[++length] = 0;
 445        str = js_NewString(cx, chars, length);
 446        if (!str) {
 447            cx->free_(chars);
 448            return NULL;
 449        }
 450    }
 451    return str;
 452}
 453
 454static JSBool
 455qname_toString(JSContext *cx, uintN argc, Value *vp)
 456{
 457    JSObject *obj = ToObject(cx, &vp[1]);
 458    if (!obj)
 459        return false;
 460
 461    if (!obj->isQName()) {
 462        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &QNameClass);
 463        return false;
 464    }
 465
 466    JSString *str = ConvertQNameToString(cx, obj);
 467    if (!str)
 468        return false;
 469
 470    vp->setString(str);
 471    return true;
 472}
 473
 474static JSFunctionSpec qname_methods[] = {
 475    JS_FN(js_toString_str,  qname_toString,    0,0),
 476    JS_FS_END
 477};
 478
 479
 480static bool
 481InitXMLQName(JSContext *cx, JSObject *obj, JSLinearString *uri, JSLinearString *prefix,
 482             JSAtom *localName)
 483{
 484    JS_ASSERT(obj->isQName());
 485    JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefixVal()));
 486    JS_ASSERT(JSVAL_IS_VOID(obj->getNameURIVal()));
 487    JS_ASSERT(JSVAL_IS_VOID(obj->getQNameLocalNameVal()));
 488
 489    /* Per ECMA-357, 13.3.5, these properties must be "own". */
 490    if (!JS_DefineProperties(cx, obj, qname_props))
 491        return false;
 492
 493    if (uri)
 494        obj->setNameURI(uri);
 495    if (prefix)
 496        obj->setNamePrefix(prefix);
 497    if (localName)
 498        obj->setQNameLocalName(localName);
 499    return true;
 500}
 501
 502static JSObject *
 503NewXMLQName(JSContext *cx, JSLinearString *uri, JSLinearString *prefix,
 504            JSAtom *localName)
 505{
 506    JSObject *obj = NewBuiltinClassInstanceXML(cx, &QNameClass);
 507    if (!obj)
 508        return NULL;
 509    if (!InitXMLQName(cx, obj, uri, prefix, localName))
 510        return NULL;
 511    return obj;
 512}
 513
 514static JSObject *
 515NewXMLAttributeName(JSContext *cx, JSLinearString *uri, JSLinearString *prefix,
 516                    JSAtom *localName)
 517{
 518    /*
 519     * AttributeName is an internal anonymous class which instances are not
 520     * exposed to scripts.
 521     */
 522    JSObject *parent = GetGlobalForScopeChain(cx);
 523    JSObject *obj = NewObjectWithGivenProto(cx, &AttributeNameClass, NULL, parent);
 524    if (!obj)
 525        return NULL;
 526    JS_ASSERT(obj->isQName());
 527    if (!InitXMLQName(cx, obj, uri, prefix, localName))
 528        return NULL;
 529    return obj;
 530}
 531
 532JSObject *
 533js_ConstructXMLQNameObject(JSContext *cx, const Value &nsval, const Value &lnval)
 534{
 535    Value argv[2];
 536
 537    /*
 538     * ECMA-357 11.1.2,
 539     * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
 540     * production, step 2.
 541     */
 542    if (nsval.isObject() &&
 543        nsval.toObject().getClass() == &AnyNameClass) {
 544        argv[0].setNull();
 545    } else {
 546        argv[0] = nsval;
 547    }
 548    argv[1] = lnval;
 549    return JS_ConstructObjectWithArguments(cx, Jsvalify(&QNameClass), NULL, 2, argv);
 550}
 551
 552static JSBool
 553IsXMLName(const jschar *cp, size_t n)
 554{
 555    JSBool rv;
 556    jschar c;
 557
 558    rv = JS_FALSE;
 559    if (n != 0 && unicode::IsXMLNamespaceStart(*cp)) {
 560        while (--n != 0) {
 561            c = *++cp;
 562            if (!unicode::IsXMLNamespacePart(c))
 563                return rv;
 564        }
 565        rv = JS_TRUE;
 566    }
 567    return rv;
 568}
 569
 570JSBool
 571js_IsXMLName(JSContext *cx, jsval v)
 572{
 573    JSLinearString *name = NULL;
 574    JSErrorReporter older;
 575
 576    /*
 577     * Inline specialization of the QName constructor called with v passed as
 578     * the only argument, to compute the localName for the constructed qname,
 579     * without actually allocating the object or computing its uri and prefix.
 580     * See ECMA-357 13.1.2.1 step 1 and 13.3.2.
 581     */
 582    if (!JSVAL_IS_PRIMITIVE(v) &&
 583        JSVAL_TO_OBJECT(v)->isQName()) {
 584        name = JSVAL_TO_OBJECT(v)->getQNameLocalName();
 585    } else {
 586        older = JS_SetErrorReporter(cx, NULL);
 587        JSString *str = ToString(cx, v);
 588        if (str)
 589            name = str->ensureLinear(cx);
 590        JS_SetErrorReporter(cx, older);
 591        if (!name) {
 592            JS_ClearPendingException(cx);
 593            return JS_FALSE;
 594        }
 595    }
 596
 597    return IsXMLName(name->chars(), name->length());
 598}
 599
 600/*
 601 * When argc is -1, it indicates argv is empty but the code should behave as
 602 * if argc is 1 and argv[0] is JSVAL_VOID.
 603 */
 604static JSBool
 605NamespaceHelper(JSContext *cx, intN argc, jsval *argv, jsval *rval)
 606{
 607    jsval urival, prefixval;
 608    JSObject *uriobj;
 609    JSBool isNamespace, isQName;
 610    Class *clasp;
 611    JSLinearString *empty, *prefix, *uri;
 612
 613    isNamespace = isQName = JS_FALSE;
 614#ifdef __GNUC__         /* suppress bogus gcc warnings */
 615    uriobj = NULL;
 616#endif
 617    if (argc <= 0) {
 618        urival = JSVAL_VOID;
 619    } else {
 620        urival = argv[argc > 1];
 621        if (!JSVAL_IS_PRIMITIVE(urival)) {
 622            uriobj = JSVAL_TO_OBJECT(urival);
 623            clasp = uriobj->getClass();
 624            isNamespace = (clasp == &NamespaceClass);
 625            isQName = (clasp == &QNameClass);
 626        }
 627    }
 628
 629    /* Namespace called as function. */
 630    if (argc == 1 && isNamespace) {
 631        /* Namespace called with one Namespace argument is identity. */
 632        *rval = urival;
 633        return JS_TRUE;
 634    }
 635
 636    JSObject *obj = NewBuiltinClassInstanceXML(cx, &NamespaceClass);
 637    if (!obj)
 638        return JS_FALSE;
 639
 640    /* Per ECMA-357, 13.2.5, these properties must be "own". */
 641    if (!JS_DefineProperties(cx, obj, namespace_props))
 642        return JS_FALSE;
 643
 644    *rval = OBJECT_TO_JSVAL(obj);
 645
 646    empty = cx->runtime->emptyString;
 647    obj->setNamePrefix(empty);
 648    obj->setNameURI(empty);
 649
 650    if (argc == 1 || argc == -1) {
 651        if (isNamespace) {
 652            obj->setNameURI(uriobj->getNameURI());
 653            obj->setNamePrefix(uriobj->getNamePrefix());
 654        } else if (isQName && (uri = uriobj->getNameURI())) {
 655            obj->setNameURI(uri);
 656            obj->setNamePrefix(uriobj->getNamePrefix());
 657        } else {
 658            JSString *str = ToString(cx, urival);
 659            if (!str)
 660                return JS_FALSE;
 661            uri = str->ensureLinear(cx);
 662            if (!uri)
 663                return JS_FALSE;
 664            obj->setNameURI(uri);
 665            if (!uri->empty())
 666                obj->clearNamePrefix();
 667        }
 668    } else if (argc == 2) {
 669        if (!isQName || !(uri = uriobj->getNameURI())) {
 670            JSString *str = ToString(cx, urival);
 671            if (!str)
 672                return JS_FALSE;
 673            uri = str->ensureLinear(cx);
 674            if (!uri)
 675                return JS_FALSE;
 676        }
 677        obj->setNameURI(uri);
 678
 679        prefixval = argv[0];
 680        if (uri->empty()) {
 681            if (!JSVAL_IS_VOID(prefixval)) {
 682                JSString *str = ToString(cx, prefixval);
 683                if (!str)
 684                    return JS_FALSE;
 685                if (!str->empty()) {
 686                    JSAutoByteString bytes;
 687                    if (js_ValueToPrintable(cx, StringValue(str), &bytes)) {
 688                        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
 689                                             JSMSG_BAD_XML_NAMESPACE, bytes.ptr());
 690                    }
 691                    return JS_FALSE;
 692                }
 693            }
 694        } else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
 695            obj->clearNamePrefix();
 696        } else {
 697            JSString *str = ToString(cx, prefixval);
 698            if (!str)
 699                return JS_FALSE;
 700            prefix = str->ensureLinear(cx);
 701            if (!prefix)
 702                return JS_FALSE;
 703            obj->setNamePrefix(prefix);
 704        }
 705    }
 706    return JS_TRUE;
 707}
 708
 709static JSBool
 710Namespace(JSContext *cx, uintN argc, Value *vp)
 711{
 712    return NamespaceHelper(cx, argc, vp + 2, vp);
 713}
 714
 715/*
 716 * When argc is -1, it indicates argv is empty but the code should behave as
 717 * if argc is 1 and argv[0] is JSVAL_VOID.
 718 */
 719static JSBool
 720QNameHelper(JSContext *cx, intN argc, jsval *argv, jsval *rval)
 721{
 722    jsval nameval, nsval;
 723    JSBool isQName, isNamespace;
 724    JSObject *qn;
 725    JSLinearString *uri, *prefix;
 726    JSObject *obj2;
 727
 728    JSAtom *name;
 729    if (argc <= 0) {
 730        nameval = JSVAL_VOID;
 731        isQName = JS_FALSE;
 732    } else {
 733        nameval = argv[argc > 1];
 734        isQName =
 735            !JSVAL_IS_PRIMITIVE(nameval) &&
 736            JSVAL_TO_OBJECT(nameval)->getClass() == &QNameClass;
 737    }
 738
 739    /* QName called as function. */
 740    if (argc == 1 && isQName) {
 741        /* QName called with one QName argument is identity. */
 742        *rval = nameval;
 743        return JS_TRUE;
 744    }
 745
 746        /* Create and return a new QName object exactly as if constructed. */
 747    JSObject *obj = NewBuiltinClassInstanceXML(cx, &QNameClass);
 748    if (!obj)
 749        return JS_FALSE;
 750    *rval = OBJECT_TO_JSVAL(obj);
 751
 752    if (isQName) {
 753        /* If namespace is not specified and name is a QName, clone it. */
 754        qn = JSVAL_TO_OBJECT(nameval);
 755        if (argc == 1) {
 756            uri = qn->getNameURI();
 757            prefix = qn->getNamePrefix();
 758            name = qn->getQNameLocalName();
 759            goto out;
 760        }
 761
 762        /* Namespace and qname were passed -- use the qname's localName. */
 763        nameval = qn->getQNameLocalNameVal();
 764    }
 765
 766    if (argc == 0) {
 767        name = cx->runtime->emptyString;
 768    } else if (argc < 0) {
 769        name = cx->runtime->atomState.typeAtoms[JSTYPE_VOID];
 770    } else {
 771        if (!js_ValueToAtom(cx, nameval, &name))
 772            return false;
 773    }
 774
 775    if (argc > 1 && !JSVAL_IS_VOID(argv[0])) {
 776        nsval = argv[0];
 777    } else if (IS_STAR(name)) {
 778        nsval = JSVAL_NULL;
 779    } else {
 780        if (!js_GetDefaultXMLNamespace(cx, &nsval))
 781            return JS_FALSE;
 782        JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
 783        JS_ASSERT(JSVAL_TO_OBJECT(nsval)->getClass() ==
 784                  &NamespaceClass);
 785    }
 786
 787    if (JSVAL_IS_NULL(nsval)) {
 788        /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
 789        prefix = uri = NULL;
 790    } else {
 791        /*
 792         * Inline specialization of the Namespace constructor called with
 793         * nsval passed as the only argument, to compute the uri and prefix
 794         * for the constructed namespace, without actually allocating the
 795         * object or computing other members.  See ECMA-357 13.3.2 6(a) and
 796         * 13.2.2.
 797         */
 798        isNamespace = isQName = JS_FALSE;
 799        if (!JSVAL_IS_PRIMITIVE(nsval)) {
 800            obj2 = JSVAL_TO_OBJECT(nsval);
 801            isNamespace = (obj2->getClass() == &NamespaceClass);
 802            isQName = (obj2->getClass() == &QNameClass);
 803        }
 804#ifdef __GNUC__         /* suppress bogus gcc warnings */
 805        else obj2 = NULL;
 806#endif
 807
 808        if (isNamespace) {
 809            uri = obj2->getNameURI();
 810            prefix = obj2->getNamePrefix();
 811        } else if (isQName && (uri = obj2->getNameURI())) {
 812            JS_ASSERT(argc > 1);
 813            prefix = obj2->getNamePrefix();
 814        } else {
 815            JS_ASSERT(argc > 1);
 816            JSString *str = ToString(cx, nsval);
 817            if (!str)
 818                return JS_FALSE;
 819            uri = str->ensureLinear(cx);
 820            if (!uri)
 821                return JS_FALSE;
 822            argv[0] = STRING_TO_JSVAL(uri);     /* local root */
 823
 824            /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
 825            prefix = uri->empty() ? cx->runtime->emptyString : NULL;
 826        }
 827    }
 828
 829out:
 830    return InitXMLQName(cx, obj, uri, prefix, name);
 831}
 832
 833static JSBool
 834QName(JSContext *cx, uintN argc, Value *vp)
 835{
 836    return QNameHelper(cx, argc, vp + 2, vp);
 837}
 838
 839/*
 840 * XMLArray library functions.
 841 */
 842static JSBool
 843namespace_identity(const JSObject *nsa, const JSObject *nsb)
 844{
 845    JSLinearString *prefixa = nsa->getNamePrefix();
 846    JSLinearString *prefixb = nsb->getNamePrefix();
 847
 848    if (prefixa && prefixb) {
 849        if (!EqualStrings(prefixa, prefixb))
 850            return JS_FALSE;
 851    } else {
 852        if (prefixa || prefixb)
 853            return JS_FALSE;
 854    }
 855    return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
 856}
 857
 858static JSBool
 859attr_identity(const JSXML *xmla, const JSXML *xmlb)
 860{
 861    return qname_identity(xmla->name, xmlb->name);
 862}
 863
 864void
 865js_XMLArrayCursorTrace(JSTracer *trc, JSXMLArrayCursor<JSXML> *cursor)
 866{
 867    for (; cursor; cursor = cursor->next) {
 868        if (cursor->root)
 869            MarkXML(trc, (const HeapPtr<JSXML> &)cursor->root, "cursor_root");
 870    }
 871}
 872
 873void
 874js_XMLArrayCursorTrace(JSTracer *trc, JSXMLArrayCursor<JSObject> *cursor)
 875{
 876    for (; cursor; cursor = cursor->next) {
 877        if (cursor->root)
 878            MarkObject(trc, (const HeapPtr<JSObject> &)cursor->root, "cursor_root");
 879    }
 880}
 881
 882template<class T>
 883static HeapPtr<T> *
 884ReallocateVector(HeapPtr<T> *vector, size_t count)
 885{
 886#if JS_BITS_PER_WORD == 32
 887    if (count > ~(size_t)0 / sizeof(HeapPtr<T>))
 888        return NULL;
 889#endif
 890
 891    size_t size = count * sizeof(HeapPtr<T>);
 892    return (HeapPtr<T> *) OffTheBooks::realloc_(vector, size);
 893}
 894
 895/* NB: called with null cx from the GC, via xml_trace => JSXMLArray::trim. */
 896template<class T>
 897bool
 898JSXMLArray<T>::setCapacity(JSContext *cx, uint32_t newCapacity)
 899{
 900    if (newCapacity == 0) {
 901        /* We could let realloc(p, 0) free this, but purify gets confused. */
 902        if (vector) {
 903            if (cx)
 904                cx->free_(vector);
 905            else
 906                Foreground::free_(vector);
 907        }
 908        vector = NULL;
 909    } else {
 910        HeapPtr<T> *tmp = ReallocateVector(vector, newCapacity);
 911        if (!tmp) {
 912            if (cx)
 913                JS_ReportOutOfMemory(cx);
 914            return false;
 915        }
 916        vector = tmp;
 917    }
 918    capacity = JSXML_PRESET_CAPACITY | newCapacity;
 919    return true;
 920}
 921
 922template<class T>
 923void
 924JSXMLArray<T>::trim()
 925{
 926    if (capacity & JSXML_PRESET_CAPACITY)
 927        return;
 928    if (length < capacity)
 929        setCapacity(NULL, length);
 930}
 931
 932template<class T>
 933void
 934JSXMLArray<T>::finish(JSContext *cx)
 935{
 936    if (!cx->runtime->gcRunning) {
 937        /* We need to clear these to trigger a write barrier. */
 938        for (uint32_t i = 0; i < length; i++)
 939            vector[i].~HeapPtr<T>();
 940    }
 941
 942    cx->free_(vector);
 943
 944    while (JSXMLArrayCursor<T> *cursor = cursors)
 945        cursor->disconnect();
 946
 947#ifdef DEBUG
 948    memset(this, 0xd5, sizeof *this);
 949#endif
 950}
 951
 952#define XML_NOT_FOUND   UINT32_MAX
 953
 954template<class T, class U>
 955static uint32_t
 956XMLArrayFindMember(const JSXMLArray<T> *array, U *elt, typename IdentityOp<T, U>::compare identity)
 957{
 958    HeapPtr<T> *vector;
 959    uint32_t i, n;
 960
 961    /* The identity op must not reallocate array->vector. */
 962    vector = array->vector;
 963    for (i = 0, n = array->length; i < n; i++) {
 964        if (identity(vector[i].get(), elt))
 965            return i;
 966    }
 967    return XML_NOT_FOUND;
 968}
 969
 970/*
 971 * Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
 972 * that, grow by LINEAR_INCREMENT.  Both must be powers of two, and threshold
 973 * should be greater than increment.
 974 */
 975#define LINEAR_THRESHOLD        256
 976#define LINEAR_INCREMENT        32
 977
 978template<class T>
 979static JSBool
 980XMLArrayAddMember(JSContext *cx, JSXMLArray<T> *array, uint32_t index, T *elt)
 981{
 982    uint32_t capacity, i;
 983    int log2;
 984    HeapPtr<T> *vector;
 985
 986    if (index >= array->length) {
 987        if (index >= JSXML_CAPACITY(array)) {
 988            /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
 989            capacity = index + 1;
 990            if (index >= LINEAR_THRESHOLD) {
 991                capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
 992            } else {
 993                JS_CEILING_LOG2(log2, capacity);
 994                capacity = JS_BIT(log2);
 995            }
 996            if (!(vector = ReallocateVector(array->vector, capacity))) {
 997                JS_ReportOutOfMemory(cx);
 998                return JS_FALSE;
 999            }
1000            array->capacity = capacity;
1001            array->vector = vector;
1002            for (i = array->length; i < index; i++)
1003                vector[i].init(NULL);
1004        }
1005        array->vector[index].init(NULL);
1006        array->length = index + 1;
1007    }
1008
1009    array->vector[index] = elt;
1010    return JS_TRUE;
1011}
1012
1013template<class T>
1014static JSBool
1015XMLArrayInsert(JSContext *cx, JSXMLArray<T> *array, uint32_t i, uint32_t n)
1016{
1017    uint32_t j, k;
1018    JSXMLArrayCursor<T> *cursor;
1019
1020    j = array->length;
1021    JS_ASSERT(i <= j);
1022    if (!array->setCapacity(cx, j + n))
1023        return JS_FALSE;
1024
1025    k = j;
1026    while (k != j + n) {
1027        array->vector[k].init(NULL);
1028        k++;
1029    }
1030
1031    array->length = j + n;
1032    JS_ASSERT(n != (uint32_t)-1);
1033    while (j != i) {
1034        --j;
1035        array->vector[j + n] = array->vector[j];
1036    }
1037
1038    for (cursor = array->cursors; cursor; cursor = cursor->next) {
1039        if (cursor->index > i)
1040            cursor->index += n;
1041    }
1042    return JS_TRUE;
1043}
1044
1045template<class T>
1046static T *
1047XMLArrayDelete(JSContext *cx, JSXMLArray<T> *array, uint32_t index, JSBool compress)
1048{
1049    uint32_t length;
1050    HeapPtr<T> *vector;
1051    T *elt;
1052    JSXMLArrayCursor<T> *cursor;
1053
1054    length = array->length;
1055    if (index >= length)
1056        return NULL;
1057
1058    vector = array->vector;
1059    elt = vector[index];
1060    if (compress) {
1061        vector[length - 1].~HeapPtr<T>();
1062        while (++index < length)
1063            vector[index-1] = vector[index];
1064        array->length = length - 1;
1065        array->capacity = JSXML_CAPACITY(array);
1066    } else {
1067        vector[index] = NULL;
1068    }
1069
1070    for (cursor = array->cursors; cursor; cursor = cursor->next) {
1071        if (cursor->index > index)
1072            --cursor->index;
1073    }
1074    return elt;
1075}
1076
1077template<class T>
1078static void
1079XMLArrayTruncate(JSContext *cx, JSXMLArray<T> *array, uint32_t length)
1080{
1081    HeapPtr<T> *vector;
1082
1083    JS_ASSERT(!array->cursors);
1084    if (length >= array->length)
1085        return;
1086
1087    for (uint32_t i = length; i < array->length; i++)
1088        array->vector[i].~HeapPtr<T>();
1089
1090    if (length == 0) {
1091        if (array->vector)
1092            cx->free_(array->vector);
1093        vector = NULL;
1094    } else {
1095        vector = ReallocateVector(array->vector, length);
1096        if (!vector)
1097            return;
1098    }
1099
1100    if (array->length > length)
1101        array->length = length;
1102    array->capacity = length;
1103    array->vector = vector;
1104}
1105
1106#define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, e, f)
1107#define XMLARRAY_HAS_MEMBER(a,e,f)  (XMLArrayFindMember(a, e, f) !=           \
1108                                     XML_NOT_FOUND)
1109#define XMLARRAY_MEMBER(a,i,t)      (((i) < (a)->length)                      \
1110                                     ? (a)->vector[i].get()                   \
1111                                     : NULL)
1112#define XMLARRAY_SET_MEMBER(a,i,e)  JS_BEGIN_MACRO                            \
1113                                        if ((a)->length <= (i)) {             \
1114                                            (a)->length = (i) + 1;            \
1115                                            ((a)->vector[i].init(e));         \
1116                                        } else {                              \
1117                                            ((a)->vector[i] = e);             \
1118                                        }                                     \
1119                                    JS_END_MACRO
1120#define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, e)
1121#define XMLARRAY_INSERT(x,a,i,n)    XMLArrayInsert(x, a, i, n)
1122#define XMLARRAY_APPEND(x,a,e)      XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
1123#define XMLARRAY_DELETE(x,a,i,c,t)  (XMLArrayDelete<t>(x, a, i, c))
1124#define XMLARRAY_TRUNCATE(x,a,n)    XMLArrayTruncate(x, a, n)
1125
1126/*
1127 * Define XML setting property strings and constants early, so everyone can
1128 * use the same names.
1129 */
1130static const char js_ignoreComments_str[]   = "ignoreComments";
1131static const char js_ignoreProcessingInstructions_str[]
1132                                            = "ignoreProcessingInstructions";
1133static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
1134static const char js_prettyPrinting_str[]   = "prettyPrinting";
1135static const char js_prettyIndent_str[]     = "prettyIndent";
1136
1137#define XSF_IGNORE_COMMENTS                JS_BIT(0)
1138#define XSF_IGNORE_PROCESSING_INSTRUCTIONS JS_BIT(1)
1139#define XSF_IGNORE_WHITESPACE              JS_BIT(2)
1140#define XSF_PRETTY_PRINTING                JS_BIT(3)
1141
1142static JSPropertySpec xml_static_props[] = {
1143    {js_ignoreComments_str, 0, JSPROP_PERMANENT, NULL, NULL},
1144    {js_ignoreProcessingInstructions_str, 0, JSPROP_PERMANENT, NULL, NULL},
1145    {js_ignoreWhitespace_str, 0, JSPROP_PERMANENT, NULL, NULL},
1146    {js_prettyPrinting_str, 0, JSPROP_PERMANENT, NULL, NULL},
1147    {js_prettyIndent_str, 0, JSPROP_PERMANENT, NULL, NULL},
1148    {0,0,0,0,0}
1149};
1150
1151/* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
1152#define IS_XML(str)                                                           \
1153    (str->length() == 3 && IS_XML_CHARS(str->chars()))
1154
1155#define IS_XMLNS(str)                                                         \
1156    (str->length() == 5 && IS_XMLNS_CHARS(str->chars()))
1157
1158static inline bool
1159IS_XML_CHARS(const jschar *chars)
1160{
1161    return (chars[0] == 'x' || chars[0] == 'X') &&
1162           (chars[1] == 'm' || chars[1] == 'M') &&
1163           (chars[2] == 'l' || chars[2] == 'L');
1164}
1165
1166static inline bool
1167HAS_NS_AFTER_XML(const jschar *chars)
1168{
1169    return (chars[3] == 'n' || chars[3] == 'N') &&
1170           (chars[4] == 's' || chars[4] == 'S');
1171}
1172
1173#define IS_XMLNS_CHARS(chars)                                                 \
1174    (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
1175
1176#define STARTS_WITH_XML(chars,length)                                         \
1177    (length >= 3 && IS_XML_CHARS(chars))
1178
1179static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
1180static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";
1181
1182void
1183JSXML::finalize(JSContext *cx, bool builtin)
1184{
1185    if (JSXML_HAS_KIDS(this)) {
1186        xml_kids.finish(cx);
1187        if (xml_class == JSXML_CLASS_ELEMENT) {
1188            xml_namespaces.finish(cx);
1189            xml_attrs.finish(cx);
1190        }
1191    }
1192#ifdef DEBUG_notme
1193    JS_REMOVE_LINK(&links);
1194#endif
1195}
1196
1197static JSObject *
1198ParseNodeToQName(Parser *parser, ParseNode *pn,
1199                 JSXMLArray<JSObject> *inScopeNSes, JSBool isAttributeName)
1200{
1201    JSContext *cx = parser->context;
1202    JSLinearString *uri, *prefix;
1203    size_t length, offset;
1204    const jschar *start, *limit, *colon;
1205    uint32_t n;
1206    JSObject *ns;
1207    JSLinearString *nsprefix;
1208
1209    JS_ASSERT(pn->isArity(PN_NULLARY));
1210    JSAtom *str = pn->pn_atom;
1211    start = str->chars();
1212    length = str->length();
1213    JS_ASSERT(length != 0 && *start != '@');
1214    JS_ASSERT(length != 1 || *start != '*');
1215
1216    JSAtom *localName;
1217
1218    uri = cx->runtime->emptyString;
1219    limit = start + length;
1220    colon = js_strchr_limit(start, ':', limit);
1221    if (colon) {
1222        offset = colon - start;
1223        prefix = js_NewDependentString(cx, str, 0, offset);
1224        if (!prefix)
1225            return NULL;
1226
1227        if (STARTS_WITH_XML(start, offset)) {
1228            if (offset == 3) {
1229                uri = JS_ASSERT_STRING_IS_FLAT(JS_InternString(cx, xml_namespace_str));
1230                if (!uri)
1231                    return NULL;
1232            } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
1233                uri = JS_ASSERT_STRING_IS_FLAT(JS_InternString(cx, xmlns_namespace_str));
1234                if (!uri)
1235                    return NULL;
1236            } else {
1237                uri = NULL;
1238            }
1239        } else {
1240            uri = NULL;
1241            n = inScopeNSes->length;
1242            while (n != 0) {
1243                --n;
1244                ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
1245                nsprefix = ns->getNamePrefix();
1246                if (nsprefix && EqualStrings(nsprefix, prefix)) {
1247                    uri = ns->getNameURI();
1248                    break;
1249                }
1250            }
1251        }
1252
1253        if (!uri) {
1254            Value v = StringValue(prefix);
1255            JSAutoByteString bytes;
1256            if (js_ValueToPrintable(parser->context, v, &bytes)) {
1257                ReportCompileErrorNumber(parser->context, &parser->tokenStream, pn,
1258                                         JSREPORT_ERROR, JSMSG_BAD_XML_NAMESPACE, bytes.ptr());
1259            }
1260            return NULL;
1261        }
1262
1263        localName = js_AtomizeChars(parser->context, colon + 1, length - (offset + 1));
1264        if (!localName)
1265            return NULL;
1266    } else {
1267        if (isAttributeName) {
1268            /*
1269             * An unprefixed attribute is not in any namespace, so set prefix
1270             * as well as uri to the empty string.
1271             */
1272            prefix = uri;
1273        } else {
1274            /*
1275             * Loop from back to front looking for the closest declared default
1276             * namespace.
1277             */
1278            n = inScopeNSes->length;
1279            while (n != 0) {
1280                --n;
1281                ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
1282                nsprefix = ns->getNamePrefix();
1283                if (!nsprefix || nsprefix->empty()) {
1284                    uri = ns->getNameURI();
1285                    break;
1286                }
1287            }
1288            prefix = uri->empty() ? parser->context->runtime->emptyString : NULL;
1289        }
1290        localName = str;
1291    }
1292
1293    return NewXMLQName(parser->context, uri, prefix, localName);
1294}
1295
1296static JSString *
1297ChompXMLWhitespace(JSContext *cx, JSString *str)
1298{
1299    size_t length, newlength, offset;
1300    const jschar *cp, *start, *end;
1301    jschar c;
1302
1303    length = str->length();
1304    start = str->getChars(cx);
1305    if (!start)
1306        return NULL;
1307
1308    for (cp = start, end = cp + length; cp < end; cp++) {
1309        c = *cp;
1310        if (!unicode::IsXMLSpace(c))
1311            break;
1312    }
1313    while (end > cp) {
1314        c = end[-1];
1315        if (!unicode::IsXMLSpace(c))
1316            break;
1317        --end;
1318    }
1319    newlength = end - cp;
1320    if (newlength == length)
1321        return str;
1322    offset = cp - start;
1323    return js_NewDependentString(cx, str, offset, newlength);
1324}
1325
1326static JSXML *
1327ParseNodeToXML(Parser *parser, ParseNode *pn,
1328               JSXMLArray<JSObject> *inScopeNSes, uintN flags)
1329{
1330    JSContext *cx = parser->context;
1331    JSXML *xml, *kid, *attr, *attrj;
1332    JSLinearString *str;
1333    uint32_t length, n, i, j;
1334    ParseNode *pn2, *pn3, *head, **pnp;
1335    JSObject *ns;
1336    JSObject *qn, *attrjqn;
1337    JSXMLClass xml_class;
1338    int stackDummy;
1339
1340    if (!JS_CHECK_STACK_SIZE(cx->runtime->nativeStackLimit, &stackDummy)) {
1341        ReportCompileErrorNumber(cx, &parser->tokenStream, pn, JSREPORT_ERROR,
1342                                 JSMSG_OVER_RECURSED);
1343        return NULL;
1344    }
1345
1346#define PN2X_SKIP_CHILD ((JSXML *) 1)
1347
1348    /*
1349     * Cases return early to avoid common code that gets an outermost xml's
1350     * object, which protects GC-things owned by xml and its descendants from
1351     * garbage collection.
1352     */
1353    xml = NULL;
1354    if (!js_EnterLocalRootScope(cx))
1355        return NULL;
1356    switch (pn->getKind()) {
1357      case PNK_XMLELEM:
1358        length = inScopeNSes->length;
1359        pn2 = pn->pn_head;
1360        xml = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
1361        if (!xml)
1362            goto fail;
1363
1364        n = pn->pn_count;
1365        JS_ASSERT(n >= 2);
1366        n -= 2;
1367        if (!xml->xml_kids.setCapacity(cx, n))
1368            goto fail;
1369
1370        i = 0;
1371        while ((pn2 = pn2->pn_next) != NULL) {
1372            if (!pn2->pn_next) {
1373                /* Don't append the end tag! */
1374                JS_ASSERT(pn2->isKind(PNK_XMLETAGO));
1375                break;
1376            }
1377
1378            if ((flags & XSF_IGNORE_WHITESPACE) &&
1379                n > 1 && pn2->isKind(PNK_XMLSPACE)) {
1380                --n;
1381                continue;
1382            }
1383
1384            kid = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
1385            if (kid == PN2X_SKIP_CHILD) {
1386                --n;
1387                continue;
1388            }
1389
1390            if (!kid)
1391                goto fail;
1392
1393            /* Store kid in xml right away, to protect it from GC. */
1394            XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1395            kid->parent = xml;
1396            ++i;
1397
1398            /* XXX where is this documented in an XML spec, or in E4X? */
1399            if ((flags & XSF_IGNORE_WHITESPACE) &&
1400                n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
1401                JSString *str = ChompXMLWhitespace(cx, kid->xml_value);
1402                if (!str)
1403                    goto fail;
1404                kid->xml_value = str;
1405            }
1406        }
1407
1408        JS_ASSERT(i == n);
1409        if (n < pn->pn_count - 2)
1410            xml->xml_kids.trim();
1411        XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1412        break;
1413
1414      case PNK_XMLLIST:
1415        xml = js_NewXML(cx, JSXML_CLASS_LIST);
1416        if (!xml)
1417            goto fail;
1418
1419        n = pn->pn_count;
1420        if (!xml->xml_kids.setCapacity(cx, n))
1421            goto fail;
1422
1423        i = 0;
1424        for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
1425            /*
1426             * Always ignore insignificant whitespace in lists -- we shouldn't
1427             * condition this on an XML.ignoreWhitespace setting when the list
1428             * constructor is XMLList (note XML/XMLList unification hazard).
1429             */
1430            if (pn2->isKind(PNK_XMLSPACE)) {
1431                --n;
1432                continue;
1433            }
1434
1435            kid = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
1436            if (kid == PN2X_SKIP_CHILD) {
1437                --n;
1438                continue;
1439            }
1440
1441            if (!kid)
1442                goto fail;
1443
1444            XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1445            ++i;
1446        }
1447
1448        if (n < pn->pn_count)
1449            xml->xml_kids.trim();
1450        break;
1451
1452      case PNK_XMLSTAGO:
1453      case PNK_XMLPTAGC:
1454        length = inScopeNSes->length;
1455        pn2 = pn->pn_head;
1456        JS_ASSERT(pn2->isKind(PNK_XMLNAME));
1457        if (pn2->isArity(PN_LIST))
1458            goto syntax;
1459
1460        xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
1461        if (!xml)
1462            goto fail;
1463
1464        /* First pass: check syntax and process namespace declarations. */
1465        JS_ASSERT(pn->pn_count >= 1);
1466        n = pn->pn_count - 1;
1467        pnp = &pn2->pn_next;
1468        head = *pnp;
1469        while ((pn2 = *pnp) != NULL) {
1470            size_t length;
1471            const jschar *chars;
1472
1473            if (!pn2->isKind(PNK_XMLNAME) || !pn2->isArity(PN_NULLARY))
1474                goto syntax;
1475
1476            /* Enforce "Well-formedness constraint: Unique Att Spec". */
1477            for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
1478                if (pn3->pn_atom == pn2->pn_atom) {
1479                    Value v = StringValue(pn2->pn_atom);
1480                    JSAutoByteString bytes;
1481                    if (js_ValueToPrintable(cx, v, &bytes)) {
1482                        ReportCompileErrorNumber(cx, &parser->tokenStream, pn2,
1483                                                 JSREPORT_ERROR, JSMSG_DUPLICATE_XML_ATTR,
1484                                                 bytes.ptr());
1485                    }
1486                    goto fail;
1487                }
1488            }
1489
1490            JSAtom *atom = pn2->pn_atom;
1491            pn2 = pn2->pn_next;
1492            JS_ASSERT(pn2);
1493            if (!pn2->isKind(PNK_XMLATTR))
1494                goto syntax;
1495
1496            chars = atom->chars();
1497            length = atom->length();
1498            if (length >= 5 &&
1499                IS_XMLNS_CHARS(chars) &&
1500                (length == 5 || chars[5] == ':')) {
1501                JSLinearString *uri, *prefix;
1502
1503                uri = pn2->pn_atom;
1504                if (length == 5) {
1505                    /* 10.3.2.1. Step 6(h)(i)(1)(a). */
1506                    prefix = cx->runtime->emptyString;
1507                } else {
1508                    prefix = js_NewStringCopyN(cx, chars + 6, length - 6);
1509                    if (!prefix)
1510                        goto fail;
1511                }
1512
1513                /*
1514                 * Once the new ns is appended to xml->xml_namespaces, it is
1515                 * protected from GC by the object that owns xml -- which is
1516                 * either xml->object if outermost, or the object owning xml's
1517                 * oldest ancestor if !outermost.
1518                 */
1519                ns = NewXMLNamespace(cx, prefix, uri, JS_TRUE);
1520                if (!ns)
1521                    goto fail;
1522
1523                /*
1524                 * Don't add a namespace that's already in scope.  If someone
1525                 * extracts a child property from its parent via [[Get]], then
1526                 * we enforce the invariant, noted many times in ECMA-357, that
1527                 * the child's namespaces form a possibly-improper superset of
1528                 * its ancestors' namespaces.
1529                 */
1530                if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
1531                    if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
1532                        !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
1533                        goto fail;
1534                    }
1535                }
1536
1537                JS_ASSERT(n >= 2);
1538                n -= 2;
1539                *pnp = pn2->pn_next;
1540                /* XXXbe recycle pn2 */
1541                continue;
1542            }
1543
1544            pnp = &pn2->pn_next;
1545        }
1546
1547        xml->xml_namespaces.trim();
1548
1549        /* Second pass: process tag name and attributes, using namespaces. */
1550        pn2 = pn->pn_head;
1551        qn = ParseNodeToQName(parser, pn2, inScopeNSes, JS_FALSE);
1552        if (!qn)
1553            goto fail;
1554        xml->name = qn;
1555
1556        JS_ASSERT((n & 1) == 0);
1557        n >>= 1;
1558        if (!xml->xml_attrs.setCapacity(cx, n))
1559            goto fail;
1560
1561        for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
1562            qn = ParseNodeToQName(parser, pn2, inScopeNSes, JS_TRUE);
1563            if (!qn) {
1564                xml->xml_attrs.length = i;
1565                goto fail;
1566            }
1567
1568            /*
1569             * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
1570             * this time checking local name and namespace URI.
1571             */
1572            for (j = 0; j < i; j++) {
1573                attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
1574                attrjqn = attrj->name;
1575                if (EqualStrings(attrjqn->getNameURI(), qn->getNameURI()) &&
1576                    EqualStrings(attrjqn->getQNameLocalName(), qn->getQNameLocalName())) {
1577                    Value v = StringValue(pn2->pn_atom);
1578                    JSAutoByteString bytes;
1579                    if (js_ValueToPrintable(cx, v, &bytes)) {
1580                        ReportCompileErrorNumber(cx, &parser->tokenStream, pn2,
1581                                                 JSREPORT_ERROR, JSMSG_DUPLICATE_XML_ATTR,
1582                                                 bytes.ptr());
1583                    }
1584                    goto fail;
1585                }
1586            }
1587
1588            pn2 = pn2->pn_next;
1589            JS_ASSERT(pn2);
1590            JS_ASSERT(pn2->isKind(PNK_XMLATTR));
1591
1592            attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
1593            if (!attr)
1594                goto fail;
1595
1596            XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
1597            attr->parent = xml;
1598            attr->name = qn;
1599            attr->xml_value = pn2->pn_atom;
1600        }
1601
1602        /* Point tag closes its own namespace scope. */
1603        if (pn->isKind(PNK_XMLPTAGC))
1604            XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1605        break;
1606
1607      case PNK_XMLSPACE:
1608      case PNK_XMLTEXT:
1609      case PNK_XMLCDATA:
1610      case PNK_XMLCOMMENT:
1611      case PNK_XMLPI:
1612        str = pn->pn_atom;
1613        qn = NULL;
1614        if (pn->isKind(PNK_XMLCOMMENT)) {
1615            if (flags & XSF_IGNORE_COMMENTS)
1616                goto skip_child;
1617            xml_class = JSXML_CLASS_COMMENT;
1618        } else if (pn->isKind(PNK_XMLPI)) {
1619            XMLProcessingInstruction &pi = pn->asXMLProcessingInstruction();
1620            if (IS_XML(str)) {
1621                Value v = StringValue(str);
1622                JSAutoByteString bytes;
1623                if (js_ValueToPrintable(cx, v, &bytes)) {
1624                    ReportCompileErrorNumber(cx, &parser->tokenStream, &pi,
1625                                             JSREPORT_ERROR, JSMSG_RESERVED_ID, bytes.ptr());
1626                }
1627                goto fail;
1628            }
1629
1630            if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
1631                goto skip_child;
1632
1633            qn = ParseNodeToQName(parser, &pi, inScopeNSes, JS_FALSE);
1634            if (!qn)
1635                goto fail;
1636
1637            str = pi.data();
1638            xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
1639        } else {
1640            /* CDATA section content, or element text. */
1641            xml_class = JSXML_CLASS_TEXT;
1642        }
1643
1644        xml = js_NewXML(cx, xml_class);
1645        if (!xml)
1646            goto fail;
1647        xml->name = qn;
1648        if (pn->isKind(PNK_XMLSPACE))
1649            xml->xml_flags |= XMLF_WHITESPACE_TEXT;
1650        xml->xml_value = str;
1651        break;
1652
1653      default:
1654        goto syntax;
1655    }
1656
1657    js_LeaveLocalRootScopeWithResult(cx, xml);
1658    return xml;
1659
1660skip_child:
1661    js_LeaveLocalRootScope(cx);
1662    return PN2X_SKIP_CHILD;
1663
1664#undef PN2X_SKIP_CHILD
1665
1666syntax:
1667    ReportCompileErrorNumber(cx, &parser->tokenStream, pn, JSREPORT_ERROR, JSMSG_BAD_XML_MARKUP);
1668fail:
1669    js_LeaveLocalRootScope(cx);
1670    return NULL;
1671}
1672
1673/*
1674 * XML helper, object-ops, and library functions.  We start with the helpers,
1675 * in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
1676 */
1677static JSBool
1678GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
1679{
1680    jsval v;
1681
1682    if (!js_FindClassObject(cx, NULL, JSProto_XML, &v))
1683        return JS_FALSE;
1684    if (JSVAL_IS_PRIMITIVE(v) || !JSVAL_TO_OBJECT(v)->isFunction()) {
1685        *vp = JSVAL_VOID;
1686        return JS_TRUE;
1687    }
1688    return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
1689}
1690
1691static JSBool
1692GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
1693{
1694    jsval v;
1695
1696    return GetXMLSetting(cx, name, &v) && JS_ValueToBoolean(cx, v, bp);
1697}
1698
1699static JSBool
1700GetUint32XMLSetting(JSContext *cx, const char *name, uint32_t *uip)
1701{
1702    jsval v;
1703
1704    return GetXMLSetting(cx, name, &v) && JS_ValueToECMAUint32(cx, v, uip);
1705}
1706
1707static JSBool
1708GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
1709{
1710    JSBool flag[4];
1711
1712    if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag[0]) ||
1713        !GetBooleanXMLSetting(cx, js_ignoreProcessingInstructions_str, &flag[1]) ||
1714        !GetBooleanXMLSetting(cx, js_ignoreWhitespace_str, &flag[2]) ||
1715        !GetBooleanXMLSetting(cx, js_prettyPrinting_str, &flag[3])) {
1716        return false;
1717    }
1718
1719    *flagsp = 0;
1720    for (size_t n = 0; n < 4; ++n)
1721        if (flag[n])
1722            *flagsp |= JS_BIT(n);
1723    return true;
1724}
1725
1726static JSObject *
1727GetCurrentScopeChain(JSContext *cx)
1728{
1729    if (cx->hasfp())
1730        return &cx->fp()->scopeChain();
1731    return JS_ObjectToInnerObject(cx, cx->globalObject);
1732}
1733
1734static JSXML *
1735ParseXMLSource(JSContext *cx, JSString *src)
1736{
1737    jsval nsval;
1738    JSLinearString *uri;
1739    size_t urilen, srclen, length, offset, dstlen;
1740    jschar *chars;
1741    const jschar *srcp, *endp;
1742    JSXML *xml;
1743    const char *filename;
1744    uintN lineno;
1745    JSOp op;
1746
1747    static const char prefix[] = "<parent xmlns=\"";
1748    static const char middle[] = "\">";
1749    static const char suffix[] = "</parent>";
1750
1751#define constrlen(constr)   (sizeof(constr) - 1)
1752
1753    if (!js_GetDefaultXMLNamespace(cx, &nsval))
1754        return NULL;
1755    uri = JSVAL_TO_OBJECT(nsval)->getNameURI();
1756    uri = js_EscapeAttributeValue(cx, uri, JS_FALSE);
1757    if (!uri)
1758        return NULL;
1759
1760    urilen = uri->length();
1761    srclen = src->length();
1762    length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
1763             constrlen(suffix);
1764
1765    chars = (jschar *) cx->malloc_((length + 1) * sizeof(jschar));
1766    if (!chars)
1767        return NULL;
1768
1769    dstlen = length;
1770    InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
1771    offset = dstlen;
1772    js_strncpy(chars + offset, uri->chars(), urilen);
1773    offset += urilen;
1774    dstlen = length - offset + 1;
1775    InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset, &dstlen);
1776    offset += dstlen;
1777    srcp = src->getChars(cx);
1778    if (!srcp) {
1779        cx->free_(chars);
1780        return NULL;
1781    }
1782    js_strncpy(chars + offset, srcp, srclen);
1783    offset += srclen;
1784    dstlen = length - offset + 1;
1785    InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset, &dstlen);
1786    chars [offset + dstlen] = 0;
1787
1788    xml = NULL;
1789    filename = NULL;
1790    lineno = 1;
1791    FrameRegsIter i(cx);
1792    if (!i.done()) {
1793        op = (JSOp) *i.pc();
1794        if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
1795            filename = i.fp()->script()->filename;
1796            lineno = js_PCToLineNumber(cx, i.fp()->script(), i.pc());
1797            for (endp = srcp + srclen; srcp < endp; srcp++) {
1798                if (*srcp == '\n')
1799                    --lineno;
1800            }
1801        }
1802    }
1803
1804    {
1805        Parser parser(cx);
1806        if (parser.init(chars, length, filename, lineno, cx->findVersion())) {
1807            JSObject *scopeChain = GetCurrentScopeChain(cx);
1808            if (!scopeChain) {
1809                cx->free_(chars);
1810                return NULL;
1811            }
1812
1813            ParseNode *pn = parser.parseXMLText(scopeChain, false);
1814            uintN flags;
1815            if (pn && GetXMLSettingFlags(cx, &flags)) {
1816                AutoNamespaceArray namespaces(cx);
1817                if (namespaces.array.setCapacity(cx, 1))
1818                    xml = ParseNodeToXML(&parser, pn, &namespaces.array, flags);
1819            }
1820        }
1821    }
1822
1823    cx->free_(chars);
1824    return xml;
1825
1826#undef constrlen
1827}
1828
1829/*
1830 * Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
1831 *
1832 * 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
1833 * the constraint:
1834 *
1835 *     for all x belonging to XML:
1836 *         x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
1837 *
1838 * must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
1839 * (in new sub-step 6(a), renumbering the others to (b) and (c)).
1840 *
1841 * Same goes for 10.4.1 Step 7(a).
1842 *
1843 * In order for XML.prototype.namespaceDeclarations() to work correctly, the
1844 * default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
1845 * flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
1846 * undeclared namespaces associated with x not belonging to ancestorNS.
1847 */
1848static JSXML *
1849OrphanXMLChild(JSContext *cx, JSXML *xml, uint32_t i)
1850{
1851    JSObject *ns;
1852
1853    ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSObject);
1854    xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
1855    if (!ns || !xml)
1856        return xml;
1857    if (xml->xml_class == JSXML_CLASS_ELEMENT) {
1858        if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1859            return NULL;
1860        ns->setNamespaceDeclared(JSVAL_VOID);
1861    }
1862    xml->parent = NULL;
1863    return xml;
1864}
1865
1866static JSObject *
1867ToXML(JSContext *cx, jsval v)
1868{
1869    JSObject *obj;
1870    JSXML *xml;
1871    Class *clasp;
1872    JSString *str;
1873    uint32_t length;
1874
1875    if (JSVAL_IS_PRIMITIVE(v)) {
1876        if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
1877            goto bad;
1878    } else {
1879        obj = JSVAL_TO_OBJECT(v);
1880        if (obj->isXML()) {
1881            xml = (JSXML *) obj->getPrivate();
1882            if (xml->xml_class == JSXML_CLASS_LIST) {
1883                if (xml->xml_kids.length != 1)
1884                    goto bad;
1885                xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
1886                if (xml) {
1887                    JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
1888                    return js_GetXMLObject(cx, xml);
1889                }
1890            }
1891            return obj;
1892        }
1893
1894        clasp = obj->getClass();
1895        if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
1896            JS_ASSERT(0);
1897        }
1898
1899        if (clasp != &StringClass &&
1900            clasp != &NumberClass &&
1901            clasp != &BooleanClass) {
1902            goto bad;
1903        }
1904    }
1905
1906    str = ToString(cx, v);
1907    if (!str)
1908        return NULL;
1909    if (str->empty()) {
1910        length = 0;
1911#ifdef __GNUC__         /* suppress bogus gcc warnings */
1912        xml = NULL;
1913#endif
1914    } else {
1915        xml = ParseXMLSource(cx, str);
1916        if (!xml)
1917            return NULL;
1918        length = JSXML_LENGTH(xml);
1919    }
1920
1921    if (length == 0) {
1922        obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
1923        if (!obj)
1924            return NULL;
1925    } else if (length == 1) {
1926        xml = OrphanXMLChild(cx, xml, 0);
1927        if (!xml)
1928            return NULL;
1929        obj = js_GetXMLObject(cx, xml);
1930        if (!obj)
1931            return NULL;
1932    } else {
1933        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
1934        return NULL;
1935    }
1936    return obj;
1937
1938bad:
1939    js_ReportValueError(cx, JSMSG_BAD_XML_CONVERSION,
1940                        JSDVG_IGNORE_STACK, v, NULL);
1941    return NULL;
1942}
1943
1944static JSBool
1945Append(JSContext *cx, JSXML *list, JSXML *kid);
1946
1947static JSObject *
1948ToXMLList(JSContext *cx, jsval v)
1949{
1950    JSObject *obj, *listobj;
1951    JSXML *xml, *list, *kid;
1952    Class *clasp;
1953    JSString *str;
1954    uint32_t i, length;
1955
1956    if (JSVAL_IS_PRIMITIVE(v)) {
1957        if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
1958            goto bad;
1959    } else {
1960        obj = JSVAL_TO_OBJECT(v);
1961        if (obj->isXML()) {
1962            xml = (JSXML *) obj->getPrivate();
1963            if (xml->xml_class != JSXML_CLASS_LIST) {
1964                listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
1965                if (!listobj)
1966                    return NULL;
1967                list = (JSXML *) listobj->getPrivate();
1968                if (!Append(cx, list, xml))
1969                    return NULL;
1970                return listobj;
1971            }
1972            return obj;
1973        }
1974
1975        clasp = obj->getClass();
1976        if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
1977            JS_ASSERT(0);
1978        }
1979
1980        if (clasp != &StringClass &&
1981            clasp != &NumberClass &&
1982            clasp != &BooleanClass) {
1983            goto bad;
1984        }
1985    }
1986
1987    str = ToString(cx, v);
1988    if (!str)
1989        return NULL;
1990    if (str->empty()) {
1991        xml = NULL;
1992        length = 0;
1993    } else {
1994        if (!js_EnterLocalRootScope(cx))
1995            return NULL;
1996        xml = ParseXMLSource(cx, str);
1997        if (!xml) {
1998            js_LeaveLocalRootScope(cx);
1999            return NULL;
2000        }
2001        length = JSXML_LENGTH(xml);
2002    }
2003
2004    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2005    if (listobj) {
2006        list = (JSXML *) listobj->getPrivate();
2007        for (i = 0; i < length; i++) {
2008            kid = OrphanXMLChild(cx, xml, i);
2009            if (!kid || !Append(cx, list, kid)) {
2010                listobj = NULL;
2011                break;
2012            }
2013        }
2014    }
2015
2016    if (xml)
2017        js_LeaveLocalRootScopeWithResult(cx, listobj);
2018    return listobj;
2019
2020bad:
2021    js_ReportValueError(cx, JSMSG_BAD_XMLLIST_CONVERSION,
2022                        JSDVG_IGNORE_STACK, v, NULL);
2023    return NULL;
2024}
2025
2026/*
2027 * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
2028 * and their library-public js_* counterparts.  The guts of MakeXMLCDataString,
2029 * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
2030 * MakeXMLSpecialString subroutine.
2031 *
2032 * These functions mutate sb, leaving it empty.
2033 */
2034static JSFlatString *
2035MakeXMLSpecialString(JSContext *cx, StringBuffer &sb,
2036                     JSString *str, JSString *str2,
2037                     const jschar *prefix, size_t prefixlength,
2038                     const jschar *suffix, size_t suffixlength)
2039{
2040    if (!sb.append(prefix, prefixlength) || !sb.append(str))
2041        return NULL;
2042    if (str2 && !str2->empty()) {
2043        if (!sb.append(' ') || !sb.append(str2))
2044            return NULL;
2045    }
2046    if (!sb.append(suffix, suffixlength))
2047        return NULL;
2048
2049    return sb.finishString();
2050}
2051
2052static JSFlatString *
2053MakeXMLCDATAString(JSContext *cx, StringBuffer &sb, JSString *str)
2054{
2055    static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
2056                                                 'C', 'D', 'A', 'T', 'A',
2057                                                 '['};
2058    static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
2059
2060    return MakeXMLSpecialString(cx, sb, str, NULL,
2061                                cdata_prefix_ucNstr, 9,
2062                                cdata_suffix_ucNstr, 3);
2063}
2064
2065static JSFlatString *
2066MakeXMLCommentString(JSContext *cx, StringBuffer &sb, JSString *str)
2067{
2068    static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
2069    static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
2070
2071    return MakeXMLSpecialString(cx, sb, str, NULL,
2072                                comment_prefix_ucNstr, 4,
2073                                comment_suffix_ucNstr, 3);
2074}
2075
2076static JSFlatString *
2077MakeXMLPIString(JSContext *cx, StringBuffer &sb, JSString *name,
2078                JSString *value)
2079{
2080    static const jschar pi_prefix_ucNstr[] = {'<', '?'};
2081    static const jschar pi_suffix_ucNstr[] = {'?', '>'};
2082
2083    return MakeXMLSpecialString(cx, sb, name, value,
2084                                pi_prefix_ucNstr, 2,
2085                                pi_suffix_ucNstr, 2);
2086}
2087
2088/*
2089 * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
2090 *
2091 * This function appends the output into the supplied string buffer.
2092 */
2093static bool
2094EscapeAttributeValueBuffer(JSContext *cx, StringBuffer &sb, JSString *str, JSBool quote)
2095{
2096    size_t length = str->length();
2097    const jschar *start = str->getChars(cx);
2098    if (!start)
2099        return false;
2100
2101    if (quote && !sb.append('"'))
2102        return false;
2103
2104    for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
2105        jschar c = *cp;
2106        switch (c) {
2107          case '"':
2108            if (!sb.append(js_quot_entity_str))
2109                return false;
2110            break;
2111          case '<':
2112            if (!sb.append(js_lt_entity_str))
2113                return false;
2114            break;
2115          case '&':
2116            if (!sb.append(js_amp_entity_str))
2117                return false;
2118            break;
2119          case '\n':
2120            if (!sb.append("&#xA;"))
2121                return false;
2122            break;
2123          case '\r':
2124            if (!sb.append("&#xD;"))
2125                return false;
2126            break;
2127          case '\t':
2128            if (!sb.append("&#x9;"))
2129                return false;
2130            break;
2131          default:
2132            if (!sb.append(c))
2133                return false;
2134        }
2135    }
2136
2137    if (quote && !sb.append('"'))
2138        return false;
2139
2140    return true;
2141}
2142
2143/*
2144 * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
2145 *
2146 * This function mutates sb, leaving it empty.
2147 */
2148static JSFlatString *
2149EscapeAttributeValue(JSContext *cx, StringBuffer &sb, JSString *str, JSBool quote)
2150{
2151    if (!EscapeAttributeValueBuffer(cx, sb, str, quote))
2152        return NULL;
2153    return sb.finishString();
2154}
2155
2156/*
2157 * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
2158 * equals, a double quote, an attribute value, and a closing double quote.
2159 */
2160static bool
2161AppendAttributeValue(JSContext *cx, StringBuffer &sb, JSString *valstr)
2162{
2163    if (!sb.append('='))
2164        return false;
2165    return EscapeAttributeValueBuffer(cx, sb, valstr, JS_TRUE);
2166}
2167
2168/*
2169 * ECMA-357 10.2.1.1 EscapeElementValue helper method.
2170
2171 * These functions mutate sb, leaving it empty.
2172 */
2173static JSFlatString *
2174EscapeElementValue(JSContext *cx, StringBuffer &sb, JSString *str, uint32_t toSourceFlag)
2175{
2176    size_t length = str->length();
2177    const jschar *start = str->getChars(cx);
2178    if (!start)
2179        return NULL;
2180
2181    for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
2182        jschar c = *cp;
2183        switch (*cp) {
2184          case '<':
2185            if (!sb.append(js_lt_entity_str))
2186                return NULL;
2187            break;
2188          case '>':
2189            if (!sb.append(js_gt_entity_str))
2190                return NULL;
2191            break;
2192          case '&':
2193            if (!sb.append(js_amp_entity_str))
2194                return NULL;
2195            break;
2196          case '{':
2197            /*
2198             * If EscapeElementValue is called by toSource/uneval, we also need
2199             * to escape '{'. See bug 463360.
2200             */
2201            if (toSourceFlag) {
2202                if (!sb.append(js_leftcurly_entity_str))
2203                    return NULL;
2204                break;
2205            }
2206            /* FALL THROUGH */
2207          default:
2208            if (!sb.append(c))
2209                return NULL;
2210        }
2211    }
2212    return sb.finishString();
2213}
2214
2215/* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
2216static JSObject *
2217GetNamespace(JSContext *cx, JSObject *qn, const JSXMLArray<JSObject> *inScopeNSes)
2218{
2219    JSLinearString *uri, *prefix, *nsprefix;
2220    JSObject *match, *ns;
2221    uint32_t i, n;
2222    jsval argv[2];
2223
2224    uri = qn->getNameURI();
2225    prefix = qn->getNamePrefix();
2226    JS_ASSERT(uri);
2227    if (!uri) {
2228        JSAutoByteString bytes;
2229        const char *s = !prefix ?
2230                        js_undefined_str
2231                        : js_ValueToPrintable(cx, StringValue(prefix), &bytes);
2232        if (s)
2233            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XML_NAMESPACE, s);
2234        return NULL;
2235    }
2236
2237    /* Look for a matching namespace in inScopeNSes, if provided. */
2238    match = NULL;
2239    if (inScopeNSes) {
2240        for (i = 0, n = inScopeNSes->length; i < n; i++) {
2241            ns = XMLARRAY_MEMBER(inScopeNSes, i, JSObject);
2242            if (!ns)
2243                continue;
2244
2245            /*
2246             * Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
2247             * If we preserve prefixes, we must match null prefix against
2248             * an empty prefix of ns, in order to avoid generating redundant
2249             * prefixed and default namespaces for cases such as:
2250             *
2251             *   x = <t xmlns="http://foo.com"/>
2252             *   print(x.toXMLString());
2253             *
2254             * Per 10.3.2.1, the namespace attribute in t has an empty string
2255             * prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
2256             *
2257             *   1. If the [local name] property of a is "xmlns"
2258             *      a. Map ns.prefix to the empty string
2259             *
2260             * But t's name has a null prefix in this implementation, meaning
2261             * *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
2262             * the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
2263             * saying how "no value" maps to an ECMA-357 value -- but it must
2264             * map to the *undefined* prefix value).
2265             *
2266             * Since "" != undefined (or null, in the current implementation)
2267             * the ECMA-357 spec will fail to match in [[GetNamespace]] called
2268             * on t with argument {} U {(prefix="", uri="http://foo.com")}.
2269             * This spec bug leads to ToXMLString results that duplicate the
2270             * declared namespace.
2271             */
2272            if (EqualStrings(ns->getNameURI(), uri)) {
2273                nsprefix = ns->getNamePrefix();
2274                if (nsprefix == prefix ||
2275                    ((nsprefix && prefix)
2276                     ? EqualStrings(nsprefix, prefix)
2277                     : (nsprefix ? nsprefix : prefix)->empty())) {
2278                    match = ns;
2279                    break;
2280                }
2281            }
2282        }
2283    }
2284
2285    /* If we didn't match, make a new namespace from qn. */
2286    if (!match) {
2287        argv[0] = prefix ? STRING_TO_JSVAL(prefix) : JSVAL_VOID;
2288        argv[1] = STRING_TO_JSVAL(uri);
2289        ns = JS_ConstructObjectWithArguments(cx, Jsvalify(&NamespaceClass), NULL, 2, argv);
2290        if (!ns)
2291            return NULL;
2292        match = ns;
2293    }
2294    return match;
2295}
2296
2297static JSLinearString *
2298GeneratePrefix(JSContext *cx, JSLinearString *uri, JSXMLArray<JSObject> *decls)
2299{
2300    const jschar *cp, *start, *end;
2301    size_t length, newlength, offset;
2302    uint32_t i, n, m, serial;
2303    jschar *bp, *dp;
2304    JSBool done;
2305    JSObject *ns;
2306    JSLinearString *nsprefix, *prefix;
2307
2308    JS_ASSERT(!uri->empty());
2309
2310    /*
2311     * If there are no *declared* namespaces, skip all collision detection and
2312     * return a short prefix quickly; an example of such a situation:
2313     *
2314     *   var x = <f/>;
2315     *   var n = new Namespace("http://example.com/");
2316     *   x.@n::att = "val";
2317     *   x.toXMLString();
2318     *
2319     * This is necessary for various log10 uses below to be valid.
2320     */
2321    if (decls->length == 0)
2322        return js_NewStringCopyZ(cx, "a");
2323
2324    /*
2325     * Try peeling off the last filename suffix or pathname component till
2326     * we have a valid XML name.  This heuristic will prefer "xul" given
2327     * ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
2328     * likely URI of the form ".../xbl2/2005".
2329     */
2330    start = uri->chars();
2331    end = start + uri->length();
2332    cp = end;
2333    while (--cp > start) {
2334        if (*cp == '.' || *cp == '/' || *cp == ':') {
2335            ++cp;
2336            length = end - cp;
2337            if (IsXMLName(cp, length) && !STARTS_WITH_XML(cp, length))
2338                break;
2339            end = --cp;
2340        }
2341    }
2342    length = end - cp;
2343
2344    /*
2345     * If the namespace consisted only of non-XML names or names that begin
2346     * case-insensitively with "xml", arbitrarily create a prefix consisting
2347     * of 'a's of size length (allowing dp-calculating code to work with or
2348     * without this branch executing) plus the space for storing a hyphen and
2349     * the serial number (avoiding reallocation if a collision happens).
2350     */
2351    bp = (jschar *) cp;
2352    newlength = length;
2353    if (STARTS_WITH_XML(cp, length) || !IsXMLName(cp, length)) {
2354        newlength = length + 2 + (size_t) log10((double) decls->length);
2355        bp = (jschar *)
2356             cx->malloc_((newlength + 1) * sizeof(jschar));
2357        if (!bp)
2358            return NULL;
2359
2360        bp[newlength] = 0;
2361        for (i = 0; i < newlength; i++)
2362             bp[i] = 'a';
2363    }
2364
2365    /*
2366     * Now search through decls looking for a collision.  If we collide with
2367     * an existing prefix, start tacking on a hyphen and a serial number.
2368     */
2369    serial = 0;
2370    do {
2371        done = JS_TRUE;
2372        for (i = 0, n = decls->length; i < n; i++) {
2373            ns = XMLARRAY_MEMBER(decls, i, JSObject);
2374            if (ns && (nsprefix = ns->getNamePrefix()) &&
2375                nsprefix->length() == newlength &&
2376                !memcmp(nsprefix->chars(), bp,
2377                        newlength * sizeof(jschar))) {
2378                if (bp == cp) {
2379                    newlength = length + 2 + (size_t) log10((double) n);
2380                    bp = (jschar *)
2381                         cx->malloc_((newlength + 1) * sizeof(jschar));
2382                    if (!bp)
2383                        return NULL;
2384                    js_strncpy(bp, cp, length);
2385                }
2386
2387                ++serial;
2388                JS_ASSERT(serial <= n);
2389                dp = bp + length + 2 + (size_t) log10((double) serial);
2390                *dp = 0;
2391                for (m = serial; m != 0; m /= 10)
2392                    *--dp = (jschar)('0' + m % 10);
2393                *--dp = '-';
2394                JS_ASSERT(dp == bp + length);
2395
2396                done = JS_FALSE;
2397                break;
2398            }
2399        }
2400    } while (!done);
2401
2402    if (bp == cp) {
2403        offset = cp - start;
2404        prefix = js_NewDependentString(cx, uri, offset, length);
2405    } else {
2406        prefix = js_NewString(cx, bp, newlength);
2407        if (!prefix)
2408            cx->free_(bp);
2409    }
2410    return prefix;
2411}
2412
2413static JSBool
2414namespace_match(const JSObject *nsa, const JSObject *nsb)
2415{
2416    JSLinearString *prefixa, *prefixb = nsb->getNamePrefix();
2417
2418    if (prefixb) {
2419        prefixa = nsa->getNamePrefix();
2420        return prefixa && EqualStrings(prefixa, prefixb);
2421    }
2422    return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
2423}
2424
2425/* ECMA-357 10.2.1 and 10.2.2 */
2426#define TO_SOURCE_FLAG 0x80000000
2427
2428static JSString *
2429XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray<JSObject> *ancestorNSes,
2430               uint32_t indentLevel, JSBool pretty)
2431{
2432    JSBool indentKids;
2433    StringBuffer sb(cx);
2434    JSString *str;
2435    JSLinearString *prefix, *nsuri;
2436    uint32_t i, n, nextIndentLevel;
2437    JSObject *ns, *ns2;
2438    AutoNamespaceArray empty(cx), decls(cx), ancdecls(cx);
2439
2440    if (pretty) {
2441        if (!sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
2442            return NULL;
2443    }
2444
2445    str = NULL;
2446
2447    switch (xml->xml_class) {
2448      case JSXML_CLASS_TEXT:
2449        /* Step 4. */
2450        if (pretty) {
2451            str = ChompXMLWhitespace(cx, xml->xml_value);
2452            if (!str)
2453                return NULL;
2454        } else {
2455            str = xml->xml_value;
2456        }
2457        return EscapeElementValue(cx, sb, str, indentLevel & TO_SOURCE_FLAG);
2458
2459      case JSXML_CLASS_ATTRIBUTE:
2460        /* Step 5. */
2461        return EscapeAttributeValue(cx, sb, xml->xml_value,
2462                                    (indentLevel & TO_SOURCE_FLAG) != 0);
2463
2464      case JSXML_CLASS_COMMENT:
2465        /* Step 6. */
2466        return MakeXMLCommentString(cx, sb, xml->xml_value);
2467
2468      case JSXML_CLASS_PROCESSING_INSTRUCTION:
2469        /* Step 7. */
2470        return MakeXMLPIString(cx, sb, xml->name->getQNameLocalName(),
2471                               xml->xml_value);
2472
2473      case JSXML_CLASS_LIST:
2474        /* ECMA-357 10.2.2. */
2475        {
2476            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
2477            i = 0;
2478            while (JSXML *kid = cursor.getNext()) {
2479                if (pretty && i != 0) {
2480                    if (!sb.append('\n'))
2481                        return NULL;
2482                }
2483
2484                JSString *kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel, pretty);
2485                if (!kidstr || !sb.append(kidstr))
2486                    return NULL;
2487                ++i;
2488            }
2489        }
2490
2491        if (sb.empty())
2492            return cx->runtime->emptyString;
2493        return sb.finishString();
2494
2495      default:;
2496    }
2497
2498    /* After this point, control must flow through label out: to exit. */
2499    if (!js_EnterLocalRootScope(cx))
2500        return NULL;
2501
2502    /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
2503    if (!ancestorNSes) {
2504        // Ensure a namespace with empty strings exists in the initial array,
2505        // otherwise every call to GetNamespace() when running toString() on
2506        // an XML object with no namespace defined will create a new Namespace
2507        // object on every call.
2508        JSObject *emptyns = NewXMLNamespace(cx, cx->runtime->emptyString, cx->runtime->emptyString, JS_FALSE);
2509        if (!emptyns || !XMLARRAY_APPEND(cx, &empty.array, emptyns))
2510            goto out;
2511        ancestorNSes = &empty.array;
2512    }
2513
2514    /* Clone in-scope namespaces not in ancestorNSes into decls. */
2515    {
2516        JSXMLArrayCursor<JSObject> cursor(&xml->xml_namespaces);
2517        while ((ns = cursor.getNext()) != NULL) {
2518            if (!IsDeclared(ns))
2519                continue;
2520            if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
2521                /* NOTE: may want to exclude unused namespaces here. */
2522                ns2 = NewXMLNamespace(cx, ns->getNamePrefix(), ns->getNameURI(), JS_TRUE);
2523                if (!ns2 || !XMLARRAY_APPEND(cx, &decls.array, ns2))
2524                    goto out;
2525            }
2526        }
2527    }
2528
2529    /*
2530     * Union ancestorNSes and decls into ancdecls.  Note that ancdecls does
2531     * not own its member references.  In the spec, ancdecls has no name, but
2532     * is always written out as (AncestorNamespaces U namespaceDeclarations).
2533     */
2534
2535    if (!ancdecls.array.setCapacity(cx, ancestorNSes->length + decls.length()))
2536        goto out;
2537    for (i = 0, n = ancestorNSes->length; i < n; i++) {
2538        ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSObject);
2539        if (!ns2)
2540            continue;
2541        JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls.array, ns2, namespace_identity));
2542        if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2))
2543            goto out;
2544    }
2545    for (i = 0, n = decls.length(); i < n; i++) {
2546        ns2 = XMLARRAY_MEMBER(&decls.array, i, JSObject);
2547        if (!ns2)
2548            continue;
2549        JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls.array, ns2, namespace_identity));
2550        if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2))
2551            goto out;
2552    }
2553
2554    /* Step 11, except we don't clone ns unless its prefix is undefined. */
2555    ns = GetNamespace(cx, xml->name, &ancdecls.array);
2556    if (!ns)
2557        goto out;
2558
2559    /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
2560    prefix = ns->getNamePrefix();
2561    if (!prefix) {
2562        /*
2563         * Create a namespace prefix that isn't used by any member of decls.
2564         * Assign the new prefix to a copy of ns.  Flag this namespace as if
2565         * it were declared, for assertion-testing's sake later below.
2566         *
2567         * Erratum: if prefix and xml->name are both null (*undefined* in
2568         * ECMA-357), we know that xml was named using the default namespace
2569         * (proof: see GetNamespace and the Namespace constructor called with
2570         * two arguments).  So we ought not generate a new prefix here, when
2571         * we can declare ns as the default namespace for xml.
2572         *
2573         * This helps descendants inherit the namespace instead of redundantly
2574         * redeclaring it with generated prefixes in each descendant.
2575         */
2576        nsuri = ns->getNameURI();
2577        if (!xml->name->getNamePrefix()) {
2578            prefix = cx->runtime->emptyString;
2579        } else {
2580            prefix = GeneratePrefix(cx, nsuri, &ancdecls.array);
2581            if (!prefix)
2582                goto out;
2583        }
2584        ns = NewXMLNamespace(cx, prefix, nsuri, JS_TRUE);
2585        if (!ns)
2586            goto out;
2587
2588        /*
2589         * If the xml->name was unprefixed, we must remove any declared default
2590         * namespace from decls before appending ns.  How can you get a default
2591         * namespace in decls that doesn't match the one from name?  Apparently
2592         * by calling x.setNamespace(ns) where ns has no prefix.  The other way
2593         * to fix this is to update x's in-scope namespaces when setNamespace
2594         * is called, but that's not specified by ECMA-357.
2595         *
2596         * Likely Erratum here, depending on whether the lack of update to x's
2597         * in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
2598         * erratum or not.  Note that changing setNamespace to update the list
2599         * of in-scope namespaces will change x.namespaceDeclarations().
2600         */
2601        if (prefix->empty()) {
2602            i = XMLArrayFindMember(&decls.array, ns, namespace_match);
2603            if (i != XML_NOT_FOUND)
2604                XMLArrayDelete(cx, &decls.array, i, JS_TRUE);
2605        }
2606
2607        /*
2608         * In the spec, ancdecls has no name, but is always written out as
2609         * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2610         * that union in ancdecls, any time we append a namespace strong
2611         * ref to decls, we must also append a weak ref to ancdecls.  Order
2612         * matters here: code at label out: releases strong refs in decls.
2613         */
2614        if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns) ||
2615            !XMLARRAY_APPEND(cx, &decls.array, ns)) {
2616            goto out;
2617        }
2618    }
2619
2620    /* Format the element or point-tag into sb. */
2621    if (!sb.append('<'))
2622        goto out;
2623
2624    if (!prefix->empty()) {
2625        if (!sb.append(prefix) || !sb.append(':'))
2626            goto out;
2627    }
2628    if (!sb.append(xml->name->getQNameLocalName()))
2629        goto out;
2630
2631    /*
2632     * Step 16 makes a union to avoid writing two loops in step 17, to share
2633     * common attribute value appending spec-code.  We prefer two loops for
2634     * faster code and less data overhead.
2635     */
2636
2637    /* Step 17(b): append attributes. */
2638    {
2639        JSXMLArrayCursor<JSXML> cursor(&xml->xml_attrs);
2640        while (JSXML *attr = cursor.getNext()) {
2641            if (!sb.append(' '))
2642                goto out;
2643            ns2 = GetNamespace(cx, attr->name, &ancdecls.array);
2644            if (!ns2)
2645                goto out;
2646
2647            /* 17(b)(ii): NULL means *undefined* here. */
2648            prefix = ns2->getNamePrefix();
2649            if (!prefix) {
2650                prefix = GeneratePrefix(cx, ns2->getNameURI(), &ancdecls.array);
2651                if (!prefix)
2652                    goto out;
2653
2654                /* Again, we avoid copying ns2 until we know it's prefix-less. */
2655                ns2 = NewXMLNamespace(cx, prefix, ns2->getNameURI(), JS_TRUE);
2656                if (!ns2)
2657                    goto out;
2658
2659                /*
2660                 * In the spec, ancdecls has no name, but is always written out as
2661                 * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2662                 * that union in ancdecls, any time we append a namespace strong
2663                 * ref to decls, we must also append a weak ref to ancdecls.  Order
2664                 * matters here: code at label out: releases strong refs in decls.
2665                 */
2666                if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2) ||
2667                    !XMLARRAY_APPEND(cx, &decls.array, ns2)) {
2668                    goto out;
2669                }
2670            }
2671
2672            /* 17(b)(iii). */
2673            if (!prefix->empty()) {
2674                if (!sb.append(prefix) || !sb.append(':'))
2675                    goto out;
2676            }
2677
2678            /* 17(b)(iv). */
2679            if (!sb.append(attr->name->getQNameLocalName()))
2680                goto out;
2681
2682            /* 17(d-g). */
2683            if (!AppendAttributeValue(cx, sb, attr->xml_value))
2684                goto out;
2685        }
2686    }
2687
2688    /* Step 17(c): append XML namespace declarations. */
2689    {
2690        JSXMLArrayCursor<JSObject> cursor(&decls.array);
2691        while (JSObject *ns3 = cursor.getNext()) {
2692            JS_ASSERT(IsDeclared(ns3));
2693
2694            if (!sb.append(" xmlns"))
2695                goto out;
2696
2697            /* 17(c)(ii): NULL means *undefined* here. */
2698            prefix = ns3->getNamePrefix();
2699            if (!prefix) {
2700                prefix = GeneratePrefix(cx, ns3->getNameURI(), &ancdecls.array);
2701                if (!prefix)
2702                    goto out;
2703                ns3->setNamePrefix(prefix);
2704            }
2705
2706            /* 17(c)(iii). */
2707            if (!prefix->empty()) {
2708                if (!sb.append(':') || !sb.append(prefix))
2709                    goto out;
2710            }
2711
2712            /* 17(d-g). */
2713            if (!AppendAttributeValue(cx, sb, ns3->getNameURI()))
2714                goto out;
2715        }
2716    }
2717
2718    /* Step 18: handle point tags. */
2719    n = xml->xml_kids.length;
2720    if (n == 0) {
2721        if (!sb.append("/>"))
2722            goto out;
2723    } else {
2724        /* Steps 19 through 25: handle element content, and open the end-tag. */
2725        if (!sb.append('>'))
2726            goto out;
2727        {
2728            JSXML *kid;
2729            indentKids = n > 1 ||
2730                         (n == 1 &&
2731                          (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
2732                          kid->xml_class != JSXML_CLASS_TEXT);
2733        }
2734
2735        if (pretty && indentKids) {
2736            if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
2737                goto out;
2738            nextIndentLevel = indentLevel + i;
2739        } else {
2740            nextIndentLevel = indentLevel & TO_SOURCE_FLAG;
2741        }
2742
2743        {
2744            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
2745            while (JSXML *kid = cursor.getNext()) {
2746                if (pretty && indentKids) {
2747                    if (!sb.append('\n'))
2748                        goto out;
2749                }
2750
2751                JSString *kidstr = XMLToXMLString(cx, kid, &ancdecls.array, nextIndentLevel, pretty);
2752                if (!kidstr)
2753                    goto out;
2754
2755                if (!sb.append(kidstr))
2756                    goto out;
2757            }
2758        }
2759
2760        if (pretty && indentKids) {
2761            if (!sb.append('\n') ||
2762                !sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
2763                goto out;
2764        }
2765        if (!sb.append("</"))
2766            goto out;
2767
2768        /* Step 26. */
2769        prefix = ns->getNamePrefix();
2770        if (prefix && !prefix->empty()) {
2771            if (!sb.append(prefix) || !sb.append(':'))
2772                goto out;
2773        }
2774
2775        /* Step 27. */
2776        if (!sb.append(xml->name->getQNameLocalName()) || !sb.append('>'))
2777            goto out;
2778    }
2779
2780    str = sb.finishString();
2781out:
2782    js_LeaveLocalRootScopeWithResult(cx, str);
2783    return str;
2784}
2785
2786/* ECMA-357 10.2 */
2787static JSString *
2788ToXMLString(JSContext *cx, jsval v, uint32_t toSourceFlag)
2789{
2790    if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
2791        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2792                             JSMSG_BAD_XML_CONVERSION,
2793                             JSVAL_IS_NULL(v) ? js_null_str : js_undefined_str);
2794        return NULL;
2795    }
2796
2797    if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
2798        return ToString(cx, v);
2799
2800    if (JSVAL_IS_STRING(v)) {
2801        StringBuffer sb(cx);
2802        return EscapeElementValue(cx, sb, JSVAL_TO_STRING(v), toSourceFlag);
2803    }
2804
2805    JSObject *obj = JSVAL_TO_OBJECT(v);
2806    if (!obj->isXML()) {
2807        if (!ToPrimitive(cx, JSTYPE_STRING, &v))
2808            return NULL;
2809        JSString *str = ToString(cx, v);
2810        if (!str)
2811            return NULL;
2812        StringBuffer sb(cx);
2813        return EscapeElementValue(cx, sb, str, toSourceFlag);
2814    }
2815
2816    JSBool pretty;
2817    if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
2818        return NULL;
2819
2820    /* Handle non-element cases in this switch, returning from each case. */
2821    JS::Anchor<JSObject *> anch(obj);
2822    JSXML *xml = reinterpret_cast<JSXML *>(obj->getPrivate());
2823    return XMLToXMLString(cx, xml, NULL, toSourceFlag | 0, pretty);
2824}
2825
2826static JSObject *
2827ToAttributeName(JSContext *cx, jsval v)
2828{
2829    JSLinearString *uri, *prefix;
2830    JSObject *obj;
2831    Class *clasp;
2832    JSObject *qn;
2833
2834    JSAtom *name;
2835    if (JSVAL_IS_STRING(v)) {
2836        if (!js_ValueToAtom(cx, v, &name))
2837            return NULL;
2838        uri = prefix = cx->runtime->emptyString;
2839    } else {
2840        if (JSVAL_IS_PRIMITIVE(v)) {
2841            js_ReportValueError(cx, JSMSG_BAD_XML_ATTR_NAME,
2842                                JSDVG_IGNORE_STACK, v, NULL);
2843            return NULL;
2844        }
2845
2846        obj = JSVAL_TO_OBJECT(v);
2847        clasp = obj->getClass();
2848        if (clasp == &AttributeNameClass)
2849            return obj;
2850
2851        if (clasp == &QNameClass) {
2852            qn = obj;
2853            uri = qn->getNameURI();
2854            prefix = qn->getNamePrefix();
2855            name = qn->getQNameLocalName();
2856        } else {
2857            if (clasp == &AnyNameClass) {
2858                name = cx->runtime->atomState.starAtom;
2859            } else {
2860                if (!js_ValueToAtom(cx, v, &name))
2861                    return NULL;
2862            }
2863            uri = prefix = cx->runtime->emptyString;
2864        }
2865    }
2866
2867    qn = NewXMLAttributeName(cx, uri, prefix, name);
2868    if (!qn)
2869        return NULL;
2870    return qn;
2871}
2872
2873static void
2874ReportBadXMLName(JSContext *cx, const Value &idval)
2875{
2876    js_ReportValueError(cx, JSMSG_BAD_XML_NAME, JSDVG_IGNORE_STACK, idval, NULL);
2877}
2878
2879namespace js {
2880
2881bool
2882GetLocalNameFromFunctionQName(JSObject *qn, JSAtom **namep, JSContext *cx)
2883{
2884    JSAtom *atom = cx->runtime->atomState.functionNamespaceURIAtom;
2885    JSLinearString *uri = qn->getNameURI();
2886    if (uri && (uri == atom || EqualStrings(uri, atom))) {
2887        *namep = qn->getQNameLocalName();
2888        return true;
2889    }
2890    return false;
2891}
2892
2893} /* namespace js */
2894
2895bool
2896js_GetLocalNameFromFunctionQName(JSObject *obj, jsid *funidp, JSContext *cx)
2897{
2898    if (!obj->isQName())
2899        return false;
2900    JSAtom *name;
2901    if (GetLocalNameFromFunctionQName(obj, &name, cx)) {
2902        *funidp = ATOM_TO_JSID(name);
2903        return true;
2904    }
2905    return false;
2906}
2907
2908static JSObject *
2909ToXMLName(JSContext *cx, jsval v, jsid *funidp)
2910{
2911    JSAtom *atomizedName;
2912    JSString *name;
2913    JSObject *obj;
2914    Class *clasp;
2915    uint32_t index;
2916
2917    if (JSVAL_IS_STRING(v)) {
2918        name = JSVAL_TO_STRING(v);
2919    } else {
2920        if (JSVAL_IS_PRIMITIVE(v)) {
2921            ReportBadXMLName(cx, v);
2922            return NULL;
2923        }
2924
2925        obj = JSVAL_TO_OBJECT(v);
2926        clasp = obj->getClass();
2927        if (clasp == &AttributeNameClass || clasp == &QNameClass)
2928            goto out;
2929        if (clasp == &AnyNameClass) {
2930            name = cx->runtime->atomState.starAtom;
2931            goto construct;
2932        }
2933        name = ToStringSlow(cx, v);
2934        if (!name)
2935            return NULL;
2936    }
2937
2938    atomizedName = js_AtomizeString(cx, name);
2939    if (!atomizedName)
2940        return NULL;
2941
2942    /*
2943     * ECMA-357 10.6.1 step 1 seems to be incorrect.  The spec says:
2944     *
2945     * 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
2946     *
2947     * First, _P_ should be _s_, to refer to the given string.
2948     *
2949     * Second, why does ToXMLName applied to the string type throw TypeError
2950     * only for numeric literals without any leading or trailing whitespace?
2951     *
2952     * If the idea is to reject uint32_t property names, then the check needs to
2953     * be stricter, to exclude hexadecimal and floating point literals.
2954     */
2955    if (js_IdIsIndex(ATOM_TO_JSID(atomizedName), &index))
2956        goto bad;
2957
2958    if (*atomizedName->chars() == '@') {
2959        name = js_NewDependentString(cx, name, 1, name->length() - 1);
2960        if (!name)
2961            return NULL;
2962        *funidp = JSID_VOID;
2963        return ToAttributeName(cx, STRING_TO_JSVAL(name));
2964    }
2965
2966construct:
2967    v = STRING_TO_JSVAL(name);
2968    obj = JS_ConstructObjectWithArguments(cx, Jsvalify(&QNameClass), NULL, 1, &v);
2969    if (!obj)
2970        return NULL;
2971
2972out:
2973    JSAtom *localName;
2974    *funidp = GetLocalNameFromFunctionQName(obj, &localName, cx)
2975              ? ATOM_TO_JSID(localName)
2976              : JSID_VOID;
2977    return obj;
2978
2979bad:
2980    JSAutoByteString bytes;
2981    if (js_ValueToPrintable(cx, StringValue(name), &bytes))
2982        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XML_NAME, bytes.ptr());
2983    return NULL;
2984}
2985
2986/* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
2987static JSBool
2988AddInScopeNamespace(JSContext *cx, JSXML *xml, JSObject *ns)
2989{
2990    JSLinearString *prefix, *prefix2;
2991    JSObject *match, *ns2;
2992    uint32_t i, n, m;
2993
2994    if (xml->xml_class != JSXML_CLASS_ELEMENT)
2995        return JS_TRUE;
2996
2997    /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
2998    prefix = ns->getNamePrefix();
2999    if (!prefix) {
3000        match = NULL;
3001        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3002            ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3003            if (ns2 && EqualStrings(ns2->getNameURI(), ns->getNameURI())) {
3004                match = ns2;
3005                break;
3006            }
3007        }
3008        if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
3009            return JS_FALSE;
3010    } else {
3011        if (prefix->empty() && xml->name->getNameURI()->empty())
3012            return JS_TRUE;
3013        match = NULL;
3014#ifdef __GNUC__         /* suppress bogus gcc warnings */
3015        m = XML_NOT_FOUND;
3016#endif
3017        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3018            ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3019            if (ns2 && (prefix2 = ns2->getNamePrefix()) &&
3020                EqualStrings(prefix2, prefix)) {
3021                match = ns2;
3022                m = i;
3023                break;
3024            }
3025        }
3026        if (match && !EqualStrings(match->getNameURI(), ns->getNameURI())) {
3027            ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
3028                                  JSObject);
3029            JS_ASSERT(ns2 == match);
3030            match->clearNamePrefix();
3031            if (!AddInScopeNamespace(cx, xml, match))
3032                return JS_FALSE;
3033        }
3034        if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
3035            return JS_FALSE;
3036    }
3037
3038    /* OPTION: enforce that descendants have superset namespaces. */
3039    return JS_TRUE;
3040}
3041
3042/* ECMA-357 9.2.1.6 XMLList [[Append]]. */
3043static JSBool
3044Append(JSContext *cx, JSXML *list, JSXML *xml)
3045{
3046    JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
3047
3048    uint32_t i = list->xml_kids.length;
3049    if (xml->xml_class == JSXML_CLASS_LIST) {
3050        list->xml_target = xml->xml_target;
3051        list->xml_targetprop = xml->xml_targetprop;
3052        uint32_t n = JSXML_LENGTH(xml);
3053        if (!list->xml_kids.setCapacity(cx, i + n))
3054            return JS_FALSE;
3055        for (uint32_t j = 0; j < n; j++) {
3056            if (JSXML *kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML))
3057                XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
3058        }
3059        return JS_TRUE;
3060    }
3061
3062    list->xml_target = xml->parent;
3063    if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
3064        list->xml_targetprop = NULL;
3065    else
3066        list->xml_targetprop = xml->name;
3067    if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
3068        return JS_FALSE;
3069    return JS_TRUE;
3070}
3071
3072/* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
3073static JSXML *
3074DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags);
3075
3076static JSXML *
3077DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
3078{
3079    JSXML *copy;
3080
3081    /* Our caller may not be protecting newborns with a local root scope. */
3082    if (!js_EnterLocalRootScope(cx))
3083        return NULL;
3084    copy = DeepCopyInLRS(cx, xml, flags);
3085    if (copy) {
3086        if (obj) {
3087            /* Caller provided the object for this copy, hook 'em up. */
3088            obj->setPrivate(copy);
3089            copy->object = obj;
3090        } else if (!js_GetXMLObject(cx, copy)) {
3091            copy = NULL;
3092        }
3093    }
3094    js_LeaveLocalRootScopeWithResult(cx, copy);
3095    return copy;
3096}
3097
3098/*
3099 * (i) We must be in a local root scope (InLRS).
3100 * (ii) parent must have a rooted object.
3101 * (iii) from's owning object must be locked if not thread-local.
3102 */
3103static JSBool
3104DeepCopySetInLRS(JSContext *cx, JSXMLArray<JSXML> *from, JSXMLArray<JSXML> *to, JSXML *parent,
3105                 uintN flags)
3106{
3107    uint32_t j, n;
3108    JSXML *kid2;
3109    JSString *str;
3110
3111    n = from->length;
3112    if (!to->setCapacity(cx, n))
3113        return JS_FALSE;
3114
3115    JSXMLArrayCursor<JSXML> cursor(from);
3116    j = 0;
3117    while (JSXML *kid = cursor.getNext()) {
3118        if ((flags & XSF_IGNORE_COMMENTS) &&
3119            kid->xml_class == JSXML_CLASS_COMMENT) {
3120            continue;
3121        }
3122        if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
3123            kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
3124            continue;
3125        }
3126        if ((flags & XSF_IGNORE_WHITESPACE) &&
3127            (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
3128            continue;
3129        }
3130        kid2 = DeepCopyInLRS(cx, kid, flags);
3131        if (!kid2) {
3132            to->length = j;
3133            return JS_FALSE;
3134        }
3135
3136        if ((flags & XSF_IGNORE_WHITESPACE) &&
3137            n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
3138            str = ChompXMLWhitespace(cx, kid2->xml_value);
3139            if (!str) {
3140                to->length = j;
3141                return JS_FALSE;
3142            }
3143            kid2->xml_value = str;
3144        }
3145
3146        XMLARRAY_SET_MEMBER(to, j, kid2);
3147        ++j;
3148        if (parent->xml_class != JSXML_CLASS_LIST)
3149            kid2->parent = parent;
3150    }
3151
3152    if (j < n)
3153        to->trim();
3154    return JS_TRUE;
3155}
3156
3157static JSXML *
3158DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
3159{
3160    JSXML *copy;
3161    JSObject *qn;
3162    JSBool ok;
3163    uint32_t i, n;
3164    JSObject *ns, *ns2;
3165
3166    JS_CHECK_RECURSION(cx, return NULL);
3167
3168    copy = js_NewXML(cx, JSXMLClass(xml->xml_class));
3169    if (!copy)
3170        return NULL;
3171    qn = xml->name;
3172    if (qn) {
3173        qn = NewXMLQName(cx, qn->getNameURI(), qn->getNamePrefix(), qn->getQNameLocalName());
3174        if (!qn) {
3175            ok = JS_FALSE;
3176            goto out;
3177        }
3178    }
3179    copy->name = qn;
3180    copy->xml_flags = xml->xml_flags;
3181
3182    if (JSXML_HAS_VALUE(xml)) {
3183        copy->xml_value = xml->xml_value;
3184        ok = JS_TRUE;
3185    } else {
3186        ok = DeepCopySetInLRS(cx, &xml->xml_kids, &copy->xml_kids, copy, flags);
3187        if (!ok)
3188            goto out;
3189
3190        if (xml->xml_class == JSXML_CLASS_LIST) {
3191            copy->xml_target = xml->xml_target;
3192            copy->xml_targetprop = xml->xml_targetprop;
3193        } else {
3194            n = xml->xml_namespaces.length;
3195            ok = copy->xml_namespaces.setCapacity(cx, n);
3196            if (!ok)
3197                goto out;
3198            for (i = 0; i < n; i++) {
3199                ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3200                if (!ns)
3201                    continue;
3202                ns2 = NewXMLNamespace(cx, ns->getNamePrefix(), ns->getNameURI(),
3203                                      IsDeclared(ns));
3204                if (!ns2) {
3205                    copy->xml_namespaces.length = i;
3206                    ok = JS_FALSE;
3207                    goto out;
3208                }
3209                XMLARRAY_SET_MEMBER(&copy->xml_namespaces, i, ns2);
3210            }
3211
3212            ok = DeepCopySetInLRS(cx, &xml->xml_attrs, &copy->xml_attrs, copy,
3213                                  0);
3214            if (!ok)
3215                goto out;
3216        }
3217    }
3218
3219out:
3220    if (!ok)
3221        return NULL;
3222    return copy;
3223}
3224
3225/* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
3226static void
3227DeleteByIndex(JSContext *cx, JSXML *xml, uint32_t index)
3228{
3229    JSXML *kid;
3230
3231    if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
3232        kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3233        if (kid)
3234            kid->parent = NULL;
3235        XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3236    }
3237}
3238
3239typedef JSBool (*JSXMLNameMatcher)(JSObject *nameqn, JSXML *xml);
3240
3241static JSBool
3242MatchAttrName(JSObject *nameqn, JSXML *attr)
3243{
3244    JSObject *attrqn = attr->name;
3245    JSLinearString *localName = nameqn->getQNameLocalName();
3246    JSLinearString *uri;
3247
3248    return (IS_STAR(localName) ||
3249            EqualStrings(attrqn->getQNameLocalName(), localName)) &&
3250           (!(uri = nameqn->getNameURI()) ||
3251            EqualStrings(attrqn->getNameURI(), uri));
3252}
3253
3254static JSBool
3255MatchElemName(JSObject *nameqn, JSXML *elem)
3256{
3257    JSLinearString *localName = nameqn->getQNameLocalName();
3258    JSLinearString *uri;
3259
3260    return (IS_STAR(localName) ||
3261            (elem->xml_class == JSXML_CLASS_ELEMENT &&
3262             EqualStrings(elem->name->getQNameLocalName(), localName))) &&
3263           (!(uri = nameqn->getNameURI()) ||
3264            (elem->xml_class == JSXML_CLASS_ELEMENT &&
3265             EqualStrings(elem->name->getNameURI(), uri)));
3266}
3267
3268/* ECMA-357 9.1.1.8 XML [[Descendants]] and 9.2.1.8 XMLList [[Descendants]]. */
3269static JSBool
3270DescendantsHelper(JSContext *cx, JSXML *xml, JSObject *nameqn, JSXML *list)
3271{
3272    uint32_t i, n;
3273    JSXML *attr, *kid;
3274
3275    JS_CHECK_RECURSION(cx, return JS_FALSE);
3276
3277    if (xml->xml_class == JSXML_CLASS_ELEMENT &&
3278        nameqn->getClass() == &AttributeNameClass) {
3279        for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
3280            attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3281            if (attr && MatchAttrName(nameqn, attr)) {
3282                if (!Append(cx, list, attr))
3283                    return JS_FALSE;
3284            }
3285        }
3286    }
3287
3288    for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
3289        kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3290        if (!kid)
3291            continue;
3292        if (nameqn->getClass() != &AttributeNameClass &&
3293            MatchElemName(nameqn, kid)) {
3294            if (!Append(cx, list, kid))
3295                return JS_FALSE;
3296        }
3297        if (!DescendantsHelper(cx, kid, nameqn, list))
3298            return JS_FALSE;
3299    }
3300    return JS_TRUE;
3301}
3302
3303static JSXML *
3304Descendants(JSContext *cx, JSXML *xml, jsval id)
3305{
3306    jsid funid;
3307    JSObject *nameqn;
3308    JSObject *listobj;
3309    JSXML *list, *kid;
3310    uint32_t i, n;
3311    JSBool ok;
3312
3313    nameqn = ToXMLName(cx, id, &funid);
3314    if (!nameqn)
3315        return NULL;
3316
3317    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3318    if (!listobj)
3319        return NULL;
3320    list = (JSXML *) listobj->getPrivate();
3321    if (!JSID_IS_VOID(funid))
3322        return list;
3323
3324    /*
3325     * Protect nameqn's object and strings from GC by linking list to it
3326     * temporarily.  The newborn GC root for the last allocated object
3327     * protects listobj, which protects list. Any other object allocations
3328     * occurring beneath DescendantsHelper use local roots.
3329     */
3330    list->name = nameqn;
3331    if (!js_EnterLocalRootScope(cx))
3332        return NULL;
3333    if (xml->xml_class == JSXML_CLASS_LIST) {
3334        ok = JS_TRUE;
3335        for (i = 0, n = xml->xml_kids.length; i < n; i++) {
3336            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3337            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3338                ok = DescendantsHelper(cx, kid, nameqn, list);
3339                if (!ok)
3340                    break;
3341            }
3342        }
3343    } else {
3344        ok = DescendantsHelper(cx, xml, nameqn, list);
3345    }
3346    js_LeaveLocalRootScopeWithResult(cx, list);
3347    if (!ok)
3348        return NULL;
3349    list->name = NULL;
3350    return list;
3351}
3352
3353/* Recursive (JSXML *) parameterized version of Equals. */
3354static JSBool
3355XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
3356{
3357    JSObject *qn, *vqn;
3358    uint32_t i, j, n;
3359    JSXML *kid, *vkid, *attr, *vattr;
3360    JSObject *xobj, *vobj;
3361
3362retry:
3363    if (xml->xml_class != vxml->xml_class) {
3364        if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
3365            xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3366            if (xml)
3367                goto retry;
3368        }
3369        if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
3370            vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
3371            if (vxml)
3372                goto retry;
3373        }
3374        *bp = JS_FALSE;
3375        return JS_TRUE;
3376    }
3377
3378    qn = xml->name;
3379    vqn = vxml->name;
3380    if (qn) {
3381        *bp = vqn &&
3382              EqualStrings(qn->getQNameLocalName(), vqn->getQNameLocalName()) &&
3383              EqualStrings(qn->getNameURI(), vqn->getNameURI());
3384    } else {
3385        *bp = vqn == NULL;
3386    }
3387    if (!*bp)
3388        return JS_TRUE;
3389
3390    if (JSXML_HAS_VALUE(xml)) {
3391        bool equal;
3392        if (!EqualStrings(cx, xml->xml_value, vxml->xml_value, &equal))
3393            return JS_FALSE;
3394        *bp = equal;
3395    } else if (xml->xml_kids.length != vxml->xml_kids.length) {
3396        *bp = JS_FALSE;
3397    } else {
3398        {
3399            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
3400            JSXMLArrayCursor<JSXML> vcursor(&vxml->xml_kids);
3401            for (;;) {
3402                kid = cursor.getNext();
3403                vkid = vcursor.getNext();
3404                if (!kid || !vkid) {
3405                    *bp = !kid && !vkid;
3406                    break;
3407                }
3408                xobj = js_GetXMLObject(cx, kid);
3409                vobj = js_GetXMLObject(cx, vkid);
3410                if (!xobj || !vobj ||
3411                    !js_TestXMLEquality(cx, ObjectValue(*xobj), ObjectValue(*vobj), bp))
3412                    return JS_FALSE;
3413                if (!*bp)
3414                    break;
3415            }
3416        }
3417
3418        if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
3419            n = xml->xml_attrs.length;
3420            if (n != vxml->xml_attrs.length)
3421                *bp = JS_FALSE;
3422            for (i = 0; *bp && i < n; i++) {
3423                attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3424                if (!attr)
3425                    continue;
3426                j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
3427                if (j == XML_NOT_FOUND) {
3428                    *bp = JS_FALSE;
3429                    break;
3430                }
3431                vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
3432                if (!vattr)
3433                    continue;
3434                bool equal;
3435                if (!EqualStrings(cx, attr->xml_value, vattr->xml_value, &equal))
3436                    return JS_FALSE;
3437                *bp = equal;
3438            }
3439        }
3440    }
3441
3442    return JS_TRUE;
3443}
3444
3445/* ECMA-357 9.1.1.9 XML [[Equals]] and 9.2.1.9 XMLList [[Equals]]. */
3446static JSBool
3447Equals(JSContext *cx, JSXML *xml, jsval v, JSBool *bp)
3448{
3449    JSObject *vobj;
3450    JSXML *vxml;
3451
3452    if (JSVAL_IS_PRIMITIVE(v)) {
3453        *bp = JS_FALSE;
3454        if (xml->xml_class == JSXML_CLASS_LIST) {
3455            if (xml->xml_kids.length == 1) {
3456                vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3457                if (!vxml)
3458                    return JS_TRUE;
3459                vobj = js_GetXMLObject(cx, vxml);
3460                if (!vobj)
3461                    return JS_FALSE;
3462                return js_TestXMLEquality(cx, ObjectValue(*vobj), v, bp);
3463            }
3464            if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
3465                *bp = JS_TRUE;
3466        }
3467    } else {
3468        vobj = JSVAL_TO_OBJECT(v);
3469        if (!vobj->isXML()) {
3470            *bp = JS_FALSE;
3471        } else {
3472            vxml = (JSXML *) vobj->getPrivate();
3473            if (!XMLEquals(cx, xml, vxml, bp))
3474                return JS_FALSE;
3475        }
3476    }
3477    return JS_TRUE;
3478}
3479
3480static JSBool
3481CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
3482{
3483    JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);
3484
3485    do {
3486        if (xml == kid) {
3487            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3488                                 JSMSG_CYCLIC_VALUE, js_XML_str);
3489            return JS_FALSE;
3490        }
3491    } while ((xml = xml->parent) != NULL);
3492
3493    return JS_TRUE;
3494}
3495
3496/* ECMA-357 9.1.1.11 XML [[Insert]]. */
3497static JSBool
3498Insert(JSContext *cx, JSXML *xml, uint32_t i, jsval v)
3499{
3500    uint32_t j, n;
3501    JSXML *vxml, *kid;
3502    JSObject *vobj;
3503    JSString *str;
3504
3505    if (!JSXML_HAS_KIDS(xml))
3506        return JS_TRUE;
3507
3508    n = 1;
3509    vxml = NULL;
3510    if (!JSVAL_IS_PRIMITIVE(v)) {
3511        vobj = JSVAL_TO_OBJECT(v);
3512        if (vobj->isXML()) {
3513            vxml = (JSXML *) vobj->getPrivate();
3514            if (vxml->xml_class == JSXML_CLASS_LIST) {
3515                n = vxml->xml_kids.length;
3516                if (n == 0)
3517                    return JS_TRUE;
3518                for (j = 0; j < n; j++) {
3519                    kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3520                    if (!kid)
3521                        continue;
3522                    if (!CheckCycle(cx, xml, kid))
3523                        return JS_FALSE;
3524                }
3525            } else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
3526                /* OPTION: enforce that descendants have superset namespaces. */
3527                if (!CheckCycle(cx, xml, vxml))
3528                    return JS_FALSE;
3529            }
3530        }
3531    }
3532    if (!vxml) {
3533        str = ToString(cx, v);
3534        if (!str)
3535            return JS_FALSE;
3536
3537        vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3538        if (!vxml)
3539            return JS_FALSE;
3540        vxml->xml_value = str;
3541    }
3542
3543    if (i > xml->xml_kids.length)
3544        i = xml->xml_kids.length;
3545
3546    if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
3547        return JS_FALSE;
3548
3549    if (vxml->xml_class == JSXML_CLASS_LIST) {
3550        for (j = 0; j < n; j++) {
3551            kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3552            if (!kid)
3553                continue;
3554            kid->parent = xml;
3555            XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);
3556
3557            /* OPTION: enforce that descendants have superset namespaces. */
3558        }
3559    } else {
3560        vxml->parent = xml;
3561        XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
3562    }
3563    return JS_TRUE;
3564}
3565
3566/* ECMA-357 9.1.1.12 XML [[Replace]]. */
3567static JSBool
3568Replace(JSContext *cx, JSXML *xml, uint32_t i, jsval v)
3569{
3570    uint32_t n;
3571    JSXML *vxml, *kid;
3572    JSObject *vobj;
3573    JSString *str;
3574
3575    if (!JSXML_HAS_KIDS(xml))
3576        return JS_TRUE;
3577
3578    /*
3579     * 9.1.1.12
3580     * [[Replace]] handles _i >= x.[[Length]]_ by incrementing _x.[[Length]_.
3581     * It should therefore constrain callers to pass in _i <= x.[[Length]]_.
3582     */
3583    n = xml->xml_kids.length;
3584    if (i > n)
3585        i = n;
3586
3587    vxml = NULL;
3588    if (!JSVAL_IS_PRIMITIVE(v)) {
3589        vobj = JSVAL_TO_OBJECT(v);
3590        if (vobj->isXML())
3591            vxml = (JSXML *) vobj->getPrivate();
3592    }
3593
3594    switch (vxml ? JSXMLClass(vxml->xml_class) : JSXML_CLASS_LIMIT) {
3595      case JSXML_CLASS_ELEMENT:
3596        /* OPTION: enforce that descendants have superset namespaces. */
3597        if (!CheckCycle(cx, xml, vxml))
3598            return JS_FALSE;
3599      case JSXML_CLASS_COMMENT:
3600      case JSXML_CLASS_PROCESSING_INSTRUCTION:
3601      case JSXML_CLASS_TEXT:
3602        goto do_replace;
3603
3604      case JSXML_CLASS_LIST:
3605        if (i < n)
3606            DeleteByIndex(cx, xml, i);
3607        if (!Insert(cx, xml, i, v))
3608            return JS_FALSE;
3609        break;
3610
3611      default:
3612        str = ToString(cx, v);
3613        if (!str)
3614            return JS_FALSE;
3615
3616        vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3617        if (!vxml)
3618            return JS_FALSE;
3619        vxml->xml_value = str;
3620
3621      do_replace:
3622        vxml->parent = xml;
3623        if (i < n) {
3624            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3625            if (kid)
3626                kid->parent = NULL;
3627        }
3628        if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
3629            return JS_FALSE;
3630        break;
3631    }
3632
3633    return JS_TRUE;
3634}
3635
3636/* ECMA-357 9.1.1.3 XML [[Delete]], 9.2.1.3 XML [[Delete]] qname cases. */
3637static void
3638DeleteNamedProperty(JSContext *cx, JSXML *xml, JSObject *nameqn,
3639                    JSBool attributes)
3640{
3641    JSXMLArray<JSXML> *array;
3642    uint32_t index, deleteCount;
3643    JSXML *kid;
3644    JSXMLNameMatcher matcher;
3645
3646    if (xml->xml_class == JSXML_CLASS_LIST) {
3647        array = &xml->xml_kids;
3648        for (index = 0; index < array->length; index++) {
3649            kid = XMLARRAY_MEMBER(array, index, JSXML);
3650            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT)
3651                DeleteNamedProperty(cx, kid, nameqn, attributes);
3652        }
3653    } else if (xml->xml_class == JSXML_CLASS_ELEMENT) {
3654        if (attributes) {
3655            array = &xml->xml_attrs;
3656            matcher = MatchAttrName;
3657        } else {
3658            array = &xml->xml_kids;
3659            matcher = MatchElemName;
3660        }
3661        deleteCount = 0;
3662        for (index = 0; index < array->length; index++) {
3663            kid = XMLARRAY_MEMBER(array, index, JSXML);
3664            if (kid && matcher(nameqn, kid)) {
3665                kid->parent = NULL;
3666                XMLArrayDelete(cx, array, index, JS_FALSE);
3667                ++deleteCount;
3668            } else if (deleteCount != 0) {
3669                XMLARRAY_SET_MEMBER(array,
3670                                    index - deleteCount,
3671                                    array->vector[index]);
3672            }
3673        }
3674        array->length -= deleteCount;
3675    }
3676}
3677
3678/* ECMA-357 9.2.1.3 index case. */
3679static void
3680DeleteListElement(JSContext *cx, JSXML *xml, uint32_t index)
3681{
3682    JSXML *kid, *parent;
3683    uint32_t kidIndex;
3684
3685    JS_ASSERT(xml->xml_class == JSXML_CLASS_LIST);
3686
3687    if (index < xml->xml_kids.length) {
3688        kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3689        if (kid) {
3690            parent = kid->parent;
3691            if (parent) {
3692                JS_ASSERT(parent != xml);
3693                JS_ASSERT(JSXML_HAS_KIDS(parent));
3694
3695                if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
3696                    DeleteNamedProperty(cx, parent, kid->name, JS_TRUE);
3697                } else {
3698                    kidIndex = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid,
3699                                                    pointer_match);
3700                    JS_ASSERT(kidIndex != XML_NOT_FOUND);
3701                    DeleteByIndex(cx, parent, kidIndex);
3702                }
3703            }
3704            XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3705        }
3706    }
3707}
3708
3709static JSBool
3710SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
3711{
3712    JSXMLArray<JSObject> *nsarray;
3713    uint32_t i, n;
3714    JSObject *ns;
3715
3716    nsarray = &xml->xml_namespaces;
3717    while ((xml = xml->parent) != NULL) {
3718        for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3719            ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3720            if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
3721                if (!XMLARRAY_APPEND(cx, nsarray, ns))
3722                    return JS_FALSE;
3723            }
3724        }
3725    }
3726    return JS_TRUE;
3727}
3728
3729static JSBool
3730GetNamedProperty(JSContext *cx, JSXML *xml, JSObject* nameqn, JSXML *list)
3731{
3732    JSXMLArray<JSXML> *array;
3733    JSXMLNameMatcher matcher;
3734    JSBool attrs;
3735
3736    if (xml->xml_class == JSXML_CLASS_LIST) {
3737        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
3738        while (JSXML *kid = cursor.getNext()) {
3739            if (kid->xml_class == JSXML_CLASS_ELEMENT &&
3740                !GetNamedProperty(cx, kid, nameqn, list)) {
3741                return JS_FALSE;
3742            }
3743        }
3744    } else if (xml->xml_class == JSXML_CLASS_ELEMENT) {
3745        attrs = (nameqn->getClass() == &AttributeNameClass);
3746        if (attrs) {
3747            array = &xml->xml_attrs;
3748            matcher = MatchAttrName;
3749        } else {
3750            array = &xml->xml_kids;
3751            matcher = MatchElemName;
3752        }
3753
3754        JSXMLArrayCursor<JSXML> cursor(array);
3755        while (JSXML *kid = cursor.getNext()) {
3756            if (matcher(nameqn, kid)) {
3757                if (!attrs &&
3758                    kid->xml_class == JSXML_CLASS_ELEMENT &&
3759                    !SyncInScopeNamespaces(cx, kid)) {
3760                    return JS_FALSE;
3761                }
3762                if (!Append(cx, list, kid))
3763                    return JS_FALSE;
3764            }
3765        }
3766    }
3767
3768    return JS_TRUE;
3769}
3770
3771/* ECMA-357 9.1.1.1 XML [[Get]] and 9.2.1.1 XMLList [[Get]]. */
3772static JSBool
3773GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
3774{
3775    JSXML *xml, *list, *kid;
3776    uint32_t index;
3777    JSObject *kidobj, *listobj;
3778    JSObject *nameqn;
3779    jsid funid;
3780
3781    if (!obj->isXML())
3782        return true;
3783    xml = (JSXML *) obj->getPrivate();
3784    if (!xml)
3785        return true;
3786
3787    if (js_IdIsIndex(id, &index)) {
3788        if (!JSXML_HAS_KIDS(xml)) {
3789            *vp = (index == 0) ? OBJECT_TO_JSVAL(obj) : JSVAL_VOID;
3790        } else {
3791            /*
3792             * ECMA-357 9.2.1.1 starts here.
3793             *
3794             * Erratum: 9.2 is not completely clear that indexed properties
3795             * correspond to kids, but that's what it seems to say, and it's
3796             * what any sane user would want.
3797             */
3798            if (index < xml->xml_kids.length) {
3799                kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3800                if (!kid) {
3801                    *vp = JSVAL_VOID;
3802                    return true;
3803                }
3804                kidobj = js_GetXMLObject(cx, kid);
3805                if (!kidobj)
3806                    return false;
3807
3808                *vp = OBJECT_TO_JSVAL(kidobj);
3809            } else {
3810                *vp = JSVAL_VOID;
3811            }
3812        }
3813        return true;
3814    }
3815
3816    /*
3817     * ECMA-357 9.2.1.1/9.1.1.1 qname case.
3818     */
3819    nameqn = ToXMLName(cx, IdToJsval(id), &funid);
3820    if (!nameqn)
3821        return false;
3822    if (!JSID_IS_VOID(funid))
3823        return GetXMLFunction(cx, obj, funid, vp);
3824
3825    jsval roots[2] = { OBJECT_TO_JSVAL(nameqn), JSVAL_NULL };
3826    AutoArrayRooter tvr(cx, ArrayLength(roots), roots);
3827
3828    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3829    if (!listobj)
3830        return false;
3831
3832    roots[1] = OBJECT_TO_JSVAL(listobj);
3833
3834    list = (JSXML *) listobj->getPrivate();
3835    if (!GetNamedProperty(cx, xml, nameqn, list))
3836        return false;
3837
3838    /*
3839     * Erratum: ECMA-357 9.1.1.1 misses that [[Append]] sets the
3840     * given list's [[TargetProperty]] to the property that is being
3841     * appended. This means that any use of the internal [[Get]]
3842     * property returns a list which, when used by e.g. [[Insert]]
3843     * duplicates the last element matched by id. See bug 336921.
3844     */
3845    list->xml_target = xml;
3846    list->xml_targetprop = nameqn;
3847    *vp = OBJECT_TO_JSVAL(listobj);
3848    return true;
3849}
3850
3851static JSXML *
3852CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
3853{
3854    JS_ASSERT(xml->object != obj);
3855
3856    xml = DeepCopy(cx, xml, obj, 0);
3857    if (!xml)
3858        return NULL;
3859
3860    JS_ASSERT(xml->object == obj);
3861    return xml;
3862}
3863
3864#define CHECK_COPY_ON_WRITE(cx,xml,obj)                                       \
3865    (xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))
3866
3867static JSString *
3868KidToString(JSContext *cx, JSXML *xml, uint32_t index)
3869{
3870    JSXML *kid;
3871    JSObject *kidobj;
3872
3873    kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3874    if (!kid)
3875        return cx->runtime->emptyString;
3876    kidobj = js_GetXMLObject(cx, kid);
3877    if (!kidobj)
3878        return NULL;
3879    return ToString(cx, ObjectValue(*kidobj));
3880}
3881
3882/* Forward declared -- its implementation uses other statics that call it. */
3883static JSBool
3884ResolveValue(JSContext *cx, JSXML *list, JSXML **result);
3885
3886/* ECMA-357 9.1.1.2 XML [[Put]] and 9.2.1.2 XMLList [[Put]]. */
3887static JSBool
3888PutProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
3889{
3890    JSBool ok, primitiveAssign;
3891    enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
3892    JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
3893    JSObject *vobj, *nameobj, *attrobj, *parentobj, *kidobj, *copyobj;
3894    JSObject *targetprop, *nameqn, *attrqn;
3895    uint32_t index, i, j, k, n, q, matchIndex;
3896    jsval attrval, nsval;
3897    jsid funid;
3898    JSObject *ns;
3899
3900    if (!obj->isXML())
3901        return JS_TRUE;
3902    xml = (JSXML *) obj->getPrivate();
3903    if (!xml)
3904        return JS_TRUE;
3905
3906    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
3907    if (!xml)
3908        return JS_FALSE;
3909
3910    /* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
3911    vxml = NULL;
3912    if (!JSVAL_IS_PRIMITIVE(*vp)) {
3913        vobj = JSVAL_TO_OBJECT(*vp);
3914        if (vobj->isXML())
3915            vxml = (JSXML *) vobj->getPrivate();
3916    }
3917
3918    ok = js_EnterLocalRootScope(cx);
3919    if (!ok)
3920        return JS_FALSE;
3921
3922    MUST_FLOW_THROUGH("out");
3923    jsval roots[3];
3924    roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
3925    roots[ID_ROOT] = IdToJsval(id);
3926    roots[VAL_ROOT] = *vp;
3927    AutoArrayRooter tvr(cx, ArrayLength(roots), roots);
3928
3929    if (js_IdIsIndex(id, &index)) {
3930        if (xml->xml_class != JSXML_CLASS_LIST) {
3931            /* See NOTE in spec: this variation is reserved for future use. */
3932            ReportBadXMLName(cx, IdToValue(id));
3933            goto bad;
3934        }
3935
3936        /*
3937         * Step 1 of ECMA-357 9.2.1.2 index case sets i to the property index.
3938         */
3939        i = index;
3940
3941        /* 2(a-b). */
3942        if (xml->xml_target) {
3943            ok = ResolveValue(cx, xml->xml_target, &rxml);
3944            if (!ok)
3945                goto out;
3946            if (!rxml)
3947                goto out;
3948            JS_ASSERT(rxml->object);
3949        } else {
3950            rxml = NULL;
3951        }
3952
3953        /* 2(c). */
3954        if (index >= xml->xml_kids.length) {
3955            /* 2(c)(i). */
3956            if (rxml) {
3957                if (rxml->xml_class == JSXML_CLASS_LIST) {
3958                    if (rxml->xml_kids.length != 1)
3959                        goto out;
3960                    rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
3961                    if (!rxml)
3962                        goto out;
3963                    ok = js_GetXMLObject(cx, rxml) != NULL;
3964                    if (!ok)
3965                        goto out;
3966                }
3967
3968                /*
3969                 * Erratum: ECMA-357 9.2.1.2 step 2(c)(ii) sets
3970                 * _y.[[Parent]] = r_ where _r_ is the result of
3971                 * [[ResolveValue]] called on _x.[[TargetObject]] in
3972                 * 2(a)(i).  This can result in text parenting text:
3973                 *
3974                 *    var MYXML = new XML();
3975                 *    MYXML.appendChild(new XML("<TEAM>Giants</TEAM>"));
3976                 *
3977                 * (testcase from Werner Sharp <wsharp@macromedia.com>).
3978                 *
3979                 * To match insertChildAfter, insertChildBefore,
3980                 * prependChild, and setChildren, we should silently
3981                 * do nothing in this case.
3982                 */
3983                if (!JSXML_HAS_KIDS(rxml))
3984                    goto out;
3985            }
3986
3987            /* 2(c)(ii) is distributed below as several js_NewXML calls. */
3988            targetprop = xml->xml_targetprop;
3989            if (!targetprop || IS_STAR(targetprop->getQNameLocalName())) {
3990                /* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
3991                kid = js_NewXML(cx, JSXML_CLASS_TEXT);
3992                if (!kid)
3993                    goto bad;
3994            } else {
3995                nameobj = targetprop;
3996                if (nameobj->getClass() == &AttributeNameClass) {
3997                    /*
3998                     * 2(c)(iii)(1-3).
3999                     * Note that rxml can't be null here, because target
4000                     * and targetprop are non-null.
4001                     */
4002                    ok = GetProperty(cx, rxml->object, id, &attrval);
4003                    if (!ok)
4004                        goto out;
4005                    if (JSVAL_IS_PRIMITIVE(attrval))    /* no such attribute */
4006                        goto out;
4007                    attrobj = JSVAL_TO_OBJECT(attrval);
4008                    attr = (JSXML *) attrobj->getPrivate();
4009                    if (JSXML_LENGTH(attr) != 0)
4010                        goto out;
4011
4012                    kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4013                } else {
4014                    /* 2(c)(v). */
4015                    kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
4016                }
4017                if (!kid)
4018                    goto bad;
4019
4020                /* An important bit of 2(c)(ii). */
4021                kid->name = targetprop;
4022            }
4023
4024            /* Final important bit of 2(c)(ii). */
4025            kid->parent = rxml;
4026
4027            /* 2(c)(vi-vii). */
4028            i = xml->xml_kids.length;
4029            if (kid->xml_class != JSXML_CLASS_ATTRIBUTE) {
4030                /*
4031                 * 2(c)(vii)(1) tests whether _y.[[Parent]]_ is not null.
4032                 * y.[[Parent]] is here called kid->parent, which we know
4033                 * from 2(c)(ii) is _r_, here called rxml.  So let's just
4034                 * test that!  Erratum, the spec should be simpler here.
4035                 */
4036                if (rxml) {
4037                    JS_ASSERT(JSXML_HAS_KIDS(rxml));
4038                    n = rxml->xml_kids.length;
4039                    j = n - 1;
4040                    if (n != 0 && i != 0) {
4041                        for (n = j, j = 0; j < n; j++) {
4042                            if (rxml->xml_kids.vector[j] ==
4043                                xml->xml_kids.vector[i-1]) {
4044                                break;
4045                            }
4046                        }
4047                    }
4048
4049                    kidobj = js_GetXMLObject(cx, kid);
4050                    if (!kidobj)
4051                        goto bad;
4052                    ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
4053                    if (!ok)
4054                        goto out;
4055                }
4056
4057                /*
4058                 * 2(c)(vii)(2-3).
4059                 * Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
4060                 * typo for [[TargetProperty]].
4061                 */
4062                if (vxml) {
4063                    kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
4064                        ? vxml->xml_targetprop
4065                        : vxml->name;
4066                }
4067            }
4068
4069            /* 2(c)(viii). */
4070            ok = Append(cx, xml, kid);
4071            if (!ok)
4072                goto out;
4073        }
4074
4075        /* 2(d). */
4076        if (!vxml ||
4077            vxml->xml_class == JSXML_CLASS_TEXT ||
4078            vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4079            ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4080            if (!ok)
4081                goto out;
4082            roots[VAL_ROOT] = *vp;
4083        }
4084
4085        /* 2(e). */
4086        kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
4087        if (!kid)
4088            goto out;
4089        parent = kid->parent;
4090        if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
4091            nameobj = kid->name;
4092            if (nameobj->getClass() != &AttributeNameClass) {
4093                nameobj = NewXMLAttributeName(cx, nameobj->getNameURI(), nameobj->getNamePrefix(),
4094                                              nameobj->getQNameLocalName());
4095                if (!nameobj)
4096                    goto bad;
4097            }
4098            id = OBJECT_TO_JSID(nameobj);
4099
4100            if (parent) {
4101                /* 2(e)(i). */
4102                parentobj = js_GetXMLObject(cx, parent);
4103                if (!parentobj)
4104                    goto bad;
4105                ok = PutProperty(cx, parentobj, id, strict, vp);
4106                if (!ok)
4107                    goto out;
4108
4109                /* 2(e)(ii). */
4110                ok = GetProperty(cx, parentobj, id, vp);
4111                if (!ok)
4112                    goto out;
4113                attr = (JSXML *) JSVAL_TO_OBJECT(*vp)->getPrivate();
4114
4115                /* 2(e)(iii) - the length check comes from the bug 375406. */
4116                if (attr->xml_kids.length != 0)
4117                    xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
4118            }
4119        }
4120
4121        /* 2(f). */
4122        else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4123            /*
4124             * 2(f)(i)
4125             *
4126             * Erratum: the spec says to create a shallow copy _c_ of _V_, but
4127             * if we do that we never change the parent of each child in the
4128             * list.  Since [[Put]] when called on an XML object deeply copies
4129             * the provided list _V_, we also do so here.  Perhaps the shallow
4130             * copy was a misguided optimization?
4131             */
4132            copy = DeepCopyInLRS(cx, vxml, 0);
4133            if (!copy)
4134                goto bad;
4135            copyobj = js_GetXMLObject(cx, copy);
4136            if (!copyobj)
4137                goto bad;
4138
4139            JS_ASSERT(parent != xml);
4140            if (parent) {
4141                q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, pointer_match);
4142                JS_ASSERT(q != XML_NOT_FOUND);
4143                ok = Replace(cx, parent, q, OBJECT_TO_JSVAL(copyobj));
4144                if (!ok)
4145                    goto out;
4146
4147#ifdef DEBUG
4148                /* Erratum: this loop in the spec is useless. */
4149                for (j = 0, n = copy->xml_kids.length; j < n; j++) {
4150                    kid2 = XMLARRAY_MEMBER(&parent->xml_kids, q + j, JSXML);
4151                    JS_ASSERT(XMLARRAY_MEMBER(&copy->xml_kids, j, JSXML)
4152                              == kid2);
4153                }
4154#endif
4155            }
4156
4157            /*
4158             * 2(f)(iv-vi).
4159             * Erratum: notice the unhandled zero-length V basis case and
4160             * the off-by-one errors for the n != 0 cases in the spec.
4161             */
4162            n = copy->xml_kids.length;
4163            if (n == 0) {
4164                XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
4165            } else {
4166                ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
4167                if (!ok)
4168                    goto out;
4169
4170                for (j = 0; j < n; j++)
4171                    xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
4172            }
4173        }
4174
4175        /* 2(g). */
4176        else if (vxml || JSXML_HAS_VALUE(kid)) {
4177            if (parent) {
4178                q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, pointer_match);
4179                JS_ASSERT(q != XML_NOT_FOUND);
4180                ok = Replace(cx, parent, q, *vp);
4181                if (!ok)
4182                    goto out;
4183
4184                vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
4185                if (!vxml)
4186                    goto out;
4187                roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vxml->object);
4188            }
4189
4190            /*
4191             * 2(g)(iii).
4192             * Erratum: _V_ may not be of type XML, but all index-named
4193             * properties _x[i]_ in an XMLList _x_ must be of type XML,
4194             * according to 9.2.1.1 Overview and other places in the spec.
4195             *
4196             * Thanks to 2(d), we know _V_ (*vp here) is either a string
4197             * or an XML/XMLList object.  If *vp is a string, call ToXML
4198             * on it to satisfy the constraint.
4199             */
4200            if (!vxml) {
4201                JS_ASSERT(JSVAL_IS_STRING(*vp));
4202                vobj = ToXML(cx, *vp);
4203                if (!vobj)
4204                    goto bad;
4205                roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vobj);
4206                vxml = (JSXML *) vobj->getPrivate();
4207            }
4208            XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
4209        }
4210
4211        /* 2(h). */
4212        else {
4213            kidobj = js_GetXMLObject(cx, kid);
4214            if (!kidobj)
4215                goto bad;
4216            id = ATOM_TO_JSID(cx->runtime->atomState.starAtom);
4217            ok = PutProperty(cx, kidobj, id, strict, vp);
4218            if (!ok)
4219                goto out;
4220        }
4221    } else {
4222        /*
4223         * ECMA-357 9.2.1.2/9.1.1.2 qname case.
4224         */
4225        nameqn = ToXMLName(cx, IdToJsval(id), &funid);
4226        if (!nameqn)
4227            goto bad;
4228        if (!JSID_IS_VOID(funid)) {
4229            ok = js_SetPropertyHelper(cx, obj, funid, 0, vp, false);
4230            goto out;
4231        }
4232        nameobj = nameqn;
4233        roots[ID_ROOT] = OBJECT_TO_JSVAL(nameobj);
4234
4235        if (xml->xml_class == JSXML_CLASS_LIST) {
4236            /*
4237             * Step 3 of 9.2.1.2.
4238             * Erratum: if x.[[Length]] > 1 or [[ResolveValue]] returns null
4239             * or an r with r.[[Length]] != 1, throw TypeError.
4240             */
4241            n = JSXML_LENGTH(xml);
4242            if (n > 1)
4243                goto type_error;
4244            if (n == 0) {
4245                ok = ResolveValue(cx, xml, &rxml);
4246                if (!ok)
4247                    goto out;
4248                if (!rxml || JSXML_LENGTH(rxml) != 1)
4249                    goto type_error;
4250                ok = Append(cx, xml, rxml);
4251                if (!ok)
4252                    goto out;
4253            }
4254            JS_ASSERT(JSXML_LENGTH(xml) == 1);
4255            xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
4256            if (!xml)
4257                goto out;
4258            JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
4259            obj = js_GetXMLObject(cx, xml);
4260            if (!obj)
4261                goto bad;
4262            roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
4263
4264            /* FALL THROUGH to non-list case */
4265        }
4266
4267        /*
4268         * ECMA-357 9.1.1.2.
4269         * Erratum: move steps 3 and 4 to before 1 and 2, to avoid wasted
4270         * effort in ToString or [[DeepCopy]].
4271         */
4272
4273        if (JSXML_HAS_VALUE(xml))
4274            goto out;
4275
4276        if (!vxml ||
4277            vxml->xml_class == JSXML_CLASS_TEXT ||
4278            vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4279            ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4280            if (!ok)
4281                goto out;
4282        } else {
4283            rxml = DeepCopyInLRS(cx, vxml, 0);
4284            if (!rxml || !js_GetXMLObject(cx, rxml))
4285                goto bad;
4286            vxml = rxml;
4287            *vp = OBJECT_TO_JSVAL(vxml->object);
4288        }
4289        roots[VAL_ROOT] = *vp;
4290
4291        /*
4292         * 6.
4293         * Erratum: why is this done here, so early? use is way later....
4294         */
4295        ok = js_GetDefaultXMLNamespace(cx, &nsval);
4296        if (!ok)
4297            goto out;
4298
4299        if (nameobj->getClass() == &AttributeNameClass) {
4300            /* 7(a). */
4301            if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
4302                goto out;
4303
4304            /* 7(b-c). */
4305            if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4306                n = vxml->xml_kids.length;
4307                if (n == 0) {
4308                    *vp = STRING_TO_JSVAL(cx->runtime->emptyString);
4309                } else {
4310                    JSString *left = KidToString(cx, vxml, 0);
4311                    if (!left)
4312                        goto bad;
4313
4314                    JSString *space = cx->runtime->atomState.spaceAtom;
4315                    for (i = 1; i < n; i++) {
4316                        left = js_ConcatStrings(cx, left, space);
4317                        if (!left)
4318                            goto bad;
4319                        JSString *right = KidToString(cx, vxml, i);
4320                        if (!right)
4321                            goto bad;
4322                        left = js_ConcatStrings(cx, left, right);
4323                        if (!left)
4324                            goto bad;
4325                    }
4326
4327                    roots[VAL_ROOT] = *vp = STRING_TO_JSVAL(left);
4328                }
4329            } else {
4330                ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4331                if (!ok)
4332                    goto out;
4333                roots[VAL_ROOT] = *vp;
4334            }
4335
4336            /* 7(d-e). */
4337            match = NULL;
4338            for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
4339                attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
4340                if (!attr)
4341                    continue;
4342                attrqn = attr->name;
4343                if (EqualStrings(attrqn->getQNameLocalName(), nameqn->getQNameLocalName())) {
4344                    JSLinearString *uri = nameqn->getNameURI();
4345                    if (!uri || EqualStrings(attrqn->getNameURI(), uri)) {
4346                        if (!match) {
4347                            match = attr;
4348                        } else {
4349                            DeleteNamedProperty(cx, xml, attrqn, JS_TRUE);
4350                            --i;
4351                        }
4352                    }
4353                }
4354            }
4355
4356            /* 7(f). */
4357            attr = match;
4358            if (!attr) {
4359                /* 7(f)(i-ii). */
4360                JSLinearString *uri = nameqn->getNameURI();
4361                JSLinearString *left, *right;
4362                if (!uri) {
4363                    left = right = cx->runtime->emptyString;
4364                } else {
4365                    left = uri;
4366                    right = nameqn->getNamePrefix();
4367                }
4368                nameqn = NewXMLQName(cx, left, right, nameqn->getQNameLocalName());
4369                if (!nameqn)
4370                    goto bad;
4371
4372                /* 7(f)(iii). */
4373                attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4374                if (!attr)
4375                    goto bad;
4376                attr->parent = xml;
4377                attr->name = nameqn;
4378
4379                /* 7(f)(iv). */
4380                ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
4381                if (!ok)
4382                    goto out;
4383
4384                /* 7(f)(v-vi). */
4385                ns = GetNamespace(cx, nameqn, NULL);
4386                if (!ns)
4387                    goto bad;
4388                ok = AddInScopeNamespace(cx, xml, ns);
4389                if (!ok)
4390                    goto out;
4391            }
4392
4393            /* 7(g). */
4394            attr->xml_value = JSVAL_TO_STRING(*vp);
4395            goto out;
4396        }
4397
4398        /* 8-9. */
4399        if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
4400            !IS_STAR(nameqn->getQNameLocalName())) {
4401            goto out;
4402        }
4403
4404        /* 10-11. */
4405        id = JSID_VOID;
4406        primitiveAssign = !vxml && !IS_STAR(nameqn->getQNameLocalName());
4407
4408        /* 12. */
4409        k = n = xml->xml_kids.length;
4410        matchIndex = XML_NOT_FOUND;
4411        kid2 = NULL;
4412        while (k != 0) {
4413            --k;
4414            kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
4415            if (kid && MatchElemName(nameqn, kid)) {
4416                if (matchIndex != XML_NOT_FOUND)
4417                    DeleteByIndex(cx, xml, matchIndex);
4418                matchIndex = k;
4419                kid2 = kid;
4420            }
4421        }
4422
4423        /*
4424         * Erratum: ECMA-357 specified child insertion inconsistently:
4425         * insertChildBefore and insertChildAfter insert an arbitrary XML
4426         * instance, and therefore can create cycles, but appendChild as
4427         * specified by the "Overview" of 13.4.4.3 calls [[DeepCopy]] on
4428         * its argument.  But the "Semantics" in 13.4.4.3 do not include
4429         * any [[DeepCopy]] call.
4430         *
4431         * Fixing this (https://bugzilla.mozilla.org/show_bug.cgi?id=312692)
4432         * required adding cycle detection, and allowing duplicate kids to
4433         * be created (see comment 6 in the bug).  Allowing duplicate kid
4434         * references means the loop above will delete all but the lowest
4435         * indexed reference, and each [[DeleteByIndex]] nulls the kid's
4436         * parent.  Thus the need to restore parent here.  This is covered
4437         * by https://bugzilla.mozilla.org/show_bug.cgi?id=327564.
4438         */
4439        if (kid2) {
4440            JS_ASSERT(kid2->parent == xml || !kid2->parent);
4441            if (!kid2->parent)
4442                kid2->parent = xml;
4443        }
4444
4445        /* 13. */
4446        if (matchIndex == XML_NOT_FOUND) {
4447            /* 13(a). */
4448            matchIndex = n;
4449
4450            /* 13(b). */
4451            if (primitiveAssign) {
4452                JSLinearString *uri = nameqn->getNameURI();
4453                JSLinearString *left, *right;
4454                if (!uri) {
4455                    ns = JSVAL_TO_OBJECT(nsval);
4456                    left = ns->getNameURI();
4457                    right = ns->getNamePrefix();
4458                } else {
4459                    left = uri;
4460                    right = nameqn->getNamePrefix();
4461                }
4462                nameqn = NewXMLQName(cx, left, right, nameqn->getQNameLocalName());
4463                if (!nameqn)
4464                    goto bad;
4465
4466                /* 13(b)(iii). */
4467                vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
4468                if (!vobj)
4469                    goto bad;
4470                vxml = (JSXML *) vobj->getPrivate();
4471                vxml->parent = xml;
4472                vxml->name = nameqn;
4473
4474                /* 13(b)(iv-vi). */
4475                ns = GetNamespace(cx, nameqn, NULL);
4476                if (!ns)
4477                    goto bad;
4478                ok = Replace(cx, xml, matchIndex, OBJECT_TO_JSVAL(vobj));
4479                if (!ok)
4480                    goto out;
4481                ok = AddInScopeNamespace(cx, vxml, ns);
4482                if (!ok)
4483                    goto out;
4484            }
4485        }
4486
4487        /* 14. */
4488        if (primitiveAssign) {
4489            JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
4490            cursor.index = matchIndex;
4491            kid = cursor.getCurrent();
4492            if (JSXML_HAS_KIDS(kid)) {
4493                kid->xml_kids.finish(cx);
4494                kid->xml_kids.init();
4495                ok = kid->xml_kids.setCapacity(cx, 1);
4496            }
4497
4498            /* 14(b-c). */
4499            /* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
4500            if (ok) {
4501                ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4502                if (ok && !JSVAL_TO_STRING(*vp)->empty()) {
4503                    roots[VAL_ROOT] = *vp;
4504                    if (cursor.getCurrent() == kid)
4505                        ok = Replace(cx, kid, 0, *vp);
4506                }
4507            }
4508        } else {
4509            /* 15(a). */
4510            ok = Replace(cx, xml, matchIndex, *vp);
4511        }
4512    }
4513
4514out:
4515    js_LeaveLocalRootScope(cx);
4516    return ok;
4517
4518type_error:
4519    {
4520        JSAutoByteString bytes;
4521        if (js_ValueToPrintable(cx, IdToValue(id), &bytes))
4522            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XMLLIST_PUT, bytes.ptr());
4523    }
4524bad:
4525    ok = JS_FALSE;
4526    goto out;
4527}
4528
4529/* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
4530static JSBool
4531ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
4532{
4533    JSXML *target, *base;
4534    JSObject *targetprop;
4535    jsid id;
4536    jsval tv;
4537
4538    if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
4539        if (!js_GetXMLObject(cx, list))
4540            return JS_FALSE;
4541        *result = list;
4542        return JS_TRUE;
4543    }
4544
4545    target = list->xml_target;
4546    targetprop = list->xml_targetprop;
4547    if (!target || !targetprop || IS_STAR(targetprop->getQNameLocalName())) {
4548        *result = NULL;
4549        return JS_TRUE;
4550    }
4551
4552    if (targetprop->getClass() == &AttributeNameClass) {
4553        *result = NULL;
4554        return JS_TRUE;
4555    }
4556
4557    if (!ResolveValue(cx, target, &base))
4558        return JS_FALSE;
4559    if (!base) {
4560        *result = NULL;
4561        return JS_TRUE;
4562    }
4563    if (!js_GetXMLObject(cx, base))
4564        return JS_FALSE;
4565
4566    id = OBJECT_TO_JSID(targetprop);
4567    if (!GetProperty(cx, base->object, id, &tv))
4568        return JS_FALSE;
4569    target = (JSXML *) JSVAL_TO_OBJECT(tv)->getPrivate();
4570
4571    if (JSXML_LENGTH(target) == 0) {
4572        if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
4573            *result = NULL;
4574            return JS_TRUE;
4575        }
4576        tv = STRING_TO_JSVAL(cx->runtime->emptyString);
4577        if (!PutProperty(cx, base->object, id, false, &tv))
4578            return JS_FALSE;
4579        if (!GetProperty(cx, base->object, id, &tv))
4580            return JS_FALSE;
4581        target = (JSXML *) JSVAL_TO_OBJECT(tv)->getPrivate();
4582    }
4583
4584    *result = target;
4585    return JS_TRUE;
4586}
4587
4588static JSBool
4589HasNamedProperty(JSXML *xml, JSObject *nameqn)
4590{
4591    JSBool found;
4592    JSXMLArray<JSXML> *array;
4593    JSXMLNameMatcher matcher;
4594    uint32_t i, n;
4595
4596    if (xml->xml_class == JSXML_CLASS_LIST) {
4597        found = JS_FALSE;
4598        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
4599        while (JSXML *kid = cursor.getNext()) {
4600            found = HasNamedProperty(kid, nameqn);
4601            if (found)
4602                break;
4603        }
4604        return found;
4605    }
4606
4607    if (xml->xml_class == JSXML_CLASS_ELEMENT) {
4608        if (nameqn->getClass() == &AttributeNameClass) {
4609            array = &xml->xml_attrs;
4610            matcher = MatchAttrName;
4611        } else {
4612            array = &xml->xml_kids;
4613            matcher = MatchElemName;
4614        }
4615        for (i = 0, n = array->length; i < n; i++) {
4616            JSXML *kid = XMLARRAY_MEMBER(array, i, JSXML);
4617            if (kid && matcher(nameqn, kid))
4618                return JS_TRUE;
4619        }
4620    }
4621
4622    return JS_FALSE;
4623}
4624
4625static JSBool
4626HasIndexedProperty(JSXML *xml, uint32_t i)
4627{
4628    if (xml->xml_class == JSXML_CLASS_LIST)
4629        return i < JSXML_LENGTH(xml);
4630
4631    if (xml->xml_class == JSXML_CLASS_ELEMENT)
4632        return i == 0;
4633
4634    return JS_FALSE;
4635}
4636
4637static JSBool
4638HasSimpleContent(JSXML *xml);
4639
4640static JSBool
4641HasFunctionProperty(JSContext *cx, JSObject *obj, jsid funid, JSBool *found)
4642{
4643    JSObject *pobj;
4644    JSProperty *prop;
4645    JSXML *xml;
4646
4647    JS_ASSERT(obj->getClass() == &XMLClass);
4648
4649    if (!js_LookupProperty(cx, obj, funid, &pobj, &prop))
4650        return false;
4651    if (!prop) {
4652        xml = (JSXML *) obj->getPrivate();
4653        if (HasSimpleContent(xml)) {
4654            /*
4655             * Search in String.prototype to set found whenever
4656             * GetXMLFunction returns existing function.
4657             */
4658            JSObject *proto = obj->global().getOrCreateStringPrototype(cx);
4659            if (!proto)
4660                return false;
4661
4662            if (!js_LookupProperty(cx, proto, funid, &pobj, &prop))
4663                return false;
4664        }
4665    }
4666    *found = (prop != NULL);
4667    return true;
4668}
4669
4670static bool
4671IdValIsIndex(JSContext *cx, jsval id, jsuint *indexp, bool *isIndex)
4672{
4673    if (JSVAL_IS_INT(id)) {
4674        jsint i;
4675        i = JSVAL_TO_INT(id);
4676        if (i < 0) {
4677            *isIndex = false;
4678            return true;
4679        }
4680        *indexp = (jsuint)i;
4681        *isIndex = true;
4682        return true;
4683    }
4684
4685    if (!JSVAL_IS_STRING(id)) {
4686        *isIndex = false;
4687        return true;
4688    }
4689
4690    JSLinearString *str = JSVAL_TO_STRING(id)->ensureLinear(cx);
4691    if (!str)
4692        return false;
4693
4694    *isIndex = StringIsArrayIndex(str, indexp);
4695    return true;
4696}
4697
4698/* ECMA-357 9.1.1.6 XML [[HasProperty]] and 9.2.1.5 XMLList [[HasProperty]]. */
4699static JSBool
4700HasProperty(JSContext *cx, JSObject *obj, jsval id, JSBool *found)
4701{
4702    JSXML *xml;
4703    bool isIndex;
4704    uint32_t i;
4705    JSObject *qn;
4706    jsid funid;
4707
4708    xml = (JSXML *) obj->getPrivate();
4709    if (!IdValIsIndex(cx, id, &i, &isIndex))
4710        return JS_FALSE;
4711
4712    if (isIndex) {
4713        *found = HasIndexedProperty(xml, i);
4714    } else {
4715        qn = ToXMLName(cx, id, &funid);
4716        if (!qn)
4717            return JS_FALSE;
4718        if (!JSID_IS_VOID(funid)) {
4719            if (!HasFunctionProperty(cx, obj, funid, found))
4720                return JS_FALSE;
4721        } else {
4722            *found = HasNamedProperty(xml, qn);
4723        }
4724    }
4725    return JS_TRUE;
4726}
4727
4728static void
4729xml_finalize(JSContext *cx, JSObject *obj)
4730{
4731}
4732
4733/*
4734 * XML objects are native. Thus xml_lookupGeneric must return a valid
4735 * Shape pointer parameter via *propp to signify "property found". Since the
4736 * only call to xml_lookupGeneric is via JSObject::lookupGeneric, and then
4737 * only from js_FindProperty (in jsobj.c, called from jsinterp.c) or from
4738 * JSOP_IN case in the interpreter, the only time we add a Shape here is when
4739 * an unqualified name is being accessed or when "name in xml" is called.
4740 *
4741 * This scope property keeps the JSOP_NAME code in js_Interpret happy by
4742 * giving it an shape with (getter, setter) == (GetProperty, PutProperty).
4743 *
4744 * NB: xml_deleteProperty must take care to remove any property added here.
4745 *
4746 * FIXME This clashes with the function namespace implementation which also
4747 * uses native properties. Effectively after xml_lookupGeneric any property
4748 * stored previously using assignments to xml.function::name will be removed.
4749 * We partially workaround the problem in GetXMLFunction. There we take
4750 * advantage of the fact that typically function:: is used to access the
4751 * functions from XML.prototype. So when js_GetProperty returns a non-function
4752 * property, we assume that it represents the result of GetProperty setter
4753 * hiding the function and use an extra prototype chain lookup to recover it.
4754 * For a proper solution see bug 355257.
4755*/
4756static JSBool
4757xml_lookupGeneric(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, JSProperty **propp)
4758{
4759    JSBool found;
4760    JSXML *xml;
4761    uint32_t i;
4762    JSObject *qn;
4763    jsid funid;
4764
4765    xml = (JSXML *) obj->getPrivate();
4766    if (js_IdIsIndex(id, &i)) {
4767        found = HasIndexedProperty(xml, i);
4768    } else {
4769        qn = ToXMLName(cx, IdToJsval(id), &funid);
4770        if (!qn)
4771            return JS_FALSE;
4772        if (!JSID_IS_VOID(funid))
4773            return js_LookupProperty(cx, obj, funid, objp, propp);
4774        found = HasNamedProperty(xml, qn);
4775    }
4776    if (!found) {
4777        *objp = NULL;
4778        *propp = NULL;
4779    } else {
4780        const Shape *shape =
4781            js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
4782                                 SHAPE_INVALID_SLOT, JSPROP_ENUMERATE,
4783                                 0, 0);
4784        if (!shape)
4785            return JS_FALSE;
4786
4787        *objp = obj;
4788        *propp = (JSProperty *) shape;
4789    }
4790    return JS_TRUE;
4791}
4792
4793static JSBool
4794xml_lookupProperty(JSContext *cx, JSObject *obj, PropertyName *name, JSObject **objp,
4795                   JSProperty **propp)
4796{
4797    return xml_lookupGeneric(cx, obj, ATOM_TO_JSID(name), objp, propp);
4798}
4799
4800static JSBool
4801xml_lookupElement(JSContext *cx, JSObject *obj, uint32_t index, JSObject **objp,
4802                  JSProperty **propp)
4803{
4804    JSXML *xml = reinterpret_cast<JSXML *>(obj->getPrivate());
4805    if (!HasIndexedProperty(xml, index)) {
4806        *objp = NULL;
4807        *propp = NULL;
4808        return true;
4809    }
4810
4811    jsid id;
4812    if (!IndexToId(cx, index, &id))
4813        return false;
4814
4815    const Shape *shape =
4816        js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
4817                             SHAPE_INVALID_SLOT, JSPROP_ENUMERATE,
4818                             0, 0);
4819    if (!shape)
4820        return false;
4821
4822    *objp = obj;
4823    *propp = (JSProperty *) shape;
4824    return true;
4825}
4826
4827static JSBool
4828xml_lookupSpecial(JSContext *cx, JSObject *obj, SpecialId sid, JSObject **objp, JSProperty **propp)
4829{
4830    return xml_lookupGeneric(cx, obj, SPECIALID_TO_JSID(sid), objp, propp);
4831}
4832
4833static JSBool
4834xml_defineGeneric(JSContext *cx, JSObject *obj, jsid id, const Value *v,
4835                  PropertyOp getter, StrictPropertyOp setter, uintN attrs)
4836{
4837    if (IsFunctionObject(*v) || getter || setter ||
4838        (attrs & JSPROP_ENUMERATE) == 0 ||
4839        (attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
4840        return js_DefineProperty(cx, obj, id, v, getter, setter, attrs);
4841    }
4842
4843    jsval tmp = *v;
4844    return PutProperty(cx, obj, id, false, &tmp);
4845}
4846
4847static JSBool
4848xml_defineProperty(JSContext *cx, JSObject *obj, PropertyName *name, const Value *v,
4849                   PropertyOp getter, StrictPropertyOp setter, uintN attrs)
4850{
4851    return xml_defineGeneric(cx, obj, ATOM_TO_JSID(name), v, getter, setter, attrs);
4852}
4853
4854static JSBool
4855xml_defineElement(JSContext *cx, JSObject *obj, uint32_t index, const Value *v,
4856                  PropertyOp getter, StrictPropertyOp setter, uintN attrs)
4857{
4858    jsid id;
4859    if (!IndexToId(cx, index, &id))
4860        return false;
4861    return xml_defineGeneric(cx, obj, id, v, getter, setter, attrs);
4862}
4863
4864static JSBool
4865xml_defineSpecial(JSContext *cx, JSObject *obj, SpecialId sid, const Value *v,
4866                  PropertyOp getter, StrictPropertyOp setter, uintN attrs)
4867{
4868    return xml_defineGeneric(cx, obj, SPECIALID_TO_JSID(sid), v, getter, setter, attrs);
4869}
4870
4871static JSBool
4872xml_getGeneric(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Value *vp)
4873{
4874    if (JSID_IS_DEFAULT_XML_NAMESPACE(id)) {
4875        vp->setUndefined();
4876        return JS_TRUE;
4877    }
4878
4879    return GetProperty(cx, obj, id, vp);
4880}
4881
4882static JSBool
4883xml_getProperty(JSContext *cx, JSObject *obj, JSObject *receiver, PropertyName *name, Value *vp)
4884{
4885    return xml_getGeneric(cx, obj, receiver, ATOM_TO_JSID(name), vp);
4886}
4887
4888static JSBool
4889xml_getElement(JSContext *cx, JSObject *obj, JSObject *receiver, uint32_t index, Value *vp)
4890{
4891    jsid id;
4892    if (!IndexToId(cx, index, &id))
4893        return false;
4894    return xml_getGeneric(cx, obj, receiver, id, vp);
4895}
4896
4897static JSBool
4898xml_getSpecial(JSContext *cx, JSObject *obj, JSObject *receiver, SpecialId sid, Value *vp)
4899{
4900    return xml_getGeneric(cx, obj, receiver, SPECIALID_TO_JSID(sid), vp);
4901}
4902
4903static JSBool
4904xml_setGeneric(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool strict)
4905{
4906    return PutProperty(cx, obj, id, strict, vp);
4907}
4908
4909static JSBool
4910xml_setProperty(JSContext *cx, JSObject *obj, PropertyName *name, Value *vp, JSBool strict)
4911{
4912    return xml_setGeneric(cx, obj, ATOM_TO_JSID(name), vp, strict);
4913}
4914
4915static JSBool
4916xml_setElement(JSContext *cx, JSObject *obj, uint32_t index, Value *vp, JSBool strict)
4917{
4918    jsid id;
4919    if (!IndexToId(cx, index, &id))
4920        return false;
4921    return xml_setGeneric(cx, obj, id, vp, strict);
4922}
4923
4924static JSBool
4925xml_setSpecial(JSContext *cx, JSObject *obj, SpecialId sid, Value *vp, JSBool strict)
4926{
4927    return xml_setGeneric(cx, obj, SPECIALID_TO_JSID(sid), vp, strict);
4928}
4929
4930static JSBool
4931xml_getGenericAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
4932{
4933    JSBool found;
4934    if (!HasProperty(cx, obj, IdToJsval(id), &found))
4935        return false;
4936
4937    *attrsp = found ? JSPROP_ENUMERATE : 0;
4938    return JS_TRUE;
4939}
4940
4941static JSBool
4942xml_getPropertyAttributes(JSContext *cx, JSObject *obj, PropertyName *name, uintN *attrsp)
4943{
4944    return xml_getGenericAttributes(cx, obj, ATOM_TO_JSID(name), attrsp);
4945}
4946
4947static JSBool
4948xml_getElementAttributes(JSContext *cx, JSObject *obj, uint32_t index, uintN *attrsp)
4949{
4950    jsid id;
4951    if (!IndexToId(cx, index, &id))
4952        return false;
4953    return xml_getGenericAttributes(cx, obj, id, attrsp);
4954}
4955
4956static JSBool
4957xml_getSpecialAttributes(JSContext *cx, JSObject *obj, SpecialId sid, uintN *attrsp)
4958{
4959    return xml_getGenericAttributes(cx, obj, SPECIALID_TO_JSID(sid), attrsp);
4960}
4961
4962static JSBool
4963xml_setGenericAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
4964{
4965    JSBool found;
4966    if (!HasProperty(cx, obj, IdToJsval(id), &found))
4967        return false;
4968
4969    if (found) {
4970        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4971                             JSMSG_CANT_SET_XML_ATTRS);
4972        return false;
4973    }
4974    return true;
4975}
4976
4977static JSBool
4978xml_setPropertyAttributes(JSContext *cx, JSObject *obj, PropertyName *name, uintN *attrsp)
4979{
4980    return xml_setGenericAttributes(cx, obj, ATOM_TO_JSID(name), attrsp);
4981}
4982
4983static JSBool
4984xml_setElementAttributes(JSContext *cx, JSObject *obj, uint32_t index, uintN *attrsp)
4985{
4986    jsid id;
4987    if (!IndexToId(cx, index, &id))
4988        return false;
4989    return xml_setGenericAttributes(cx, obj, id, attrsp);
4990}
4991
4992static JSBool
4993xml_setSpecialAttributes(JSContext *cx, JSObject *obj, SpecialId sid, uintN *attrsp)
4994{
4995    return xml_setGenericAttributes(cx, obj, SPECIALID_TO_JSID(sid), attrsp);
4996}
4997
4998static JSBool
4999xml_deleteGeneric(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool strict)
5000{
5001    uint32_t index;
5002    JSObject *nameqn;
5003    jsid funid;
5004
5005    Value idval = IdToValue(id);
5006    JSXML *xml = (JSXML *) obj->getPrivate();
5007    if (js_IdIsIndex(id, &index)) {
5008        if (xml->xml_class != JSXML_CLASS_LIST) {
5009            /* See NOTE in spec: this variation is reserved for future use. */
5010            ReportBadXMLName(cx, IdToValue(id));
5011            return false;
5012        }
5013
5014        /* ECMA-357 9.2.1.3. */
5015        DeleteListElement(cx, xml, index);
5016    } else {
5017        nameqn = ToXMLName(cx, idval, &funid);
5018        if (!nameqn)
5019            return false;
5020        if (!JSID_IS_VOID(funid))
5021            return js_DeleteGeneric(cx, obj, funid, rval, false);
5022
5023        DeleteNamedProperty(cx, xml, nameqn,
5024                            nameqn->getClass() == &AttributeNameClass);
5025    }
5026
5027    /*
5028     * If this object has its own (mutable) scope,  then we may have added a
5029     * property to the scope in xml_lookupGeneric for it to return to mean
5030     * "found" and to provide a handle for access operations to call the
5031     * property's getter or setter. But now it's time to remove any such
5032     * property, to purge the property cache and remove the scope entry.
5033     */
5034    if (!obj->nativeEmpty() && !js_DeleteGeneric(cx, obj, id, rval, false))
5035        return false;
5036
5037    rval->setBoolean(true);
5038    return true;
5039}
5040
5041static JSBool
5042xml_deleteProperty(JSContext *cx, JSObject *obj, PropertyName *name, Value *rval, JSBool strict)
5043{
5044    return xml_deleteGeneric(cx, obj, ATOM_TO_JSID(name), rval, strict);
5045}
5046
5047static JSBool
5048xml_deleteElement(JSContext *cx, JSObject *obj, uint32_t index, Value *rval, JSBool strict)
5049{
5050    JSXML *xml = reinterpret_cast<JSXML *>(obj->getPrivate());
5051    if (xml->xml_class != JSXML_CLASS_LIST) {
5052        /* See NOTE in spec: this variation is reserved for future use. */
5053        ReportBadXMLName(cx, DoubleValue(index));
5054        return false;
5055    }
5056
5057    /* ECMA-357 9.2.1.3. */
5058    DeleteListElement(cx, xml, index);
5059
5060    /*
5061     * If this object has its own (mutable) scope,  then we may have added a
5062     * property to the scope in xml_lookupGeneric for it to return to mean
5063     * "found" and to provide a handle for access operations to call the
5064     * property's getter or setter. But now it's time to remove any such
5065     * property, to purge the property cache and remove the scope entry.
5066     */
5067    if (!obj->nativeEmpty() && !js_DeleteElement(cx, obj, index, rval, false))
5068        return false;
5069
5070    rval->setBoolean(true);
5071    return true;
5072}
5073
5074static JSBool
5075xml_deleteSpecial(JSContext *cx, JSObject *obj, SpecialId sid, Value *rval, JSBool strict)
5076{
5077    return xml_deleteGeneric(cx, obj, SPECIALID_TO_JSID(sid), rval, strict);
5078}
5079
5080static JSString *
5081xml_toString_helper(JSContext *cx, JSXML *xml);
5082
5083JSBool
5084xml_convert(JSContext *cx, JSObject *obj, JSType hint, Value *rval)
5085{
5086    JS_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
5087    JS_ASSERT(obj->isXML());
5088
5089    JS::Anchor<JSObject *> anch(obj);
5090    JSString *str = xml_toString_helper(cx, reinterpret_cast<JSXML *>(obj->getPrivate()));
5091    if (!str)
5092        return false;
5093    *rval = StringValue(str);
5094    return true;
5095}
5096
5097static JSBool
5098xml_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, Value *statep, jsid *idp)
5099{
5100    JSXML *xml;
5101    uint32_t length, index;
5102    JSXMLArrayCursor<JSXML> *cursor;
5103
5104    xml = (JSXML *)obj->getPrivate();
5105    length = JSXML_LENGTH(xml);
5106
5107    switch (enum_op) {
5108      case JSENUMERATE_INIT:
5109      case JSENUMERATE_INIT_ALL:
5110        if (length == 0) {
5111            statep->setInt32(0);
5112        } else {
5113            cursor = cx->new_< JSXMLArrayCursor<JSXML> >(&xml->xml_kids);
5114            if (!cursor)
5115                return JS_FALSE;
5116            statep->setPrivate(cursor);
5117        }
5118        if (idp)
5119            *idp = INT_TO_JSID(length);
5120        break;
5121
5122      case JSENUMERATE_NEXT:
5123        if (statep->isInt32(0)) {
5124            statep->setNull();
5125            break;
5126        }
5127        cursor = (JSXMLArrayCursor<JSXML> *) statep->toPrivate();
5128        if (cursor && cursor->array && (index = cursor->index) < length) {
5129            *idp = INT_TO_JSID(index);
5130            cursor->index = index + 1;
5131            break;
5132        }
5133        /* FALL THROUGH */
5134
5135      case JSENUMERATE_DESTROY:
5136        if (!statep->isInt32(0)) {
5137            cursor = (JSXMLArrayCursor<JSXML> *) statep->toPrivate();
5138            if (cursor)
5139                cx->delete_(cursor);
5140        }
5141        statep->setNull();
5142        break;
5143    }
5144    return JS_TRUE;
5145}
5146
5147static JSType
5148xml_typeOf(JSContext *cx, JSObject *obj)
5149{
5150    return JSTYPE_XML;
5151}
5152
5153static JSBool
5154xml_hasInstance(JSContext *cx, JSObject *obj, const Value *, JSBool *bp)
5155{
5156    return JS_TRUE;
5157}
5158
5159static void
5160xml_trace(JSTracer *trc, JSObject *obj)
5161{
5162    JSXML *xml = (JSXML *) obj->getPrivate();
5163    /*
5164     * This is safe to leave Unbarriered for incremental GC, but we'll need
5165     * to fix somehow for generational.
5166     */
5167    if (xml)
5168        MarkXMLUnbarriered(trc, xml, "private");
5169}
5170
5171static JSBool
5172xml_fix(JSContext *cx, JSObject *obj, bool *success, AutoIdVector *props)
5173{
5174    JS_ASSERT(obj->isExtensible());
5175    *success = false;
5176    return true;
5177}
5178
5179static void
5180xml_clear(JSContext *cx, JSObject *obj)
5181{
5182}
5183
5184static JSBool
5185HasSimpleContent(JSXML *xml)
5186{
5187    JSXML *kid;
5188    JSBool simple;
5189    uint32_t i, n;
5190
5191again:
5192    switch (xml->xml_class) {
5193      case JSXML_CLASS_COMMENT:
5194      case JSXML_CLASS_PROCESSING_INSTRUCTION:
5195        return JS_FALSE;
5196      case JSXML_CLASS_LIST:
5197        if (xml->xml_kids.length == 0)
5198            return JS_TRUE;
5199        if (xml->xml_kids.length == 1) {
5200            kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5201            if (kid) {
5202                xml = kid;
5203                goto again;
5204            }
5205        }
5206        /* FALL THROUGH */
5207      default:
5208        simple = JS_TRUE;
5209        for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5210            kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5211            if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5212                simple = JS_FALSE;
5213                break;
5214            }
5215        }
5216        return simple;
5217    }
5218}
5219
5220/*
5221 * 11.2.2.1 Step 3(d) onward.
5222 */
5223JSBool
5224js_GetXMLMethod(JSContext *cx, JSObject *obj, jsid id, Value *vp)
5225{
5226    JS_ASSERT(obj->isXML());
5227
5228    if (JSID_IS_OBJECT(id))
5229        js_GetLocalNameFromFunctionQName(JSID_TO_OBJECT(id), &id, cx);
5230
5231    /*
5232     * As our callers have a bad habit of passing a pointer to an unrooted
5233     * local value as vp, we use a proper root here.
5234     */
5235    AutoValueRooter tvr(cx);
5236    JSBool ok = GetXMLFunction(cx, obj, id, tvr.addr());
5237    *vp = tvr.value();
5238    return ok;
5239}
5240
5241JSBool
5242js_TestXMLEquality(JSContext *cx, const Value &v1, const Value &v2, JSBool *bp)
5243{
5244    JSXML *xml, *vxml;
5245    JSObject *vobj;
5246    JSBool ok;
5247    JSString *str, *vstr;
5248    jsdouble d, d2;
5249
5250    JSObject *obj;
5251    jsval v;
5252    if (v1.isObject() && v1.toObject().isXML()) {
5253        obj = &v1.toObject();
5254        v = v2;
5255    } else {
5256        v = v1;
5257        obj = &v2.toObject();
5258    }
5259
5260    JS_ASSERT(obj->isXML());
5261
5262    xml = (JSXML *) obj->getPrivate();
5263    vxml = NULL;
5264    if (!JSVAL_IS_PRIMITIVE(v)) {
5265        vobj = JSVAL_TO_OBJECT(v);
5266        if (vobj->isXML())
5267            vxml = (JSXML *) vobj->getPrivate();
5268    }
5269
5270    if (xml->xml_class == JSXML_CLASS_LIST) {
5271        ok = Equals(cx, xml, v, bp);
5272    } else if (vxml) {
5273        if (vxml->xml_class == JSXML_CLASS_LIST) {
5274            ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
5275        } else {
5276            if (((xml->xml_class == JSXML_CLASS_TEXT ||
5277                  xml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5278                 HasSimpleContent(vxml)) ||
5279                ((vxml->xml_class == JSXML_CLASS_TEXT ||
5280                  vxml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5281                 HasSimpleContent(xml))) {
5282                ok = js_EnterLocalRootScope(cx);
5283                if (ok) {
5284                    ok = (str = ToStringSlow(cx, ObjectValue(*obj))) &&
5285                         (vstr = ToString(cx, v));
5286                    if (ok) {
5287                        bool equal;
5288                        ok = EqualStrings(cx, str, vstr, &equal);
5289                        *bp = equal;
5290                    }
5291                    js_LeaveLocalRootScope(cx);
5292                }
5293            } else {
5294                ok = XMLEquals(cx, xml, vxml, bp);
5295            }
5296        }
5297    } else {
5298        ok = js_EnterLocalRootScope(cx);
5299        if (ok) {
5300            if (HasSimpleContent(xml)) {
5301                ok = (str = ToString(cx, ObjectValue(*obj))) &&
5302                     (vstr = ToString(cx, v));
5303                if (ok) {
5304                    bool equal;
5305                    ok = EqualStrings(cx, str, vstr, &equal);
5306                    *bp = equal;
5307                }
5308            } else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
5309                str = ToString(cx, ObjectValue(*obj));
5310                if (!str) {
5311                    ok = JS_FALSE;
5312                } else if (JSVAL_IS_STRING(v)) {
5313                    bool equal;
5314                    ok = EqualStrings(cx, str, JSVAL_TO_STRING(v), &equal);
5315                    if (ok)
5316                        *bp = equal;
5317                } else {
5318                    ok = JS_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
5319                    if (ok) {
5320                        d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
5321                                             : JSVAL_TO_DOUBLE(v);
5322                        *bp = (d == d2);
5323                    }
5324                }
5325            } else {
5326                *bp = JS_FALSE;
5327            }
5328            js_LeaveLocalRootScope(cx);
5329        }
5330    }
5331    return ok;
5332}
5333
5334JSBool
5335js_ConcatenateXML(JSContext *cx, JSObject *obj, JSObject *robj, Value *vp)
5336{
5337    JSBool ok;
5338    JSObject *listobj;
5339    JSXML *list, *lxml, *rxml;
5340
5341    JS_ASSERT(obj->isXML());
5342    ok = js_EnterLocalRootScope(cx);
5343    if (!ok)
5344        return JS_FALSE;
5345
5346    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5347    if (!listobj) {
5348        ok = JS_FALSE;
5349        goto out;
5350    }
5351
5352    list = (JSXML *) listobj->getPrivate();
5353    lxml = (JSXML *) obj->getPrivate();
5354    ok = Append(cx, list, lxml);
5355    if (!ok)
5356        goto out;
5357
5358    JS_ASSERT(robj->isXML());
5359    rxml = (JSXML *) robj->getPrivate();
5360    ok = Append(cx, list, rxml);
5361    if (!ok)
5362        goto out;
5363
5364    vp->setObject(*listobj);
5365out:
5366    js_LeaveLocalRootScopeWithResult(cx, *vp);
5367    return ok;
5368}
5369
5370JS_FRIEND_DATA(Class) js::XMLClass = {
5371    js_XML_str,
5372    JSCLASS_HAS_PRIVATE |
5373    JSCLASS_HAS_CACHED_PROTO(JSProto_XML),
5374    JS_PropertyStub,         /* addProperty */
5375    JS_PropertyStub,         /* delProperty */
5376    JS_PropertyStub,         /* getProperty */
5377    JS_StrictPropertyStub,   /* setProperty */
5378    JS_EnumerateStub,
5379    JS_ResolveStub,
5380    xml_convert,
5381    xml_finalize,
5382    NULL,                 /* reserved0   */
5383    NULL,                 /* checkAccess */
5384    NULL,                 /* call        */
5385    NULL,                 /* construct   */
5386    NULL,                 /* xdrObject   */
5387    xml_hasInstance,
5388    xml_trace,
5389    JS_NULL_CLASS_EXT,
5390    {
5391        xml_lookupGeneric,
5392        xml_lookupProperty,
5393        xml_lookupElement,
5394        xml_lookupSpecial,
5395        xml_defineGeneric,
5396        xml_defineProperty,
5397        xml_defineElement,
5398        xml_defineSpecial,
5399        xml_getGeneric,
5400        xml_getProperty,
5401        xml_getElement,
5402        NULL, /* getElementIfPresent */
5403        xml_getSpecial,
5404        xml_setGeneric,
5405        xml_setProperty,
5406        xml_setElement,
5407        xml_setSpecial,
5408        xml_getGenericAttributes,
5409        xml_getPropertyAttributes,
5410        xml_getElementAttributes,
5411        xml_getSpecialAttributes,
5412        xml_setGenericAttributes,
5413        xml_setPropertyAttributes,
5414        xml_setElementAttributes,
5415        xml_setSpecialAttributes,
5416        xml_deleteProperty,
5417        xml_deleteElement,
5418        xml_deleteSpecial,
5419        xml_enumerate,
5420        xml_typeOf,
5421        xml_fix,
5422        NULL,       /* thisObject     */
5423        xml_clear
5424    }
5425};
5426
5427static JSXML *
5428StartNonListXMLMethod(JSContext *cx, jsval *vp, JSObject **objp)
5429{
5430    JSXML *xml;
5431    JSFunction *fun;
5432    char numBuf[12];
5433
5434    JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
5435    JS_ASSERT(JSVAL_TO_OBJECT(*vp)->isFunction());
5436
5437    *objp = ToObject(cx, &vp[1]);
5438    if (!*objp)
5439        return NULL;
5440    if (!(*objp)->isXML()) {
5441        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &XMLClass);
5442        return NULL;
5443    }
5444    xml = (JSXML *) (*objp)->getPrivate();
5445    if (!xml || xml->xml_class != JSXML_CLASS_LIST)
5446        return xml;
5447
5448    if (xml->xml_kids.length == 1) {
5449        xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5450        if (xml) {
5451            *objp = js_GetXMLObject(cx, xml);
5452            if (!*objp)
5453                return NULL;
5454            vp[1] = OBJECT_TO_JSVAL(*objp);
5455            return xml;
5456        }
5457    }
5458
5459    fun = JSVAL_TO_OBJECT(*vp)->toFunction();
5460    JS_snprintf(numBuf, sizeof numBuf, "%u", xml->xml_kids.length);
5461    JSAutoByteString funNameBytes;
5462    if (const char *funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
5463        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NON_LIST_XML_METHOD,
5464                             funName, numBuf);
5465    }
5466    return NULL;
5467}
5468
5469/* Beware: these two are not bracketed by JS_BEGIN/END_MACRO. */
5470#define XML_METHOD_PROLOG                                                     \
5471    JSObject *obj = ToObject(cx, &vp[1]);                                     \
5472    if (!obj)                                                                 \
5473        return JS_FALSE;                                                      \
5474    if (!obj->isXML()) {                                                      \
5475        ReportIncompatibleMethod(cx, CallReceiverFromVp(vp), &XMLClass);      \
5476        return JS_FALSE;                                                      \
5477    }                                                                         \
5478    JSXML *xml = (JSXML *)obj->getPrivate();                                  \
5479    if (!xml)                                                                 \
5480        return JS_FALSE
5481
5482#define NON_LIST_XML_METHOD_PROLOG                                            \
5483    JSObject *obj;                                                            \
5484    JSXML *xml = StartNonListXMLMethod(cx, vp, &obj);                         \
5485    if (!xml)                                                                 \
5486        return JS_FALSE;                                                      \
5487    JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST)
5488
5489static JSBool
5490xml_addNamespace(JSContext *cx, uintN argc, jsval *vp)
5491{
5492    JSObject *ns;
5493
5494    NON_LIST_XML_METHOD_PROLOG;
5495    if (xml->xml_class != JSXML_CLASS_ELEMENT)
5496        goto done;
5497    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5498    if (!xml)
5499        return JS_FALSE;
5500
5501    if (!NamespaceHelper(cx, argc == 0 ? -1 : 1, vp + 2, vp))
5502        return JS_FALSE;
5503    JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
5504
5505    ns = JSVAL_TO_OBJECT(*vp);
5506    if (!AddInScopeNamespace(cx, xml, ns))
5507        return JS_FALSE;
5508    ns->setNamespaceDeclared(JSVAL_TRUE);
5509
5510  done:
5511    *vp = OBJECT_TO_JSVAL(obj);
5512    return JS_TRUE;
5513}
5514
5515static JSBool
5516xml_appendChild(JSContext *cx, uintN argc, jsval *vp)
5517{
5518    jsval v;
5519    JSObject *vobj;
5520    JSXML *vxml;
5521
5522    NON_LIST_XML_METHOD_PROLOG;
5523    xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5524    if (!xml)
5525        return JS_FALSE;
5526
5527    jsid name;
5528    if (!js_GetAnyName(cx, &name))
5529        return JS_FALSE;
5530
5531    if (!GetProperty(cx, obj, name, &v))
5532        return JS_FALSE;
5533
5534    JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5535    vobj = JSVAL_TO_OBJECT(v);
5536    JS_ASSERT(vobj->isXML());
5537    vxml = (JSXML *) vobj->getPrivate();
5538    JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);
5539
5540    if (!IndexToId(cx, vxml->xml_kids.length, &name))
5541        return JS_FALSE;
5542    *vp = (argc != 0) ? vp[2] : JSVAL_VOID;
5543
5544    if (!PutProperty(cx, JSVAL_TO_OBJECT(v), name, false, vp))
5545        return JS_FALSE;
5546
5547    *vp = OBJECT_TO_JSVAL(obj);
5548    return JS_TRUE;
5549}
5550
5551/* XML and XMLList */
5552static JSBool
5553xml_attribute(JSContext *cx, uintN argc, jsval *vp)
5554{
5555    JSObject *qn;
5556
5557    if (argc == 0) {
5558        js_ReportMissingArg(cx, *vp, 0);
5559        return JS_FALSE;
5560    }
5561
5562    qn = ToAttributeName(cx, vp[2]);
5563    if (!qn)
5564        return JS_FALSE;
5565    vp[2] = OBJECT_TO_JSVAL(qn);        /* local root */
5566
5567    jsid id = OBJECT_TO_JSID(qn);
5568    JSObject *obj = ToObject(cx, &vp[1]);
5569    if (!obj)
5570        return JS_FALSE;
5571    return GetProperty(cx, obj, id, vp);
5572}
5573
5574/* XML and XMLList */
5575static JSBool
5576xml_attributes(JSContext *cx, uintN argc, jsval *vp)
5577{
5578    jsval name = STRING_TO_JSVAL(cx->runtime->atomState.starAtom);
5579    JSObject *qn = ToAttributeName(cx, name);
5580    if (!qn)
5581        return JS_FALSE;
5582
5583    jsid id = OBJECT_TO_JSID(qn);
5584    JSObject *obj = ToObject(cx, &vp[1]);
5585    if (!obj)
5586        return JS_FALSE;
5587    return GetProperty(cx, obj, id, vp);
5588}
5589
5590static JSXML *
5591xml_list_helper(JSContext *cx, JSXML *xml, jsval *rval)
5592{
5593    JSObject *listobj;
5594    JSXML *list;
5595
5596    listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5597    if (!listobj)
5598        return NULL;
5599
5600    *rval = OBJECT_TO_JSVAL(listobj);
5601    list = (JSXML *) listobj->getPrivate();
5602    list->xml_target = xml;
5603    return list;
5604}
5605
5606static JSBool
5607ValueToId(JSContext *cx, jsval v, AutoIdRooter *idr)
5608{
5609    if (JSVAL_IS_INT(v)) {
5610        jsint i = JSVAL_TO_INT(v);
5611        if (INT_FITS_IN_JSID(i))
5612            *idr->addr() = INT_TO_JSID(i);
5613        else if (!js_ValueToStringId(cx, v, idr->addr()))
5614            return JS_FALSE;
5615    } else if (JSVAL_IS_STRING(v)) {
5616        JSAtom *atom = js_AtomizeString(cx, JSVAL_TO_STRING(v));
5617        if (!atom)
5618            return JS_FALSE;
5619        *idr->addr() = ATOM_TO_JSID(atom);
5620    } else if (!JSVAL_IS_PRIMITIVE(v)) {
5621        *idr->addr() = OBJECT_TO_JSID(JSVAL_TO_OBJECT(v));
5622    } else {
5623        ReportBadXMLName(cx, v);
5624        return JS_FALSE;
5625    }
5626    return JS_TRUE;
5627}
5628
5629static JSBool
5630xml_child_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval name,
5631                 jsval *rval)
5632{
5633    bool isIndex;
5634    uint32_t index;
5635    JSXML *kid;
5636    JSObject *kidobj;
5637
5638    /* ECMA-357 13.4.4.6 */
5639    JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
5640
5641    if (!IdValIsIndex(cx, name, &index, &isIndex))
5642        return JS_FALSE;
5643
5644    if (isIndex) {
5645        if (index >= JSXML_LENGTH(xml)) {
5646            *rval = JSVAL_VOID;
5647        } else {
5648            kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
5649            if (!kid) {
5650                *rval = JSVAL_VOID;
5651            } else {
5652                kidobj = js_GetXMLObject(cx, kid);
5653                if (!kidobj)
5654                    return JS_FALSE;
5655                *rval = OBJECT_TO_JSVAL(kidobj);
5656            }
5657        }
5658        return JS_TRUE;
5659    }
5660
5661    AutoIdRooter idr(cx);
5662    if (!ValueToId(cx, name, &idr))
5663        return JS_FALSE;
5664
5665    return GetProperty(cx, obj, idr.id(), rval);
5666}
5667
5668/* XML and XMLList */
5669static JSBool
5670xml_child(JSContext *cx, uintN argc, jsval *vp)
5671{
5672    jsval v;
5673    JSXML *list, *vxml;
5674    JSObject *kidobj;
5675
5676    XML_METHOD_PROLOG;
5677    jsval name = argc != 0 ? vp[2] : JSVAL_VOID;
5678    if (xml->xml_class == JSXML_CLASS_LIST) {
5679        /* ECMA-357 13.5.4.4 */
5680        list = xml_list_helper(cx, xml, vp);
5681        if (!list)
5682            return JS_FALSE;
5683
5684        JSXMLArrayCursor<JSXML> cursor(&xml->xml_kids);
5685        while (JSXML *kid = cursor.getNext()) {
5686            kidobj = js_GetXMLObject(cx, kid);
5687            if (!kidobj)
5688                return JS_FALSE;
5689            if (!xml_child_helper(cx, kidobj, kid, name, &v))
5690                return JS_FALSE;
5691            if (JSVAL_IS_VOID(v)) {
5692                /* The property didn't exist in this kid. */
5693                continue;
5694            }
5695
5696            JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5697            vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
5698            if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
5699                !Append(cx, list, vxml)) {
5700                return JS_FALSE;
5701            }
5702        }
5703        return JS_TRUE;
5704    }
5705
5706    /* ECMA-357 Edition 2 13.3.4.6 (note 13.3, not 13.4 as in Edition 1). */
5707    if (!xml_child_helper(cx, obj, xml, name, vp))
5708        return JS_FALSE;
5709    if (JSVAL_IS_VOID(*vp) && !xml_list_helper(cx, xml, vp))
5710        return JS_FALSE;
5711    return JS_TRUE;
5712}
5713
5714static JSBool
5715xml_childIndex(JSContext *cx, uintN argc, jsval *vp)
5716{
5717    JSXML *parent;
5718    uint32_t i, n;
5719
5720    NON_LIST_XML_METHOD_PROLOG;
5721    parent = xml->parent;
5722    if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
5723        *