Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsxml.cpp
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 #include "jstypes.h"
48 #include "jsstdint.h"
49 #include "jsbit.h"
50 #include "jsprf.h"
51 #include "jsutil.h"
52 #include "jsapi.h"
53 #include "jsarray.h"
54 #include "jsatom.h"
55 #include "jsbool.h"
56 #include "jscntxt.h"
57 #include "jsfun.h"
58 #include "jsgc.h"
59 #include "jsinterp.h"
60 #include "jslock.h"
61 #include "jsnum.h"
62 #include "jsobj.h"
63 #include "jsopcode.h"
64 #include "jsparse.h"
65 #include "jsscan.h"
66 #include "jsscope.h"
67 #include "jsscript.h"
68 #include "jsstr.h"
69 #include "jsxml.h"
70 #include "jsstaticcheck.h"
71 #include "jsvector.h"
72
73 #include "jsatominlines.h"
74 #include "jscntxtinlines.h"
75 #include "jsinterpinlines.h"
76 #include "jsobjinlines.h"
77 #include "jsstrinlines.h"
78
79 #ifdef DEBUG
80 #include <string.h>     /* for #ifdef DEBUG memset calls */
81 #endif
82
83 using namespace js;
84 using namespace js::gc;
85
86 /*
87  * NOTES
88  * - in the js shell, you must use the -x command line option, or call
89  *   options('xml') before compiling anything that uses XML literals
90  *
91  * TODO
92  * - XXXbe patrol
93  * - Fuse objects and their JSXML* private data into single GC-things
94  * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
95  * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
96  */
97
98 static inline bool
99 js_EnterLocalRootScope(JSContext *cx)
100 {
101     return true;
102 }
103
104 static inline void
105 js_LeaveLocalRootScope(JSContext *cx)
106 {
107 }
108
109 static inline void
110 js_LeaveLocalRootScopeWithResult(JSContext *cx, jsval rval)
111 {
112 }
113
114 static inline void
115 js_LeaveLocalRootScopeWithResult(JSContext *cx, Value rval)
116 {
117 }
118
119 static inline void
120 js_LeaveLocalRootScopeWithResult(JSContext *cx, void *rval)
121 {
122 }
123
124 #ifdef XML_METERING
125 static struct {
126     jsrefcount  qname;
127     jsrefcount  xmlnamespace;
128     jsrefcount  xml;
129     jsrefcount  xmlobj;
130 } xml_stats;
131
132 #define METER(x)        JS_ATOMIC_INCREMENT(&(x))
133 #define UNMETER(x)      JS_ATOMIC_DECREMENT(&(x))
134 #else
135 #define METER(x)        /* nothing */
136 #define UNMETER(x)      /* nothing */
137 #endif
138
139 /*
140  * Random utilities and global functions.
141  */
142 const char js_AttributeName_str[] = "AttributeName";
143 const char js_isXMLName_str[]     = "isXMLName";
144 const char js_XMLList_str[]       = "XMLList";
145 const char js_localName_str[]     = "localName";
146 const char js_xml_parent_str[]    = "parent";
147 const char js_prefix_str[]        = "prefix";
148 const char js_toXMLString_str[]   = "toXMLString";
149 const char js_uri_str[]           = "uri";
150
151 const char js_amp_entity_str[]    = "&amp;";
152 const char js_gt_entity_str[]     = "&gt;";
153 const char js_lt_entity_str[]     = "&lt;";
154 const char js_quot_entity_str[]   = "&quot;";
155 const char js_leftcurly_entity_str[]   = "&#123;";
156
157 #define IS_STAR(str)  ((str)->length() == 1 && *(str)->chars() == '*')
158
159 static JSBool
160 GetXMLFunction(JSContext *cx, JSObject *obj, jsid id, jsval *vp);
161
162 static JSBool
163 IsDeclared(const JSObject *obj)
164 {
165     jsval v;
166
167     JS_ASSERT(obj->getClass() == &js_NamespaceClass);
168     v = obj->getNamespaceDeclared();
169     JS_ASSERT(JSVAL_IS_VOID(v) || v == JSVAL_TRUE);
170     return v == JSVAL_TRUE;
171 }
172
173 static JSBool
174 xml_isXMLName(JSContext *cx, uintN argc, jsval *vp)
175 {
176     *vp = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argc ? vp[2] : JSVAL_VOID));
177     return JS_TRUE;
178 }
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  */
185 static inline JSObject *
186 NewBuiltinClassInstanceXML(JSContext *cx, Class *clasp)
187 {
188     JSObject *obj = NewBuiltinClassInstance(cx, clasp);
189     if (obj)
190         obj->syncSpecialEquality();
191     return obj;
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  */
205 DEFINE_GETTER(NamePrefix_getter,
206               if (obj->getClass() == &js_NamespaceClass) *vp = obj->getNamePrefixVal())
207 DEFINE_GETTER(NameURI_getter,
208               if (obj->getClass() == &js_NamespaceClass) *vp = obj->getNameURIVal())
209
210 static JSBool
211 namespace_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() != &js_NamespaceClass)
218           ? JS_FALSE
219           : EqualStrings(obj->getNameURI(), obj2->getNameURI());
220     return JS_TRUE;
221 }
222
223 JS_FRIEND_DATA(Class) js_NamespaceClass = {
224     "Namespace",
225     JSCLASS_CONSTRUCT_PROTOTYPE |
226     JSCLASS_HAS_RESERVED_SLOTS(JSObject::NAMESPACE_CLASS_RESERVED_SLOTS) |
227     JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_Namespace),
228     PropertyStub,         /* addProperty */
229     PropertyStub,         /* delProperty */
230     PropertyStub,         /* getProperty */
231     StrictPropertyStub,   /* setProperty */
232     EnumerateStub,
233     ResolveStub,
234     ConvertStub,
235     FinalizeStub,
236     NULL,                 /* reserved0   */
237     NULL,                 /* checkAccess */
238     NULL,                 /* call        */
239     NULL,                 /* construct   */
240     NULL,                 /* xdrObject   */
241     NULL,                 /* hasInstance */
242     NULL,                 /* mark        */
243     {
244         namespace_equality,
245         NULL,             /* outerObject    */
246         NULL,             /* innerObject    */
247         NULL,             /* iteratorObject */
248         NULL,             /* wrappedObject  */
249     }
250 };
251
252 #define NAMESPACE_ATTRS                                                       \
253     (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
254
255 static JSPropertySpec namespace_props[] = {
256     {js_prefix_str, 0, NAMESPACE_ATTRS, NamePrefix_getter, 0},
257     {js_uri_str,    0, NAMESPACE_ATTRS, NameURI_getter,    0},
258     {0,0,0,0,0}
259 };
260
261 static JSBool
262 namespace_toString(JSContext *cx, uintN argc, Value *vp)
263 {
264     JSObject *obj = ToObject(cx, &vp[1]);
265     if (!obj)
266         return JS_FALSE;
267     if (!JS_InstanceOf(cx, obj, Jsvalify(&js_NamespaceClass), Jsvalify(vp + 2)))
268         return JS_FALSE;
269     *vp = Valueify(obj->getNameURIVal());
270     return JS_TRUE;
271 }
272
273 static JSFunctionSpec namespace_methods[] = {
274     JS_FN(js_toString_str,  namespace_toString,        0,0),
275     JS_FS_END
276 };
277
278 static JSObject *
279 NewXMLNamespace(JSContext *cx, JSLinearString *prefix, JSLinearString *uri, JSBool declared)
280 {
281     JSObject *obj;
282
283     obj = NewBuiltinClassInstanceXML(cx, &js_NamespaceClass);
284     if (!obj)
285         return JS_FALSE;
286     JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefixVal()));
287     JS_ASSERT(JSVAL_IS_VOID(obj->getNameURIVal()));
288     JS_ASSERT(JSVAL_IS_VOID(obj->getNamespaceDeclared()));
289     if (prefix)
290         obj->setNamePrefix(prefix);
291     if (uri)
292         obj->setNameURI(uri);
293     if (declared)
294         obj->setNamespaceDeclared(JSVAL_TRUE);
295     METER(xml_stats.xmlnamespace);
296     return obj;
297 }
298
299 /*
300  * QName class and library functions.
301  */
302 DEFINE_GETTER(QNameNameURI_getter,
303               if (obj->getClass() == &js_QNameClass)
304                   *vp = JSVAL_IS_VOID(obj->getNameURIVal()) ? JSVAL_NULL : obj->getNameURIVal())
305 DEFINE_GETTER(QNameLocalName_getter,
306               if (obj->getClass() == &js_QNameClass)
307                   *vp = obj->getQNameLocalNameVal())
308
309 static JSBool
310 qname_identity(JSObject *qna, JSObject *qnb)
311 {
312     JSLinearString *uri1 = qna->getNameURI();
313     JSLinearString *uri2 = qnb->getNameURI();
314
315     if (!uri1 ^ !uri2)
316         return JS_FALSE;
317     if (uri1 && !EqualStrings(uri1, uri2))
318         return JS_FALSE;
319     return EqualStrings(qna->getQNameLocalName(), qnb->getQNameLocalName());
320 }
321
322 static JSBool
323 qname_equality(JSContext *cx, JSObject *qn, const Value *v, JSBool *bp)
324 {
325     JSObject *obj2;
326
327     obj2 = v->toObjectOrNull();
328     *bp = (!obj2 || obj2->getClass() != &js_QNameClass)
329           ? JS_FALSE
330           : qname_identity(qn, obj2);
331     return JS_TRUE;
332 }
333
334 JS_FRIEND_DATA(Class) js_QNameClass = {
335     "QName",
336     JSCLASS_CONSTRUCT_PROTOTYPE |
337     JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
338     JSCLASS_MARK_IS_TRACE | JSCLASS_HAS_CACHED_PROTO(JSProto_QName),
339     PropertyStub,         /* addProperty */
340     PropertyStub,         /* delProperty */
341     PropertyStub,         /* getProperty */
342     StrictPropertyStub,   /* setProperty */
343     EnumerateStub,
344     ResolveStub,
345     ConvertStub,
346     FinalizeStub,
347     NULL,                 /* reserved0   */
348     NULL,                 /* checkAccess */
349     NULL,                 /* call        */
350     NULL,                 /* construct   */
351     NULL,                 /* xdrObject   */
352     NULL,                 /* hasInstance */
353     NULL,                 /* mark        */
354     {
355         qname_equality,
356         NULL,             /* outerObject    */
357         NULL,             /* innerObject    */
358         NULL,             /* iteratorObject */
359         NULL,             /* wrappedObject  */
360     }
361 };
362
363 /*
364  * Classes for the ECMA-357-internal types AttributeName and AnyName, which
365  * are like QName, except that they have no property getters.  They share the
366  * qname_toString method, and therefore are exposed as constructable objects
367  * in this implementation.
368  */
369 JS_FRIEND_DATA(Class) js_AttributeNameClass = {
370     js_AttributeName_str,
371     JSCLASS_CONSTRUCT_PROTOTYPE |
372     JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
373     JSCLASS_MARK_IS_TRACE | JSCLASS_IS_ANONYMOUS,
374     PropertyStub,         /* addProperty */
375     PropertyStub,         /* delProperty */
376     PropertyStub,         /* getProperty */
377     StrictPropertyStub,   /* setProperty */
378     EnumerateStub,
379     ResolveStub,
380     ConvertStub,
381     FinalizeStub
382 };
383
384 JS_FRIEND_DATA(Class) js_AnyNameClass = {
385     js_AnyName_str,
386     JSCLASS_CONSTRUCT_PROTOTYPE |
387     JSCLASS_HAS_RESERVED_SLOTS(JSObject::QNAME_CLASS_RESERVED_SLOTS) |
388     JSCLASS_MARK_IS_TRACE | JSCLASS_IS_ANONYMOUS,
389     PropertyStub,         /* addProperty */
390     PropertyStub,         /* delProperty */
391     PropertyStub,         /* getProperty */
392     StrictPropertyStub,   /* setProperty */
393     EnumerateStub,
394     ResolveStub,
395     ConvertStub,
396     FinalizeStub
397 };
398
399 #define QNAME_ATTRS (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
400
401 static JSPropertySpec qname_props[] = {
402     {js_uri_str,       0, QNAME_ATTRS, QNameNameURI_getter,   0},
403     {js_localName_str, 0, QNAME_ATTRS, QNameLocalName_getter, 0},
404     {0,0,0,0,0}
405 };
406
407 static JSString *
408 ConvertQNameToString(JSContext *cx, JSObject *obj)
409 {
410     JS_ASSERT(obj->isQName());
411     JSString *uri = obj->getNameURI();
412     JSString *str;
413     if (!uri) {
414         /* No uri means wildcard qualifier. */
415         str = ATOM_TO_STRING(cx->runtime->atomState.starQualifierAtom);
416     } else if (uri->empty()) {
417         /* Empty string for uri means localName is in no namespace. */
418         str = cx->runtime->emptyString;
419     } else {
420         JSString *qualstr = ATOM_TO_STRING(cx->runtime->atomState.qualifierAtom);
421         str = js_ConcatStrings(cx, uri, qualstr);
422         if (!str)
423             return NULL;
424     }
425     str = js_ConcatStrings(cx, str, obj->getQNameLocalName());
426     if (!str)
427         return NULL;
428
429     if (obj->getClass() == &js_AttributeNameClass) {
430         JS::Anchor<JSString *> anchor(str);
431         size_t length = str->length();
432         jschar *chars = (jschar *) cx->malloc((length + 2) * sizeof(jschar));
433         if (!chars)
434             return JS_FALSE;
435         *chars = '@';
436         const jschar *strChars = str->getChars(cx);
437         if (!strChars) {
438             cx->free(chars);
439             return NULL;
440         }
441         js_strncpy(chars + 1, strChars, length);
442         chars[++length] = 0;
443         str = js_NewString(cx, chars, length);
444         if (!str) {
445             cx->free(chars);
446             return NULL;
447         }
448     }
449     return str;
450 }
451
452 static JSBool
453 qname_toString(JSContext *cx, uintN argc, Value *vp)
454 {
455     JSObject *obj = ToObject(cx, &vp[1]);
456     if (!obj)
457         return false;
458
459     if (!InstanceOf(cx, obj, &js_QNameClass, vp + 2))
460         return false;
461
462     JSString *str = ConvertQNameToString(cx, obj);
463     if (!str)
464         return false;
465
466     vp->setString(str);
467     return true;
468 }
469
470 static JSFunctionSpec qname_methods[] = {
471     JS_FN(js_toString_str,  qname_toString,    0,0),
472     JS_FS_END
473 };
474
475
476 static void
477 InitXMLQName(JSObject *obj, JSLinearString *uri, JSLinearString *prefix,
478              JSLinearString *localName)
479 {
480     JS_ASSERT(obj->isQName());
481     JS_ASSERT(JSVAL_IS_VOID(obj->getNamePrefixVal()));
482     JS_ASSERT(JSVAL_IS_VOID(obj->getNameURIVal()));
483     JS_ASSERT(JSVAL_IS_VOID(obj->getQNameLocalNameVal()));
484     if (uri)
485         obj->setNameURI(uri);
486     if (prefix)
487         obj->setNamePrefix(prefix);
488     if (localName)
489         obj->setQNameLocalName(localName);
490 }
491
492 static JSObject *
493 NewXMLQName(JSContext *cx, JSLinearString *uri, JSLinearString *prefix,
494             JSLinearString *localName)
495 {
496     JSObject *obj = NewBuiltinClassInstanceXML(cx, &js_QNameClass);
497     if (!obj)
498         return NULL;
499     InitXMLQName(obj, uri, prefix, localName);
500     METER(xml_stats.qname);
501     return obj;
502 }
503
504 static JSObject *
505 NewXMLAttributeName(JSContext *cx, JSLinearString *uri, JSLinearString *prefix,
506                     JSLinearString *localName)
507 {
508     /*
509      * AttributeName is an internal anonymous class which instances are not
510      * exposed to scripts.
511      */
512     JSObject *obj = NewNonFunction<WithProto::Given>(cx, &js_AttributeNameClass, NULL, NULL);
513     if (!obj)
514         return NULL;
515     JS_ASSERT(obj->isQName());
516     InitXMLQName(obj, uri, prefix, localName);
517     METER(xml_stats.qname);
518     return obj;
519 }
520
521 JSObject *
522 js_ConstructXMLQNameObject(JSContext *cx, const Value &nsval, const Value &lnval)
523 {
524     Value argv[2];
525
526     /*
527      * ECMA-357 11.1.2,
528      * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
529      * production, step 2.
530      */
531     if (nsval.isObject() &&
532         nsval.toObject().getClass() == &js_AnyNameClass) {
533         argv[0].setNull();
534     } else {
535         argv[0] = nsval;
536     }
537     argv[1] = lnval;
538     return js_ConstructObject(cx, &js_QNameClass, NULL, NULL, 2, argv);
539 }
540
541 static JSBool
542 IsXMLName(const jschar *cp, size_t n)
543 {
544     JSBool rv;
545     jschar c;
546
547     rv = JS_FALSE;
548     if (n != 0 && JS_ISXMLNSSTART(*cp)) {
549         while (--n != 0) {
550             c = *++cp;
551             if (!JS_ISXMLNS(c))
552                 return rv;
553         }
554         rv = JS_TRUE;
555     }
556     return rv;
557 }
558
559 JSBool
560 js_IsXMLName(JSContext *cx, jsval v)
561 {
562     JSLinearString *name = NULL;
563     JSErrorReporter older;
564
565     /*
566      * Inline specialization of the QName constructor called with v passed as
567      * the only argument, to compute the localName for the constructed qname,
568      * without actually allocating the object or computing its uri and prefix.
569      * See ECMA-357 13.1.2.1 step 1 and 13.3.2.
570      */
571     if (!JSVAL_IS_PRIMITIVE(v) &&
572         JSVAL_TO_OBJECT(v)->isQName()) {
573         name = JSVAL_TO_OBJECT(v)->getQNameLocalName();
574     } else {
575         older = JS_SetErrorReporter(cx, NULL);
576         JSString *str = js_ValueToString(cx, Valueify(v));
577         if (str)
578             name = str->ensureLinear(cx);
579         JS_SetErrorReporter(cx, older);
580         if (!name) {
581             JS_ClearPendingException(cx);
582             return JS_FALSE;
583         }
584     }
585
586     return IsXMLName(name->chars(), name->length());
587 }
588
589 /*
590  * When argc is -1, it indicates argv is empty but the code should behave as
591  * if argc is 1 and argv[0] is JSVAL_VOID.
592  */
593 static JSBool
594 NamespaceHelper(JSContext *cx, JSObject *obj, intN argc, jsval *argv,
595                 jsval *rval)
596 {
597     jsval urival, prefixval;
598     JSObject *uriobj;
599     JSBool isNamespace, isQName;
600     Class *clasp;
601     JSLinearString *empty, *prefix, *uri;
602
603     isNamespace = isQName = JS_FALSE;
604 #ifdef __GNUC__         /* suppress bogus gcc warnings */
605     uriobj = NULL;
606 #endif
607     if (argc <= 0) {
608         urival = JSVAL_VOID;
609     } else {
610         urival = argv[argc > 1];
611         if (!JSVAL_IS_PRIMITIVE(urival)) {
612             uriobj = JSVAL_TO_OBJECT(urival);
613             clasp = uriobj->getClass();
614             isNamespace = (clasp == &js_NamespaceClass);
615             isQName = (clasp == &js_QNameClass);
616         }
617     }
618
619     if (!obj) {
620         /* Namespace called as function. */
621         if (argc == 1 && isNamespace) {
622             /* Namespace called with one Namespace argument is identity. */
623             *rval = urival;
624             return JS_TRUE;
625         }
626
627         obj = NewBuiltinClassInstanceXML(cx, &js_NamespaceClass);
628         if (!obj)
629             return JS_FALSE;
630     }
631     *rval = OBJECT_TO_JSVAL(obj);
632     METER(xml_stats.xmlnamespace);
633
634     empty = cx->runtime->emptyString;
635     obj->setNamePrefix(empty);
636     obj->setNameURI(empty);
637
638     if (argc == 1 || argc == -1) {
639         if (isNamespace) {
640             obj->setNameURI(uriobj->getNameURI());
641             obj->setNamePrefix(uriobj->getNamePrefix());
642         } else if (isQName && (uri = uriobj->getNameURI())) {
643             obj->setNameURI(uri);
644             obj->setNamePrefix(uriobj->getNamePrefix());
645         } else {
646             JSString *str = js_ValueToString(cx, Valueify(urival));
647             if (!str)
648                 return JS_FALSE;
649             uri = str->ensureLinear(cx);
650             if (!uri)
651                 return JS_FALSE;
652             obj->setNameURI(uri);
653             if (!uri->empty())
654                 obj->clearNamePrefix();
655         }
656     } else if (argc == 2) {
657         if (!isQName || !(uri = uriobj->getNameURI())) {
658             JSString *str = js_ValueToString(cx, Valueify(urival));
659             if (!str)
660                 return JS_FALSE;
661             uri = str->ensureLinear(cx);
662             if (!uri)
663                 return JS_FALSE;
664         }
665         obj->setNameURI(uri);
666
667         prefixval = argv[0];
668         if (uri->empty()) {
669             if (!JSVAL_IS_VOID(prefixval)) {
670                 JSString *str = js_ValueToString(cx, Valueify(prefixval));
671                 if (!str)
672                     return JS_FALSE;
673                 if (!str->empty()) {
674                     JSAutoByteString bytes;
675                     if (js_ValueToPrintable(cx, StringValue(str), &bytes)) {
676                         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
677                                              JSMSG_BAD_XML_NAMESPACE, bytes.ptr());
678                     }
679                     return JS_FALSE;
680                 }
681             }
682         } else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
683             obj->clearNamePrefix();
684         } else {
685             JSString *str = js_ValueToString(cx, Valueify(prefixval));
686             if (!str)
687                 return JS_FALSE;
688             prefix = str->ensureLinear(cx);
689             if (!prefix)
690                 return JS_FALSE;
691             obj->setNamePrefix(prefix);
692         }
693     }
694     return JS_TRUE;
695 }
696
697 static JSBool
698 Namespace(JSContext *cx, uintN argc, Value *vp)
699 {
700     JSObject *thisobj = NULL;
701     (void)IsConstructing_PossiblyWithGivenThisObject(vp, &thisobj);
702     return NamespaceHelper(cx, thisobj, argc, Jsvalify(vp + 2), Jsvalify(vp));
703 }
704
705 /*
706  * When argc is -1, it indicates argv is empty but the code should behave as
707  * if argc is 1 and argv[0] is JSVAL_VOID.
708  */
709 static JSBool
710 QNameHelper(JSContext *cx, JSObject *obj, intN argc, jsval *argv, jsval *rval)
711 {
712     jsval nameval, nsval;
713     JSBool isQName, isNamespace;
714     JSObject *qn;
715     JSLinearString *uri, *prefix, *name;
716     JSObject *obj2;
717
718     if (argc <= 0) {
719         nameval = JSVAL_VOID;
720         isQName = JS_FALSE;
721     } else {
722         nameval = argv[argc > 1];
723         isQName =
724             !JSVAL_IS_PRIMITIVE(nameval) &&
725             JSVAL_TO_OBJECT(nameval)->getClass() == &js_QNameClass;
726     }
727
728     if (!obj) {
729         /* QName called as function. */
730         if (argc == 1 && isQName) {
731             /* QName called with one QName argument is identity. */
732             *rval = nameval;
733             return JS_TRUE;
734         }
735
736         /* Create and return a new QName object exactly as if constructed. */
737         obj = NewBuiltinClassInstanceXML(cx, &js_QNameClass);
738         if (!obj)
739             return JS_FALSE;
740     }
741     *rval = OBJECT_TO_JSVAL(obj);
742     METER(xml_stats.qname);
743
744     if (isQName) {
745         /* If namespace is not specified and name is a QName, clone it. */
746         qn = JSVAL_TO_OBJECT(nameval);
747         if (argc == 1) {
748             uri = qn->getNameURI();
749             prefix = qn->getNamePrefix();
750             name = qn->getQNameLocalName();
751             goto out;
752         }
753
754         /* Namespace and qname were passed -- use the qname's localName. */
755         nameval = qn->getQNameLocalNameVal();
756     }
757
758     if (argc == 0) {
759         name = cx->runtime->emptyString;
760     } else if (argc < 0) {
761         name = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
762     } else {
763         JSString *str = js_ValueToString(cx, Valueify(nameval));
764         if (!str)
765             return JS_FALSE;
766         name = str->ensureLinear(cx);
767         if (!name)
768             return JS_FALSE;
769         argv[argc > 1] = STRING_TO_JSVAL(name);
770     }
771
772     if (argc > 1 && !JSVAL_IS_VOID(argv[0])) {
773         nsval = argv[0];
774     } else if (IS_STAR(name)) {
775         nsval = JSVAL_NULL;
776     } else {
777         if (!js_GetDefaultXMLNamespace(cx, &nsval))
778             return JS_FALSE;
779         JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
780         JS_ASSERT(JSVAL_TO_OBJECT(nsval)->getClass() ==
781                   &js_NamespaceClass);
782     }
783
784     if (JSVAL_IS_NULL(nsval)) {
785         /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
786         prefix = uri = NULL;
787     } else {
788         /*
789          * Inline specialization of the Namespace constructor called with
790          * nsval passed as the only argument, to compute the uri and prefix
791          * for the constructed namespace, without actually allocating the
792          * object or computing other members.  See ECMA-357 13.3.2 6(a) and
793          * 13.2.2.
794          */
795         isNamespace = isQName = JS_FALSE;
796         if (!JSVAL_IS_PRIMITIVE(nsval)) {
797             obj2 = JSVAL_TO_OBJECT(nsval);
798             isNamespace = (obj2->getClass() == &js_NamespaceClass);
799             isQName = (obj2->getClass() == &js_QNameClass);
800         }
801 #ifdef __GNUC__         /* suppress bogus gcc warnings */
802         else obj2 = NULL;
803 #endif
804
805         if (isNamespace) {
806             uri = obj2->getNameURI();
807             prefix = obj2->getNamePrefix();
808         } else if (isQName && (uri = obj2->getNameURI())) {
809             JS_ASSERT(argc > 1);
810             prefix = obj2->getNamePrefix();
811         } else {
812             JS_ASSERT(argc > 1);
813             JSString *str = js_ValueToString(cx, Valueify(nsval));
814             if (!str)
815                 return JS_FALSE;
816             uri = str->ensureLinear(cx);
817             if (!uri)
818                 return JS_FALSE;
819             argv[0] = STRING_TO_JSVAL(uri);     /* local root */
820
821             /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
822             prefix = uri->empty() ? cx->runtime->emptyString : NULL;
823         }
824     }
825
826 out:
827     InitXMLQName(obj, uri, prefix, name);
828     return JS_TRUE;
829 }
830
831 static JSBool
832 QName(JSContext *cx, uintN argc, Value *vp)
833 {
834     JSObject *thisobj = NULL;
835     (void)IsConstructing_PossiblyWithGivenThisObject(vp, &thisobj);
836     return QNameHelper(cx, thisobj, argc, Jsvalify(vp + 2), Jsvalify(vp));
837 }
838
839 /*
840  * XMLArray library functions.
841  */
842 static JSBool
843 namespace_identity(const void *a, const void *b)
844 {
845     const JSObject *nsa = (const JSObject *) a;
846     const JSObject *nsb = (const JSObject *) b;
847     JSLinearString *prefixa = nsa->getNamePrefix();
848     JSLinearString *prefixb = nsb->getNamePrefix();
849
850     if (prefixa && prefixb) {
851         if (!EqualStrings(prefixa, prefixb))
852             return JS_FALSE;
853     } else {
854         if (prefixa || prefixb)
855             return JS_FALSE;
856     }
857     return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
858 }
859
860 static JSBool
861 attr_identity(const void *a, const void *b)
862 {
863     const JSXML *xmla = (const JSXML *) a;
864     const JSXML *xmlb = (const JSXML *) b;
865
866     return qname_identity(xmla->name, xmlb->name);
867 }
868
869 void
870 JSXMLArrayCursor::trace(JSTracer *trc) {
871 #ifdef DEBUG
872     size_t index = 0;
873 #endif
874     for (JSXMLArrayCursor *cursor = this; cursor; cursor = cursor->next)
875         js::gc::MarkGCThing(trc, cursor->root, "cursor_root", index++);
876 }
877
878 static void
879 XMLArrayCursorTrace(JSTracer *trc, JSXMLArrayCursor *cursor)
880 {
881     cursor->trace(trc);
882 }
883
884 /* NB: called with null cx from the GC, via xml_trace => JSXMLArray::trim. */
885 bool
886 JSXMLArray::setCapacity(JSContext *cx, uint32 newCapacity)
887 {
888     if (newCapacity == 0) {
889         /* We could let realloc(p, 0) free this, but purify gets confused. */
890         if (vector) {
891             if (cx)
892                 cx->free(vector);
893             else
894                 js_free(vector);
895         }
896         vector = NULL;
897     } else {
898         void **tmp;
899
900         if (
901 #if JS_BITS_PER_WORD == 32
902             (size_t)newCapacity > ~(size_t)0 / sizeof(void *) ||
903 #endif
904             !(tmp = (void **) js_realloc(vector, newCapacity * sizeof(void *)))) {
905             if (cx)
906                 JS_ReportOutOfMemory(cx);
907             return false;
908         }
909         vector = tmp;
910     }
911     capacity = JSXML_PRESET_CAPACITY | newCapacity;
912     return true;
913 }
914
915 void
916 JSXMLArray::trim()
917 {
918     if (capacity & JSXML_PRESET_CAPACITY)
919         return;
920     if (length < capacity)
921         setCapacity(NULL, length);
922 }
923
924 void
925 JSXMLArray::finish(JSContext *cx)
926 {
927     cx->free(vector);
928
929     while (JSXMLArrayCursor *cursor = cursors)
930         cursor->disconnect();
931
932 #ifdef DEBUG
933     memset(this, 0xd5, sizeof *this);
934 #endif
935 }
936
937 #define XML_NOT_FOUND   ((uint32) -1)
938
939 static uint32
940 XMLArrayFindMember(const JSXMLArray *array, void *elt, JSIdentityOp identity)
941 {
942     void **vector;
943     uint32 i, n;
944
945     /* The identity op must not reallocate array->vector. */
946     vector = array->vector;
947     if (identity) {
948         for (i = 0, n = array->length; i < n; i++) {
949             if (identity(vector[i], elt))
950                 return i;
951         }
952     } else {
953         for (i = 0, n = array->length; i < n; i++) {
954             if (vector[i] == elt)
955                 return i;
956         }
957     }
958     return XML_NOT_FOUND;
959 }
960
961 /*
962  * Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
963  * that, grow by LINEAR_INCREMENT.  Both must be powers of two, and threshold
964  * should be greater than increment.
965  */
966 #define LINEAR_THRESHOLD        256
967 #define LINEAR_INCREMENT        32
968
969 static JSBool
970 XMLArrayAddMember(JSContext *cx, JSXMLArray *array, uint32 index, void *elt)
971 {
972     uint32 capacity, i;
973     int log2;
974     void **vector;
975
976     if (index >= array->length) {
977         if (index >= JSXML_CAPACITY(array)) {
978             /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
979             capacity = index + 1;
980             if (index >= LINEAR_THRESHOLD) {
981                 capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
982             } else {
983                 JS_CEILING_LOG2(log2, capacity);
984                 capacity = JS_BIT(log2);
985             }
986             if (
987 #if JS_BITS_PER_WORD == 32
988                 (size_t)capacity > ~(size_t)0 / sizeof(void *) ||
989 #endif
990                 !(vector = (void **)
991                            js_realloc(array->vector, capacity * sizeof(void *)))) {
992                 JS_ReportOutOfMemory(cx);
993                 return JS_FALSE;
994             }
995             array->capacity = capacity;
996             array->vector = vector;
997             for (i = array->length; i < index; i++)
998                 vector[i] = NULL;
999         }
1000         array->length = index + 1;
1001     }
1002
1003     array->vector[index] = elt;
1004     return JS_TRUE;
1005 }
1006
1007 static JSBool
1008 XMLArrayInsert(JSContext *cx, JSXMLArray *array, uint32 i, uint32 n)
1009 {
1010     uint32 j;
1011     JSXMLArrayCursor *cursor;
1012
1013     j = array->length;
1014     JS_ASSERT(i <= j);
1015     if (!array->setCapacity(cx, j + n))
1016         return JS_FALSE;
1017
1018     array->length = j + n;
1019     JS_ASSERT(n != (uint32)-1);
1020     while (j != i) {
1021         --j;
1022         array->vector[j + n] = array->vector[j];
1023     }
1024
1025     for (cursor = array->cursors; cursor; cursor = cursor->next) {
1026         if (cursor->index > i)
1027             cursor->index += n;
1028     }
1029     return JS_TRUE;
1030 }
1031
1032 static void *
1033 XMLArrayDelete(JSContext *cx, JSXMLArray *array, uint32 index, JSBool compress)
1034 {
1035     uint32 length;
1036     void **vector, *elt;
1037     JSXMLArrayCursor *cursor;
1038
1039     length = array->length;
1040     if (index >= length)
1041         return NULL;
1042
1043     vector = array->vector;
1044     elt = vector[index];
1045     if (compress) {
1046         while (++index < length)
1047             vector[index-1] = vector[index];
1048         array->length = length - 1;
1049         array->capacity = JSXML_CAPACITY(array);
1050     } else {
1051         vector[index] = NULL;
1052     }
1053
1054     for (cursor = array->cursors; cursor; cursor = cursor->next) {
1055         if (cursor->index > index)
1056             --cursor->index;
1057     }
1058     return elt;
1059 }
1060
1061 static void
1062 XMLArrayTruncate(JSContext *cx, JSXMLArray *array, uint32 length)
1063 {
1064     void **vector;
1065
1066     JS_ASSERT(!array->cursors);
1067     if (length >= array->length)
1068         return;
1069
1070     if (length == 0) {
1071         if (array->vector)
1072             cx->free(array->vector);
1073         vector = NULL;
1074     } else {
1075         vector = (void **) js_realloc(array->vector, length * sizeof(void *));
1076         if (!vector)
1077             return;
1078     }
1079
1080     if (array->length > length)
1081         array->length = length;
1082     array->capacity = length;
1083     array->vector = vector;
1084 }
1085
1086 #define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, (void *)(e), f)
1087 #define XMLARRAY_HAS_MEMBER(a,e,f)  (XMLArrayFindMember(a, (void *)(e), f) != \
1088                                      XML_NOT_FOUND)
1089 #define XMLARRAY_MEMBER(a,i,t)      (((i) < (a)->length)                      \
1090                                      ? (t *) (a)->vector[i]                   \
1091                                      : NULL)
1092 #define XMLARRAY_SET_MEMBER(a,i,e)  JS_BEGIN_MACRO                            \
1093                                         if ((a)->length <= (i))               \
1094                                             (a)->length = (i) + 1;            \
1095                                         ((a)->vector[i] = (void *)(e));       \
1096                                     JS_END_MACRO
1097 #define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, (void *)(e))
1098 #define XMLARRAY_INSERT(x,a,i,n)    XMLArrayInsert(x, a, i, n)
1099 #define XMLARRAY_APPEND(x,a,e)      XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
1100 #define XMLARRAY_DELETE(x,a,i,c,t)  ((t *) XMLArrayDelete(x, a, i, c))
1101 #define XMLARRAY_TRUNCATE(x,a,n)    XMLArrayTruncate(x, a, n)
1102
1103 /*
1104  * Define XML setting property strings and constants early, so everyone can
1105  * use the same names.
1106  */
1107 static const char js_ignoreComments_str[]   = "ignoreComments";
1108 static const char js_ignoreProcessingInstructions_str[]
1109                                             = "ignoreProcessingInstructions";
1110 static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
1111 static const char js_prettyPrinting_str[]   = "prettyPrinting";
1112 static const char js_prettyIndent_str[]     = "prettyIndent";
1113
1114 #define XSF_IGNORE_COMMENTS                JS_BIT(0)
1115 #define XSF_IGNORE_PROCESSING_INSTRUCTIONS JS_BIT(1)
1116 #define XSF_IGNORE_WHITESPACE              JS_BIT(2)
1117 #define XSF_PRETTY_PRINTING                JS_BIT(3)
1118
1119 static JSPropertySpec xml_static_props[] = {
1120     {js_ignoreComments_str, 0, JSPROP_PERMANENT, NULL, NULL},
1121     {js_ignoreProcessingInstructions_str, 0, JSPROP_PERMANENT, NULL, NULL},
1122     {js_ignoreWhitespace_str, 0, JSPROP_PERMANENT, NULL, NULL},
1123     {js_prettyPrinting_str, 0, JSPROP_PERMANENT, NULL, NULL},
1124     {js_prettyIndent_str, 0, JSPROP_PERMANENT, NULL, NULL},
1125     {0,0,0,0,0}
1126 };
1127
1128 /* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
1129 #define IS_XML(str)                                                           \
1130     (str->length() == 3 && IS_XML_CHARS(str->chars()))
1131
1132 #define IS_XMLNS(str)                                                         \
1133     (str->length() == 5 && IS_XMLNS_CHARS(str->chars()))
1134
1135 #define IS_XML_CHARS(chars)                                                   \
1136     (JS_TOLOWER((chars)[0]) == 'x' &&                                         \
1137      JS_TOLOWER((chars)[1]) == 'm' &&                                         \
1138      JS_TOLOWER((chars)[2]) == 'l')
1139
1140 #define HAS_NS_AFTER_XML(chars)                                               \
1141     (JS_TOLOWER((chars)[3]) == 'n' &&                                         \
1142      JS_TOLOWER((chars)[4]) == 's')
1143
1144 #define IS_XMLNS_CHARS(chars)                                                 \
1145     (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
1146
1147 #define STARTS_WITH_XML(chars,length)                                         \
1148     (length >= 3 && IS_XML_CHARS(chars))
1149
1150 static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
1151 static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";
1152
1153 static JSObject *
1154 ParseNodeToQName(Parser *parser, JSParseNode *pn,
1155                  JSXMLArray *inScopeNSes, JSBool isAttributeName)
1156 {
1157     JSContext *cx = parser->context;
1158     JSLinearString *str, *uri, *prefix, *localName;
1159     size_t length, offset;
1160     const jschar *start, *limit, *colon;
1161     uint32 n;
1162     JSObject *ns;
1163     JSLinearString *nsprefix;
1164
1165     JS_ASSERT(pn->pn_arity == PN_NULLARY);
1166     str = pn->pn_atom;
1167     start = str->chars();
1168     length = str->length();
1169     JS_ASSERT(length != 0 && *start != '@');
1170     JS_ASSERT(length != 1 || *start != '*');
1171
1172     uri = cx->runtime->emptyString;
1173     limit = start + length;
1174     colon = js_strchr_limit(start, ':', limit);
1175     if (colon) {
1176         offset = colon - start;
1177         prefix = js_NewDependentString(cx, str, 0, offset);
1178         if (!prefix)
1179             return NULL;
1180
1181         if (STARTS_WITH_XML(start, offset)) {
1182             if (offset == 3) {
1183                 uri = JS_ASSERT_STRING_IS_FLAT(JS_InternString(cx, xml_namespace_str));
1184                 if (!uri)
1185                     return NULL;
1186             } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
1187                 uri = JS_ASSERT_STRING_IS_FLAT(JS_InternString(cx, xmlns_namespace_str));
1188                 if (!uri)
1189                     return NULL;
1190             } else {
1191                 uri = NULL;
1192             }
1193         } else {
1194             uri = NULL;
1195             n = inScopeNSes->length;
1196             while (n != 0) {
1197                 --n;
1198                 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
1199                 nsprefix = ns->getNamePrefix();
1200                 if (nsprefix && EqualStrings(nsprefix, prefix)) {
1201                     uri = ns->getNameURI();
1202                     break;
1203                 }
1204             }
1205         }
1206
1207         if (!uri) {
1208             Value v = StringValue(prefix);
1209             JSAutoByteString bytes;
1210             if (js_ValueToPrintable(parser->context, v, &bytes)) {
1211                 ReportCompileErrorNumber(parser->context, &parser->tokenStream, pn,
1212                                          JSREPORT_ERROR, JSMSG_BAD_XML_NAMESPACE, bytes.ptr());
1213             }
1214             return NULL;
1215         }
1216
1217         localName = js_NewStringCopyN(parser->context, colon + 1, length - (offset + 1));
1218         if (!localName)
1219             return NULL;
1220     } else {
1221         if (isAttributeName) {
1222             /*
1223              * An unprefixed attribute is not in any namespace, so set prefix
1224              * as well as uri to the empty string.
1225              */
1226             prefix = uri;
1227         } else {
1228             /*
1229              * Loop from back to front looking for the closest declared default
1230              * namespace.
1231              */
1232             n = inScopeNSes->length;
1233             while (n != 0) {
1234                 --n;
1235                 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSObject);
1236                 nsprefix = ns->getNamePrefix();
1237                 if (!nsprefix || nsprefix->empty()) {
1238                     uri = ns->getNameURI();
1239                     break;
1240                 }
1241             }
1242             prefix = uri->empty() ? parser->context->runtime->emptyString : NULL;
1243         }
1244         localName = str;
1245     }
1246
1247     return NewXMLQName(parser->context, uri, prefix, localName);
1248 }
1249
1250 static JSString *
1251 ChompXMLWhitespace(JSContext *cx, JSString *str)
1252 {
1253     size_t length, newlength, offset;
1254     const jschar *cp, *start, *end;
1255     jschar c;
1256
1257     length = str->length();
1258     start = str->getChars(cx);
1259     if (!start)
1260         return NULL;
1261
1262     for (cp = start, end = cp + length; cp < end; cp++) {
1263         c = *cp;
1264         if (!JS_ISXMLSPACE(c))
1265             break;
1266     }
1267     while (end > cp) {
1268         c = end[-1];
1269         if (!JS_ISXMLSPACE(c))
1270             break;
1271         --end;
1272     }
1273     newlength = end - cp;
1274     if (newlength == length)
1275         return str;
1276     offset = cp - start;
1277     return js_NewDependentString(cx, str, offset, newlength);
1278 }
1279
1280 static JSXML *
1281 ParseNodeToXML(Parser *parser, JSParseNode *pn,
1282                JSXMLArray *inScopeNSes, uintN flags)
1283 {
1284     JSContext *cx = parser->context;
1285     JSXML *xml, *kid, *attr, *attrj;
1286     JSLinearString *str;
1287     uint32 length, n, i, j;
1288     JSParseNode *pn2, *pn3, *head, **pnp;
1289     JSObject *ns;
1290     JSObject *qn, *attrjqn;
1291     JSXMLClass xml_class;
1292     int stackDummy;
1293
1294     if (!JS_CHECK_STACK_SIZE(cx->stackLimit, &stackDummy)) {
1295         ReportCompileErrorNumber(cx, &parser->tokenStream, pn, JSREPORT_ERROR,
1296                                  JSMSG_OVER_RECURSED);
1297         return NULL;
1298     }
1299
1300 #define PN2X_SKIP_CHILD ((JSXML *) 1)
1301
1302     /*
1303      * Cases return early to avoid common code that gets an outermost xml's
1304      * object, which protects GC-things owned by xml and its descendants from
1305      * garbage collection.
1306      */
1307     xml = NULL;
1308     if (!js_EnterLocalRootScope(cx))
1309         return NULL;
1310     switch (pn->pn_type) {
1311       case TOK_XMLELEM:
1312         length = inScopeNSes->length;
1313         pn2 = pn->pn_head;
1314         xml = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
1315         if (!xml)
1316             goto fail;
1317
1318         n = pn->pn_count;
1319         JS_ASSERT(n >= 2);
1320         n -= 2;
1321         if (!xml->xml_kids.setCapacity(cx, n))
1322             goto fail;
1323
1324         i = 0;
1325         while ((pn2 = pn2->pn_next) != NULL) {
1326             if (!pn2->pn_next) {
1327                 /* Don't append the end tag! */
1328                 JS_ASSERT(pn2->pn_type == TOK_XMLETAGO);
1329                 break;
1330             }
1331
1332             if ((flags & XSF_IGNORE_WHITESPACE) &&
1333                 n > 1 && pn2->pn_type == TOK_XMLSPACE) {
1334                 --n;
1335                 continue;
1336             }
1337
1338             kid = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
1339             if (kid == PN2X_SKIP_CHILD) {
1340                 --n;
1341                 continue;
1342             }
1343
1344             if (!kid)
1345                 goto fail;
1346
1347             /* Store kid in xml right away, to protect it from GC. */
1348             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1349             kid->parent = xml;
1350             ++i;
1351
1352             /* XXX where is this documented in an XML spec, or in E4X? */
1353             if ((flags & XSF_IGNORE_WHITESPACE) &&
1354                 n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
1355                 JSString *str = ChompXMLWhitespace(cx, kid->xml_value);
1356                 if (!str)
1357                     goto fail;
1358                 kid->xml_value = str;
1359             }
1360         }
1361
1362         JS_ASSERT(i == n);
1363         if (n < pn->pn_count - 2)
1364             xml->xml_kids.trim();
1365         XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1366         break;
1367
1368       case TOK_XMLLIST:
1369         xml = js_NewXML(cx, JSXML_CLASS_LIST);
1370         if (!xml)
1371             goto fail;
1372
1373         n = pn->pn_count;
1374         if (!xml->xml_kids.setCapacity(cx, n))
1375             goto fail;
1376
1377         i = 0;
1378         for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
1379             /*
1380              * Always ignore insignificant whitespace in lists -- we shouldn't
1381              * condition this on an XML.ignoreWhitespace setting when the list
1382              * constructor is XMLList (note XML/XMLList unification hazard).
1383              */
1384             if (pn2->pn_type == TOK_XMLSPACE) {
1385                 --n;
1386                 continue;
1387             }
1388
1389             kid = ParseNodeToXML(parser, pn2, inScopeNSes, flags);
1390             if (kid == PN2X_SKIP_CHILD) {
1391                 --n;
1392                 continue;
1393             }
1394
1395             if (!kid)
1396                 goto fail;
1397
1398             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1399             ++i;
1400         }
1401
1402         if (n < pn->pn_count)
1403             xml->xml_kids.trim();
1404         break;
1405
1406       case TOK_XMLSTAGO:
1407       case TOK_XMLPTAGC:
1408         length = inScopeNSes->length;
1409         pn2 = pn->pn_head;
1410         JS_ASSERT(pn2->pn_type == TOK_XMLNAME);
1411         if (pn2->pn_arity == PN_LIST)
1412             goto syntax;
1413
1414         xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
1415         if (!xml)
1416             goto fail;
1417
1418         /* First pass: check syntax and process namespace declarations. */
1419         JS_ASSERT(pn->pn_count >= 1);
1420         n = pn->pn_count - 1;
1421         pnp = &pn2->pn_next;
1422         head = *pnp;
1423         while ((pn2 = *pnp) != NULL) {
1424             size_t length;
1425             const jschar *chars;
1426
1427             if (pn2->pn_type != TOK_XMLNAME || pn2->pn_arity != PN_NULLARY)
1428                 goto syntax;
1429
1430             /* Enforce "Well-formedness constraint: Unique Att Spec". */
1431             for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
1432                 if (pn3->pn_atom == pn2->pn_atom) {
1433                     Value v = StringValue(ATOM_TO_STRING(pn2->pn_atom));
1434                     JSAutoByteString bytes;
1435                     if (js_ValueToPrintable(cx, v, &bytes)) { 
1436                         ReportCompileErrorNumber(cx, &parser->tokenStream, pn2,
1437                                                  JSREPORT_ERROR, JSMSG_DUPLICATE_XML_ATTR,
1438                                                  bytes.ptr());
1439                     }
1440                     goto fail;
1441                 }
1442             }
1443
1444             JSAtom *atom = pn2->pn_atom;
1445             pn2 = pn2->pn_next;
1446             JS_ASSERT(pn2);
1447             if (pn2->pn_type != TOK_XMLATTR)
1448                 goto syntax;
1449
1450             chars = atom->chars();
1451             length = atom->length();
1452             if (length >= 5 &&
1453                 IS_XMLNS_CHARS(chars) &&
1454                 (length == 5 || chars[5] == ':')) {
1455                 JSLinearString *uri, *prefix;
1456
1457                 uri = ATOM_TO_STRING(pn2->pn_atom);
1458                 if (length == 5) {
1459                     /* 10.3.2.1. Step 6(h)(i)(1)(a). */
1460                     prefix = cx->runtime->emptyString;
1461                 } else {
1462                     prefix = js_NewStringCopyN(cx, chars + 6, length - 6);
1463                     if (!prefix)
1464                         goto fail;
1465                 }
1466
1467                 /*
1468                  * Once the new ns is appended to xml->xml_namespaces, it is
1469                  * protected from GC by the object that owns xml -- which is
1470                  * either xml->object if outermost, or the object owning xml's
1471                  * oldest ancestor if !outermost.
1472                  */
1473                 ns = NewXMLNamespace(cx, prefix, uri, JS_TRUE);
1474                 if (!ns)
1475                     goto fail;
1476
1477                 /*
1478                  * Don't add a namespace that's already in scope.  If someone
1479                  * extracts a child property from its parent via [[Get]], then
1480                  * we enforce the invariant, noted many times in ECMA-357, that
1481                  * the child's namespaces form a possibly-improper superset of
1482                  * its ancestors' namespaces.
1483                  */
1484                 if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
1485                     if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
1486                         !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
1487                         goto fail;
1488                     }
1489                 }
1490
1491                 JS_ASSERT(n >= 2);
1492                 n -= 2;
1493                 *pnp = pn2->pn_next;
1494                 /* XXXbe recycle pn2 */
1495                 continue;
1496             }
1497
1498             pnp = &pn2->pn_next;
1499         }
1500
1501         xml->xml_namespaces.trim();
1502
1503         /* Second pass: process tag name and attributes, using namespaces. */
1504         pn2 = pn->pn_head;
1505         qn = ParseNodeToQName(parser, pn2, inScopeNSes, JS_FALSE);
1506         if (!qn)
1507             goto fail;
1508         xml->name = qn;
1509
1510         JS_ASSERT((n & 1) == 0);
1511         n >>= 1;
1512         if (!xml->xml_attrs.setCapacity(cx, n))
1513             goto fail;
1514
1515         for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
1516             qn = ParseNodeToQName(parser, pn2, inScopeNSes, JS_TRUE);
1517             if (!qn) {
1518                 xml->xml_attrs.length = i;
1519                 goto fail;
1520             }
1521
1522             /*
1523              * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
1524              * this time checking local name and namespace URI.
1525              */
1526             for (j = 0; j < i; j++) {
1527                 attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
1528                 attrjqn = attrj->name;
1529                 if (EqualStrings(attrjqn->getNameURI(), qn->getNameURI()) &&
1530                     EqualStrings(attrjqn->getQNameLocalName(), qn->getQNameLocalName())) {
1531                     Value v = StringValue(ATOM_TO_STRING(pn2->pn_atom));
1532                     JSAutoByteString bytes;
1533                     if (js_ValueToPrintable(cx, v, &bytes)) {
1534                         ReportCompileErrorNumber(cx, &parser->tokenStream, pn2,
1535                                                  JSREPORT_ERROR, JSMSG_DUPLICATE_XML_ATTR,
1536                                                  bytes.ptr());
1537                     }
1538                     goto fail;
1539                 }
1540             }
1541
1542             pn2 = pn2->pn_next;
1543             JS_ASSERT(pn2);
1544             JS_ASSERT(pn2->pn_type == TOK_XMLATTR);
1545
1546             attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
1547             if (!attr)
1548                 goto fail;
1549
1550             XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
1551             attr->parent = xml;
1552             attr->name = qn;
1553             attr->xml_value = ATOM_TO_STRING(pn2->pn_atom);
1554         }
1555
1556         /* Point tag closes its own namespace scope. */
1557         if (pn->pn_type == TOK_XMLPTAGC)
1558             XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1559         break;
1560
1561       case TOK_XMLSPACE:
1562       case TOK_XMLTEXT:
1563       case TOK_XMLCDATA:
1564       case TOK_XMLCOMMENT:
1565       case TOK_XMLPI:
1566         str = ATOM_TO_STRING(pn->pn_atom);
1567         qn = NULL;
1568         if (pn->pn_type == TOK_XMLCOMMENT) {
1569             if (flags & XSF_IGNORE_COMMENTS)
1570                 goto skip_child;
1571             xml_class = JSXML_CLASS_COMMENT;
1572         } else if (pn->pn_type == TOK_XMLPI) {
1573             if (IS_XML(str)) {
1574                 Value v = StringValue(str);
1575                 JSAutoByteString bytes;
1576                 if (js_ValueToPrintable(cx, v, &bytes)) { 
1577                     ReportCompileErrorNumber(cx, &parser->tokenStream, pn,
1578                                              JSREPORT_ERROR, JSMSG_RESERVED_ID, bytes.ptr());
1579                 }
1580                 goto fail;
1581             }
1582
1583             if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
1584                 goto skip_child;
1585
1586             qn = ParseNodeToQName(parser, pn, inScopeNSes, JS_FALSE);
1587             if (!qn)
1588                 goto fail;
1589
1590             str = pn->pn_atom2
1591                   ? ATOM_TO_STRING(pn->pn_atom2)
1592                   : cx->runtime->emptyString;
1593             xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
1594         } else {
1595             /* CDATA section content, or element text. */
1596             xml_class = JSXML_CLASS_TEXT;
1597         }
1598
1599         xml = js_NewXML(cx, xml_class);
1600         if (!xml)
1601             goto fail;
1602         xml->name = qn;
1603         if (pn->pn_type == TOK_XMLSPACE)
1604             xml->xml_flags |= XMLF_WHITESPACE_TEXT;
1605         xml->xml_value = str;
1606         break;
1607
1608       default:
1609         goto syntax;
1610     }
1611
1612     js_LeaveLocalRootScopeWithResult(cx, xml);
1613     return xml;
1614
1615 skip_child:
1616     js_LeaveLocalRootScope(cx);
1617     return PN2X_SKIP_CHILD;
1618
1619 #undef PN2X_SKIP_CHILD
1620
1621 syntax:
1622     ReportCompileErrorNumber(cx, &parser->tokenStream, pn, JSREPORT_ERROR, JSMSG_BAD_XML_MARKUP);
1623 fail:
1624     js_LeaveLocalRootScope(cx);
1625     return NULL;
1626 }
1627
1628 /*
1629  * XML helper, object-ops, and library functions.  We start with the helpers,
1630  * in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
1631  */
1632 static JSBool
1633 GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
1634 {
1635     jsval v;
1636
1637     if (!js_FindClassObject(cx, NULL, JSProto_XML, Valueify(&v)))
1638         return JS_FALSE;
1639     if (!VALUE_IS_FUNCTION(cx, v)) {
1640         *vp = JSVAL_VOID;
1641         return JS_TRUE;
1642     }
1643     return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
1644 }
1645
1646 static JSBool
1647 GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
1648 {
1649     jsval v;
1650
1651     return GetXMLSetting(cx, name, &v) && JS_ValueToBoolean(cx, v, bp);
1652 }
1653
1654 static JSBool
1655 GetUint32XMLSetting(JSContext *cx, const char *name, uint32 *uip)
1656 {
1657     jsval v;
1658
1659     return GetXMLSetting(cx, name, &v) && JS_ValueToECMAUint32(cx, v, uip);
1660 }
1661
1662 static JSBool
1663 GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
1664 {
1665     JSBool flag[4];
1666
1667     if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag[0]) ||
1668         !GetBooleanXMLSetting(cx, js_ignoreProcessingInstructions_str, &flag[1]) ||
1669         !GetBooleanXMLSetting(cx, js_ignoreWhitespace_str, &flag[2]) ||
1670         !GetBooleanXMLSetting(cx, js_prettyPrinting_str, &flag[3])) {
1671         return false;
1672     }
1673
1674     *flagsp = 0;
1675     for (size_t n = 0; n < 4; ++n)
1676         if (flag[n])
1677             *flagsp |= JS_BIT(n);
1678     return true;
1679 }
1680
1681 static JSXML *
1682 ParseXMLSource(JSContext *cx, JSString *src)
1683 {
1684     jsval nsval;
1685     JSLinearString *uri;
1686     size_t urilen, srclen, length, offset, dstlen;
1687     jschar *chars;
1688     const jschar *srcp, *endp;
1689     JSXML *xml;
1690     const char *filename;
1691     uintN lineno;
1692     JSOp op;
1693
1694     static const char prefix[] = "<parent xmlns=\"";
1695     static const char middle[] = "\">";
1696     static const char suffix[] = "</parent>";
1697
1698 #define constrlen(constr)   (sizeof(constr) - 1)
1699
1700     if (!js_GetDefaultXMLNamespace(cx, &nsval))
1701         return NULL;
1702     uri = JSVAL_TO_OBJECT(nsval)->getNameURI();
1703     uri = js_EscapeAttributeValue(cx, uri, JS_FALSE);
1704     if (!uri)
1705         return NULL;
1706
1707     urilen = uri->length();
1708     srclen = src->length();
1709     length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
1710              constrlen(suffix);
1711
1712     chars = (jschar *) cx->malloc((length + 1) * sizeof(jschar));
1713     if (!chars)
1714         return NULL;
1715
1716     dstlen = length;
1717     js_InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
1718     offset = dstlen;
1719     js_strncpy(chars + offset, uri->chars(), urilen);
1720     offset += urilen;
1721     dstlen = length - offset + 1;
1722     js_InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset,
1723                              &dstlen);
1724     offset += dstlen;
1725     srcp = src->getChars(cx);
1726     if (!srcp) {
1727         cx->free(chars);
1728         return NULL;
1729     }
1730     js_strncpy(chars + offset, srcp, srclen);
1731     offset += srclen;
1732     dstlen = length - offset + 1;
1733     js_InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset,
1734                              &dstlen);
1735     chars [offset + dstlen] = 0;
1736
1737     LeaveTrace(cx);
1738     xml = NULL;
1739     FrameRegsIter i(cx);
1740     for (; !i.done() && !i.pc(); ++i)
1741         JS_ASSERT(!i.fp()->isScriptFrame());
1742     filename = NULL;
1743     lineno = 1;
1744     if (!i.done()) {
1745         JSStackFrame *fp = i.fp();
1746         op = (JSOp) *i.pc();
1747         if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
1748             filename = fp->script()->filename;
1749             lineno = js_FramePCToLineNumber(cx, fp);
1750             for (endp = srcp + srclen; srcp < endp; srcp++) {
1751                 if (*srcp == '\n')
1752                     --lineno;
1753             }
1754         }
1755     }
1756
1757     {
1758         Parser parser(cx);
1759         if (parser.init(chars, length, filename, lineno, cx->findVersion())) {
1760             JSObject *scopeChain = GetScopeChain(cx);
1761             if (!scopeChain) {
1762                 cx->free(chars);
1763                 return NULL;
1764             }
1765             JSParseNode *pn = parser.parseXMLText(scopeChain, false);
1766             uintN flags;
1767             if (pn && GetXMLSettingFlags(cx, &flags)) {
1768                 AutoNamespaceArray namespaces(cx);
1769                 if (namespaces.array.setCapacity(cx, 1))
1770                     xml = ParseNodeToXML(&parser, pn, &namespaces.array, flags);
1771             }
1772         }
1773     }
1774
1775     cx->free(chars);
1776     return xml;
1777
1778 #undef constrlen
1779 }
1780
1781 /*
1782  * Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
1783  *
1784  * 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
1785  * the constraint:
1786  *
1787  *     for all x belonging to XML:
1788  *         x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
1789  *
1790  * must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
1791  * (in new sub-step 6(a), renumbering the others to (b) and (c)).
1792  *
1793  * Same goes for 10.4.1 Step 7(a).
1794  *
1795  * In order for XML.prototype.namespaceDeclarations() to work correctly, the
1796  * default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
1797  * flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
1798  * undeclared namespaces associated with x not belonging to ancestorNS.
1799  */
1800 static JSXML *
1801 OrphanXMLChild(JSContext *cx, JSXML *xml, uint32 i)
1802 {
1803     JSObject *ns;
1804
1805     ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSObject);
1806     xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
1807     if (!ns || !xml)
1808         return xml;
1809     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
1810         if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1811             return NULL;
1812         ns->setNamespaceDeclared(JSVAL_VOID);
1813     }
1814     xml->parent = NULL;
1815     return xml;
1816 }
1817
1818 static JSObject *
1819 ToXML(JSContext *cx, jsval v)
1820 {
1821     JSObject *obj;
1822     JSXML *xml;
1823     Class *clasp;
1824     JSString *str;
1825     uint32 length;
1826
1827     if (JSVAL_IS_PRIMITIVE(v)) {
1828         if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
1829             goto bad;
1830     } else {
1831         obj = JSVAL_TO_OBJECT(v);
1832         if (obj->isXML()) {
1833             xml = (JSXML *) obj->getPrivate();
1834             if (xml->xml_class == JSXML_CLASS_LIST) {
1835                 if (xml->xml_kids.length != 1)
1836                     goto bad;
1837                 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
1838                 if (xml) {
1839                     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
1840                     return js_GetXMLObject(cx, xml);
1841                 }
1842             }
1843             return obj;
1844         }
1845
1846         clasp = obj->getClass();
1847         if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
1848             JS_ASSERT(0);
1849         }
1850
1851         if (clasp != &js_StringClass &&
1852             clasp != &js_NumberClass &&
1853             clasp != &js_BooleanClass) {
1854             goto bad;
1855         }
1856     }
1857
1858     str = js_ValueToString(cx, Valueify(v));
1859     if (!str)
1860         return NULL;
1861     if (str->empty()) {
1862         length = 0;
1863 #ifdef __GNUC__         /* suppress bogus gcc warnings */
1864         xml = NULL;
1865 #endif
1866     } else {
1867         xml = ParseXMLSource(cx, str);
1868         if (!xml)
1869             return NULL;
1870         length = JSXML_LENGTH(xml);
1871     }
1872
1873     if (length == 0) {
1874         obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
1875         if (!obj)
1876             return NULL;
1877     } else if (length == 1) {
1878         xml = OrphanXMLChild(cx, xml, 0);
1879         if (!xml)
1880             return NULL;
1881         obj = js_GetXMLObject(cx, xml);
1882         if (!obj)
1883             return NULL;
1884     } else {
1885         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
1886         return NULL;
1887     }
1888     return obj;
1889
1890 bad:
1891     js_ReportValueError(cx, JSMSG_BAD_XML_CONVERSION,
1892                         JSDVG_IGNORE_STACK, Valueify(v), NULL);
1893     return NULL;
1894 }
1895
1896 static JSBool
1897 Append(JSContext *cx, JSXML *list, JSXML *kid);
1898
1899 static JSObject *
1900 ToXMLList(JSContext *cx, jsval v)
1901 {
1902     JSObject *obj, *listobj;
1903     JSXML *xml, *list, *kid;
1904     Class *clasp;
1905     JSString *str;
1906     uint32 i, length;
1907
1908     if (JSVAL_IS_PRIMITIVE(v)) {
1909         if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
1910             goto bad;
1911     } else {
1912         obj = JSVAL_TO_OBJECT(v);
1913         if (obj->isXML()) {
1914             xml = (JSXML *) obj->getPrivate();
1915             if (xml->xml_class != JSXML_CLASS_LIST) {
1916                 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
1917                 if (!listobj)
1918                     return NULL;
1919                 list = (JSXML *) listobj->getPrivate();
1920                 if (!Append(cx, list, xml))
1921                     return NULL;
1922                 return listobj;
1923             }
1924             return obj;
1925         }
1926
1927         clasp = obj->getClass();
1928         if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
1929             JS_ASSERT(0);
1930         }
1931
1932         if (clasp != &js_StringClass &&
1933             clasp != &js_NumberClass &&
1934             clasp != &js_BooleanClass) {
1935             goto bad;
1936         }
1937     }
1938
1939     str = js_ValueToString(cx, Valueify(v));
1940     if (!str)
1941         return NULL;
1942     if (str->empty()) {
1943         xml = NULL;
1944         length = 0;
1945     } else {
1946         if (!js_EnterLocalRootScope(cx))
1947             return NULL;
1948         xml = ParseXMLSource(cx, str);
1949         if (!xml) {
1950             js_LeaveLocalRootScope(cx);
1951             return NULL;
1952         }
1953         length = JSXML_LENGTH(xml);
1954     }
1955
1956     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
1957     if (listobj) {
1958         list = (JSXML *) listobj->getPrivate();
1959         for (i = 0; i < length; i++) {
1960             kid = OrphanXMLChild(cx, xml, i);
1961             if (!kid || !Append(cx, list, kid)) {
1962                 listobj = NULL;
1963                 break;
1964             }
1965         }
1966     }
1967
1968     if (xml)
1969         js_LeaveLocalRootScopeWithResult(cx, listobj);
1970     return listobj;
1971
1972 bad:
1973     js_ReportValueError(cx, JSMSG_BAD_XMLLIST_CONVERSION,
1974                         JSDVG_IGNORE_STACK, Valueify(v), NULL);
1975     return NULL;
1976 }
1977
1978 /*
1979  * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
1980  * and their library-public js_* counterparts.  The guts of MakeXMLCDataString,
1981  * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
1982  * MakeXMLSpecialString subroutine.
1983  *
1984  * These functions mutate sb, leaving it empty.
1985  */
1986 static JSFlatString *
1987 MakeXMLSpecialString(JSContext *cx, StringBuffer &sb,
1988                      JSString *str, JSString *str2,
1989                      const jschar *prefix, size_t prefixlength,
1990                      const jschar *suffix, size_t suffixlength)
1991 {
1992     if (!sb.append(prefix, prefixlength) || !sb.append(str))
1993         return NULL;
1994     if (str2 && !str2->empty()) {
1995         if (!sb.append(' ') || !sb.append(str2))
1996             return NULL;
1997     }
1998     if (!sb.append(suffix, suffixlength))
1999         return NULL;
2000
2001     return sb.finishString();
2002 }
2003
2004 static JSFlatString *
2005 MakeXMLCDATAString(JSContext *cx, StringBuffer &sb, JSString *str)
2006 {
2007     static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
2008                                                  'C', 'D', 'A', 'T', 'A',
2009                                                  '['};
2010     static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
2011
2012     return MakeXMLSpecialString(cx, sb, str, NULL,
2013                                 cdata_prefix_ucNstr, 9,
2014                                 cdata_suffix_ucNstr, 3);
2015 }
2016
2017 static JSFlatString *
2018 MakeXMLCommentString(JSContext *cx, StringBuffer &sb, JSString *str)
2019 {
2020     static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
2021     static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
2022
2023     return MakeXMLSpecialString(cx, sb, str, NULL,
2024                                 comment_prefix_ucNstr, 4,
2025                                 comment_suffix_ucNstr, 3);
2026 }
2027
2028 static JSFlatString *
2029 MakeXMLPIString(JSContext *cx, StringBuffer &sb, JSString *name,
2030                 JSString *value)
2031 {
2032     static const jschar pi_prefix_ucNstr[] = {'<', '?'};
2033     static const jschar pi_suffix_ucNstr[] = {'?', '>'};
2034
2035     return MakeXMLSpecialString(cx, sb, name, value,
2036                                 pi_prefix_ucNstr, 2,
2037                                 pi_suffix_ucNstr, 2);
2038 }
2039
2040 /*
2041  * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
2042  * equals, a double quote, an attribute value, and a closing double quote.
2043  */
2044 static bool
2045 AppendAttributeValue(JSContext *cx, StringBuffer &sb, JSString *valstr)
2046 {
2047     if (!sb.append('='))
2048         return false;
2049     valstr = js_EscapeAttributeValue(cx, valstr, JS_TRUE);
2050     return valstr && sb.append(valstr);
2051 }
2052
2053 /*
2054  * ECMA-357 10.2.1.1 EscapeElementValue helper method.
2055
2056  * These functions mutate sb, leaving it empty.
2057  */
2058 static JSFlatString *
2059 EscapeElementValue(JSContext *cx, StringBuffer &sb, JSString *str, uint32 toSourceFlag)
2060 {
2061     size_t length = str->length();
2062     const jschar *start = str->getChars(cx);
2063     if (!start)
2064         return NULL;
2065
2066     for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
2067         jschar c = *cp;
2068         switch (*cp) {
2069           case '<':
2070             if (!sb.append(js_lt_entity_str))
2071                 return NULL;
2072             break;
2073           case '>':
2074             if (!sb.append(js_gt_entity_str))
2075                 return NULL;
2076             break;
2077           case '&':
2078             if (!sb.append(js_amp_entity_str))
2079                 return NULL;
2080             break;
2081           case '{':
2082             /*
2083              * If EscapeElementValue is called by toSource/uneval, we also need
2084              * to escape '{'. See bug 463360.
2085              */
2086             if (toSourceFlag) {
2087                 if (!sb.append(js_leftcurly_entity_str))
2088                     return NULL;
2089                 break;
2090             }
2091             /* FALL THROUGH */
2092           default:
2093             if (!sb.append(c))
2094                 return NULL;
2095         }
2096     }
2097     return sb.finishString();
2098 }
2099
2100 /*
2101  * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
2102  *
2103  * These functions mutate sb, leaving it empty.
2104  */
2105 static JSFlatString *
2106 EscapeAttributeValue(JSContext *cx, StringBuffer &sb, JSString *str, JSBool quote)
2107 {
2108     size_t length = str->length();
2109     const jschar *start = str->getChars(cx);
2110     if (!start)
2111         return NULL;
2112
2113     if (quote && !sb.append('"'))
2114         return NULL;
2115
2116     for (const jschar *cp = start, *end = start + length; cp != end; ++cp) {
2117         jschar c = *cp;
2118         switch (c) {
2119           case '"':
2120             if (!sb.append(js_quot_entity_str))
2121                 return NULL;
2122             break;
2123           case '<':
2124             if (!sb.append(js_lt_entity_str))
2125                 return NULL;
2126             break;
2127           case '&':
2128             if (!sb.append(js_amp_entity_str))
2129                 return NULL;
2130             break;
2131           case '\n':
2132             if (!sb.append("&#xA;"))
2133                 return NULL;
2134             break;
2135           case '\r':
2136             if (!sb.append("&#xD;"))
2137                 return NULL;
2138             break;
2139           case '\t':
2140             if (!sb.append("&#x9;"))
2141                 return NULL;
2142             break;
2143           default:
2144             if (!sb.append(c))
2145                 return NULL;
2146         }
2147     }
2148
2149     if (quote && !sb.append('"'))
2150         return NULL;
2151
2152     return sb.finishString();
2153 }
2154
2155 /* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
2156 static JSObject *
2157 GetNamespace(JSContext *cx, JSObject *qn, const JSXMLArray *inScopeNSes)
2158 {
2159     JSLinearString *uri, *prefix, *nsprefix;
2160     JSObject *match, *ns;
2161     uint32 i, n;
2162     jsval argv[2];
2163
2164     uri = qn->getNameURI();
2165     prefix = qn->getNamePrefix();
2166     JS_ASSERT(uri);
2167     if (!uri) {
2168         JSAutoByteString bytes;
2169         const char *s = !prefix ?
2170                         js_undefined_str
2171                         : js_ValueToPrintable(cx, StringValue(prefix), &bytes);
2172         if (s)
2173             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XML_NAMESPACE, s);
2174         return NULL;
2175     }
2176
2177     /* Look for a matching namespace in inScopeNSes, if provided. */
2178     match = NULL;
2179     if (inScopeNSes) {
2180         for (i = 0, n = inScopeNSes->length; i < n; i++) {
2181             ns = XMLARRAY_MEMBER(inScopeNSes, i, JSObject);
2182             if (!ns)
2183                 continue;
2184
2185             /*
2186              * Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
2187              * If we preserve prefixes, we must match null prefix against
2188              * an empty prefix of ns, in order to avoid generating redundant
2189              * prefixed and default namespaces for cases such as:
2190              *
2191              *   x = <t xmlns="http://foo.com"/>
2192              *   print(x.toXMLString());
2193              *
2194              * Per 10.3.2.1, the namespace attribute in t has an empty string
2195              * prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
2196              *
2197              *   1. If the [local name] property of a is "xmlns"
2198              *      a. Map ns.prefix to the empty string
2199              *
2200              * But t's name has a null prefix in this implementation, meaning
2201              * *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
2202              * the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
2203              * saying how "no value" maps to an ECMA-357 value -- but it must
2204              * map to the *undefined* prefix value).
2205              *
2206              * Since "" != undefined (or null, in the current implementation)
2207              * the ECMA-357 spec will fail to match in [[GetNamespace]] called
2208              * on t with argument {} U {(prefix="", uri="http://foo.com")}.
2209              * This spec bug leads to ToXMLString results that duplicate the
2210              * declared namespace.
2211              */
2212             if (EqualStrings(ns->getNameURI(), uri)) {
2213                 nsprefix = ns->getNamePrefix();
2214                 if (nsprefix == prefix ||
2215                     ((nsprefix && prefix)
2216                      ? EqualStrings(nsprefix, prefix)
2217                      : (nsprefix ? nsprefix : prefix)->empty())) {
2218                     match = ns;
2219                     break;
2220                 }
2221             }
2222         }
2223     }
2224
2225     /* If we didn't match, make a new namespace from qn. */
2226     if (!match) {
2227         argv[0] = prefix ? STRING_TO_JSVAL(prefix) : JSVAL_VOID;
2228         argv[1] = STRING_TO_JSVAL(uri);
2229         ns = js_ConstructObject(cx, &js_NamespaceClass, NULL, NULL,
2230                                 2, Valueify(argv));
2231         if (!ns)
2232             return NULL;
2233         match = ns;
2234     }
2235     return match;
2236 }
2237
2238 static JSLinearString *
2239 GeneratePrefix(JSContext *cx, JSLinearString *uri, JSXMLArray *decls)
2240 {
2241     const jschar *cp, *start, *end;
2242     size_t length, newlength, offset;
2243     uint32 i, n, m, serial;
2244     jschar *bp, *dp;
2245     JSBool done;
2246     JSObject *ns;
2247     JSLinearString *nsprefix, *prefix;
2248
2249     JS_ASSERT(!uri->empty());
2250
2251     /*
2252      * If there are no *declared* namespaces, skip all collision detection and
2253      * return a short prefix quickly; an example of such a situation:
2254      *
2255      *   var x = <f/>;
2256      *   var n = new Namespace("http://example.com/");
2257      *   x.@n::att = "val";
2258      *   x.toXMLString();
2259      *
2260      * This is necessary for various log10 uses below to be valid.
2261      */
2262     if (decls->length == 0)
2263         return js_NewStringCopyZ(cx, "a");
2264
2265     /*
2266      * Try peeling off the last filename suffix or pathname component till
2267      * we have a valid XML name.  This heuristic will prefer "xul" given
2268      * ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
2269      * likely URI of the form ".../xbl2/2005".
2270      */
2271     start = uri->chars();
2272     end = start + uri->length();
2273     cp = end;
2274     while (--cp > start) {
2275         if (*cp == '.' || *cp == '/' || *cp == ':') {
2276             ++cp;
2277             length = end - cp;
2278             if (IsXMLName(cp, length) && !STARTS_WITH_XML(cp, length))
2279                 break;
2280             end = --cp;
2281         }
2282     }
2283     length = end - cp;
2284
2285     /*
2286      * If the namespace consisted only of non-XML names or names that begin
2287      * case-insensitively with "xml", arbitrarily create a prefix consisting
2288      * of 'a's of size length (allowing dp-calculating code to work with or
2289      * without this branch executing) plus the space for storing a hyphen and
2290      * the serial number (avoiding reallocation if a collision happens).
2291      */
2292     bp = (jschar *) cp;
2293     newlength = length;
2294     if (STARTS_WITH_XML(cp, length) || !IsXMLName(cp, length)) {
2295         newlength = length + 2 + (size_t) log10((double) decls->length);
2296         bp = (jschar *)
2297              cx->malloc((newlength + 1) * sizeof(jschar));
2298         if (!bp)
2299             return NULL;
2300
2301         bp[newlength] = 0;
2302         for (i = 0; i < newlength; i++)
2303              bp[i] = 'a';
2304     }
2305
2306     /*
2307      * Now search through decls looking for a collision.  If we collide with
2308      * an existing prefix, start tacking on a hyphen and a serial number.
2309      */
2310     serial = 0;
2311     do {
2312         done = JS_TRUE;
2313         for (i = 0, n = decls->length; i < n; i++) {
2314             ns = XMLARRAY_MEMBER(decls, i, JSObject);
2315             if (ns && (nsprefix = ns->getNamePrefix()) &&
2316                 nsprefix->length() == newlength &&
2317                 !memcmp(nsprefix->chars(), bp,
2318                         newlength * sizeof(jschar))) {
2319                 if (bp == cp) {
2320                     newlength = length + 2 + (size_t) log10((double) n);
2321                     bp = (jschar *)
2322                          cx->malloc((newlength + 1) * sizeof(jschar));
2323                     if (!bp)
2324                         return NULL;
2325                     js_strncpy(bp, cp, length);
2326                 }
2327
2328                 ++serial;
2329                 JS_ASSERT(serial <= n);
2330                 dp = bp + length + 2 + (size_t) log10((double) serial);
2331                 *dp = 0;
2332                 for (m = serial; m != 0; m /= 10)
2333                     *--dp = (jschar)('0' + m % 10);
2334                 *--dp = '-';
2335                 JS_ASSERT(dp == bp + length);
2336
2337                 done = JS_FALSE;
2338                 break;
2339             }
2340         }
2341     } while (!done);
2342
2343     if (bp == cp) {
2344         offset = cp - start;
2345         prefix = js_NewDependentString(cx, uri, offset, length);
2346     } else {
2347         prefix = js_NewString(cx, bp, newlength);
2348         if (!prefix)
2349             cx->free(bp);
2350     }
2351     return prefix;
2352 }
2353
2354 static JSBool
2355 namespace_match(const void *a, const void *b)
2356 {
2357     const JSObject *nsa = (const JSObject *) a;
2358     const JSObject *nsb = (const JSObject *) b;
2359     JSLinearString *prefixa, *prefixb = nsb->getNamePrefix();
2360
2361     if (prefixb) {
2362         prefixa = nsa->getNamePrefix();
2363         return prefixa && EqualStrings(prefixa, prefixb);
2364     }
2365     return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
2366 }
2367
2368 /* ECMA-357 10.2.1 and 10.2.2 */
2369 #define TO_SOURCE_FLAG 0x80000000
2370
2371 static JSString *
2372 XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray *ancestorNSes,
2373                uint32 indentLevel)
2374 {
2375     JSBool pretty, indentKids;
2376     StringBuffer sb(cx);
2377     JSString *str;
2378     JSLinearString *prefix, *nsuri;
2379     uint32 i, n, nextIndentLevel;
2380     JSObject *ns, *ns2;
2381     AutoNamespaceArray empty(cx), decls(cx), ancdecls(cx);
2382
2383     if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
2384         return NULL;
2385
2386     if (pretty) {
2387         if (!sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
2388             return NULL;
2389     }
2390
2391     str = NULL;
2392
2393     switch (xml->xml_class) {
2394       case JSXML_CLASS_TEXT:
2395         /* Step 4. */
2396         if (pretty) {
2397             str = ChompXMLWhitespace(cx, xml->xml_value);
2398             if (!str)
2399                 return NULL;
2400         } else {
2401             str = xml->xml_value;
2402         }
2403         return EscapeElementValue(cx, sb, str, indentLevel & TO_SOURCE_FLAG);
2404
2405       case JSXML_CLASS_ATTRIBUTE:
2406         /* Step 5. */
2407         return EscapeAttributeValue(cx, sb, xml->xml_value,
2408                                     (indentLevel & TO_SOURCE_FLAG) != 0);
2409
2410       case JSXML_CLASS_COMMENT:
2411         /* Step 6. */
2412         return MakeXMLCommentString(cx, sb, xml->xml_value);
2413
2414       case JSXML_CLASS_PROCESSING_INSTRUCTION:
2415         /* Step 7. */
2416         return MakeXMLPIString(cx, sb, xml->name->getQNameLocalName(),
2417                                xml->xml_value);
2418
2419       case JSXML_CLASS_LIST:
2420         /* ECMA-357 10.2.2. */
2421         {
2422             JSXMLArrayCursor cursor(&xml->xml_kids);
2423             i = 0;
2424             while (JSXML *kid = (JSXML *) cursor.getNext()) {
2425                 if (pretty && i != 0) {
2426                     if (!sb.append('\n'))
2427                         return NULL;
2428                 }
2429
2430                 JSString *kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
2431                 if (!kidstr || !sb.append(kidstr))
2432                     return NULL;
2433                 ++i;
2434             }
2435         }
2436
2437         if (sb.empty())
2438             return cx->runtime->emptyString;
2439         return sb.finishString();
2440
2441       default:;
2442     }
2443
2444     /* After this point, control must flow through label out: to exit. */
2445     if (!js_EnterLocalRootScope(cx))
2446         return NULL;
2447
2448     /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
2449     if (!ancestorNSes)
2450         ancestorNSes = &empty.array;
2451
2452     /* Clone in-scope namespaces not in ancestorNSes into decls. */
2453     {
2454         JSXMLArrayCursor cursor(&xml->xml_namespaces);
2455         while ((ns = (JSObject *) cursor.getNext()) != NULL) {
2456             if (!IsDeclared(ns))
2457                 continue;
2458             if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
2459                 /* NOTE: may want to exclude unused namespaces here. */
2460                 ns2 = NewXMLNamespace(cx, ns->getNamePrefix(), ns->getNameURI(), JS_TRUE);
2461                 if (!ns2 || !XMLARRAY_APPEND(cx, &decls.array, ns2))
2462                     goto out;
2463             }
2464         }
2465     }
2466
2467     /*
2468      * Union ancestorNSes and decls into ancdecls.  Note that ancdecls does
2469      * not own its member references.  In the spec, ancdecls has no name, but
2470      * is always written out as (AncestorNamespaces U namespaceDeclarations).
2471      */
2472
2473     if (!ancdecls.array.setCapacity(cx, ancestorNSes->length + decls.length()))
2474         goto out;
2475     for (i = 0, n = ancestorNSes->length; i < n; i++) {
2476         ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSObject);
2477         if (!ns2)
2478             continue;
2479         JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls.array, ns2, namespace_identity));
2480         if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2))
2481             goto out;
2482     }
2483     for (i = 0, n = decls.length(); i < n; i++) {
2484         ns2 = XMLARRAY_MEMBER(&decls.array, i, JSObject);
2485         if (!ns2)
2486             continue;
2487         JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls.array, ns2, namespace_identity));
2488         if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2))
2489             goto out;
2490     }
2491
2492     /* Step 11, except we don't clone ns unless its prefix is undefined. */
2493     ns = GetNamespace(cx, xml->name, &ancdecls.array);
2494     if (!ns)
2495         goto out;
2496
2497     /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
2498     prefix = ns->getNamePrefix();
2499     if (!prefix) {
2500         /*
2501          * Create a namespace prefix that isn't used by any member of decls.
2502          * Assign the new prefix to a copy of ns.  Flag this namespace as if
2503          * it were declared, for assertion-testing's sake later below.
2504          *
2505          * Erratum: if prefix and xml->name are both null (*undefined* in
2506          * ECMA-357), we know that xml was named using the default namespace
2507          * (proof: see GetNamespace and the Namespace constructor called with
2508          * two arguments).  So we ought not generate a new prefix here, when
2509          * we can declare ns as the default namespace for xml.
2510          *
2511          * This helps descendants inherit the namespace instead of redundantly
2512          * redeclaring it with generated prefixes in each descendant.
2513          */
2514         nsuri = ns->getNameURI();
2515         if (!xml->name->getNamePrefix()) {
2516             prefix = cx->runtime->emptyString;
2517         } else {
2518             prefix = GeneratePrefix(cx, nsuri, &ancdecls.array);
2519             if (!prefix)
2520                 goto out;
2521         }
2522         ns = NewXMLNamespace(cx, prefix, nsuri, JS_TRUE);
2523         if (!ns)
2524             goto out;
2525
2526         /*
2527          * If the xml->name was unprefixed, we must remove any declared default
2528          * namespace from decls before appending ns.  How can you get a default
2529          * namespace in decls that doesn't match the one from name?  Apparently
2530          * by calling x.setNamespace(ns) where ns has no prefix.  The other way
2531          * to fix this is to update x's in-scope namespaces when setNamespace
2532          * is called, but that's not specified by ECMA-357.
2533          *
2534          * Likely Erratum here, depending on whether the lack of update to x's
2535          * in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
2536          * erratum or not.  Note that changing setNamespace to update the list
2537          * of in-scope namespaces will change x.namespaceDeclarations().
2538          */
2539         if (prefix->empty()) {
2540             i = XMLArrayFindMember(&decls.array, ns, namespace_match);
2541             if (i != XML_NOT_FOUND)
2542                 XMLArrayDelete(cx, &decls.array, i, JS_TRUE);
2543         }
2544
2545         /*
2546          * In the spec, ancdecls has no name, but is always written out as
2547          * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2548          * that union in ancdecls, any time we append a namespace strong
2549          * ref to decls, we must also append a weak ref to ancdecls.  Order
2550          * matters here: code at label out: releases strong refs in decls.
2551          */
2552         if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns) ||
2553             !XMLARRAY_APPEND(cx, &decls.array, ns)) {
2554             goto out;
2555         }
2556     }
2557
2558     /* Format the element or point-tag into sb. */
2559     if (!sb.append('<'))
2560         goto out;
2561
2562     if (!prefix->empty()) {
2563         if (!sb.append(prefix) || !sb.append(':'))
2564             goto out;
2565     }
2566     if (!sb.append(xml->name->getQNameLocalName()))
2567         goto out;
2568
2569     /*
2570      * Step 16 makes a union to avoid writing two loops in step 17, to share
2571      * common attribute value appending spec-code.  We prefer two loops for
2572      * faster code and less data overhead.
2573      */
2574
2575     /* Step 17(b): append attributes. */
2576     {
2577         JSXMLArrayCursor cursor(&xml->xml_attrs);
2578         while (JSXML *attr = (JSXML *) cursor.getNext()) {
2579             if (!sb.append(' '))
2580                 goto out;
2581             ns2 = GetNamespace(cx, attr->name, &ancdecls.array);
2582             if (!ns2)
2583                 goto out;
2584
2585             /* 17(b)(ii): NULL means *undefined* here. */
2586             prefix = ns2->getNamePrefix();
2587             if (!prefix) {
2588                 prefix = GeneratePrefix(cx, ns2->getNameURI(), &ancdecls.array);
2589                 if (!prefix)
2590                     goto out;
2591
2592                 /* Again, we avoid copying ns2 until we know it's prefix-less. */
2593                 ns2 = NewXMLNamespace(cx, prefix, ns2->getNameURI(), JS_TRUE);
2594                 if (!ns2)
2595                     goto out;
2596
2597                 /*
2598                  * In the spec, ancdecls has no name, but is always written out as
2599                  * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2600                  * that union in ancdecls, any time we append a namespace strong
2601                  * ref to decls, we must also append a weak ref to ancdecls.  Order
2602                  * matters here: code at label out: releases strong refs in decls.
2603                  */
2604                 if (!XMLARRAY_APPEND(cx, &ancdecls.array, ns2) ||
2605                     !XMLARRAY_APPEND(cx, &decls.array, ns2)) {
2606                     goto out;
2607                 }
2608             }
2609
2610             /* 17(b)(iii). */
2611             if (!prefix->empty()) {
2612                 if (!sb.append(prefix) || !sb.append(':'))
2613                     goto out;
2614             }
2615
2616             /* 17(b)(iv). */
2617             if (!sb.append(attr->name->getQNameLocalName()))
2618                 goto out;
2619
2620             /* 17(d-g). */
2621             if (!AppendAttributeValue(cx, sb, attr->xml_value))
2622                 goto out;
2623         }
2624     }
2625
2626     /* Step 17(c): append XML namespace declarations. */
2627     {
2628         JSXMLArrayCursor cursor(&decls.array);
2629         while (JSObject *ns3 = (JSObject *) cursor.getNext()) {
2630             JS_ASSERT(IsDeclared(ns3));
2631
2632             if (!sb.append(" xmlns"))
2633                 goto out;
2634
2635             /* 17(c)(ii): NULL means *undefined* here. */
2636             prefix = ns3->getNamePrefix();
2637             if (!prefix) {
2638                 prefix = GeneratePrefix(cx, ns3->getNameURI(), &ancdecls.array);
2639                 if (!prefix)
2640                     goto out;
2641                 ns3->setNamePrefix(prefix);
2642             }
2643
2644             /* 17(c)(iii). */
2645             if (!prefix->empty()) {
2646                 if (!sb.append(':') || !sb.append(prefix))
2647                     goto out;
2648             }
2649
2650             /* 17(d-g). */
2651             if (!AppendAttributeValue(cx, sb, ns3->getNameURI()))
2652                 goto out;
2653         }
2654     }
2655
2656     /* Step 18: handle point tags. */
2657     n = xml->xml_kids.length;
2658     if (n == 0) {
2659         if (!sb.append("/>"))
2660             goto out;
2661     } else {
2662         /* Steps 19 through 25: handle element content, and open the end-tag. */
2663         if (!sb.append('>'))
2664             goto out;
2665         {
2666             JSXML *kid;
2667             indentKids = n > 1 ||
2668                          (n == 1 &&
2669                           (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
2670                           kid->xml_class != JSXML_CLASS_TEXT);
2671         }
2672
2673         if (pretty && indentKids) {
2674             if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
2675                 goto out;
2676             nextIndentLevel = indentLevel + i;
2677         } else {
2678             nextIndentLevel = indentLevel & TO_SOURCE_FLAG;
2679         }
2680
2681         {
2682             JSXMLArrayCursor cursor(&xml->xml_kids);
2683             while (JSXML *kid = (JSXML *) cursor.getNext()) {
2684                 if (pretty && indentKids) {
2685                     if (!sb.append('\n'))
2686                         goto out;
2687                 }
2688
2689                 JSString *kidstr = XMLToXMLString(cx, kid, &ancdecls.array, nextIndentLevel);
2690                 if (!kidstr)
2691                     goto out;
2692
2693                 if (!sb.append(kidstr))
2694                     goto out;
2695             }
2696         }
2697
2698         if (pretty && indentKids) {
2699             if (!sb.append('\n') ||
2700                 !sb.appendN(' ', indentLevel & ~TO_SOURCE_FLAG))
2701                 goto out;
2702         }
2703         if (!sb.append("</"))
2704             goto out;
2705
2706         /* Step 26. */
2707         prefix = ns->getNamePrefix();
2708         if (prefix && !prefix->empty()) {
2709             if (!sb.append(prefix) || !sb.append(':'))
2710                 goto out;
2711         }
2712
2713         /* Step 27. */
2714         if (!sb.append(xml->name->getQNameLocalName()) || !sb.append('>'))
2715             goto out;
2716     }
2717
2718     str = sb.finishString();
2719 out:
2720     js_LeaveLocalRootScopeWithResult(cx, str);
2721     return str;
2722 }
2723
2724 /* ECMA-357 10.2 */
2725 static JSString *
2726 ToXMLString(JSContext *cx, jsval v, uint32 toSourceFlag)
2727 {
2728     JSObject *obj;
2729     JSString *str;
2730     JSXML *xml;
2731
2732     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
2733         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2734                              JSMSG_BAD_XML_CONVERSION,
2735                              JSVAL_IS_NULL(v) ? js_null_str : js_undefined_str);
2736         return NULL;
2737     }
2738
2739     if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
2740         return js_ValueToString(cx, Valueify(v));
2741
2742     if (JSVAL_IS_STRING(v)) {
2743         StringBuffer sb(cx);
2744         return EscapeElementValue(cx, sb, JSVAL_TO_STRING(v), toSourceFlag);
2745     }
2746
2747     obj = JSVAL_TO_OBJECT(v);
2748     if (!obj->isXML()) {
2749         if (!DefaultValue(cx, obj, JSTYPE_STRING, Valueify(&v)))
2750             return NULL;
2751         str = js_ValueToString(cx, Valueify(v));
2752         if (!str)
2753             return NULL;
2754         StringBuffer sb(cx);
2755         return EscapeElementValue(cx, sb, str, toSourceFlag);
2756     }
2757
2758     /* Handle non-element cases in this switch, returning from each case. */
2759     xml = (JSXML *) obj->getPrivate();
2760     return XMLToXMLString(cx, xml, NULL, toSourceFlag | 0);
2761 }
2762
2763 static JSObject *
2764 ToAttributeName(JSContext *cx, jsval v)
2765 {
2766     JSLinearString *name, *uri, *prefix;
2767     JSObject *obj;
2768     Class *clasp;
2769     JSObject *qn;
2770
2771     if (JSVAL_IS_STRING(v)) {
2772         name = JSVAL_TO_STRING(v)->ensureLinear(cx);
2773         if (!name)
2774             return NULL;
2775         uri = prefix = cx->runtime->emptyString;
2776     } else {
2777         if (JSVAL_IS_PRIMITIVE(v)) {
2778             js_ReportValueError(cx, JSMSG_BAD_XML_ATTR_NAME,
2779                                 JSDVG_IGNORE_STACK, Valueify(v), NULL);
2780             return NULL;
2781         }
2782
2783         obj = JSVAL_TO_OBJECT(v);
2784         clasp = obj->getClass();
2785         if (clasp == &js_AttributeNameClass)
2786             return obj;
2787
2788         if (clasp == &js_QNameClass) {
2789             qn = obj;
2790             uri = qn->getNameURI();
2791             prefix = qn->getNamePrefix();
2792             name = qn->getQNameLocalName();
2793         } else {
2794             if (clasp == &js_AnyNameClass) {
2795                 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
2796             } else {
2797                 JSString *str = js_ValueToString(cx, Valueify(v));
2798                 if (!str)
2799                     return NULL;
2800                 name = str->ensureLinear(cx);
2801                 if (!name)
2802                     return NULL;
2803             }
2804             uri = prefix = cx->runtime->emptyString;
2805         }
2806     }
2807
2808     qn = NewXMLAttributeName(cx, uri, prefix, name);
2809     if (!qn)
2810         return NULL;
2811     return qn;
2812 }
2813
2814 static void
2815 ReportBadXMLName(JSContext *cx, const Value &idval)
2816 {
2817     js_ReportValueError(cx, JSMSG_BAD_XML_NAME, JSDVG_IGNORE_STACK, idval, NULL);
2818 }
2819
2820 static JSBool
2821 IsFunctionQName(JSContext *cx, JSObject *qn, jsid *funidp)
2822 {
2823     JSAtom *atom;
2824     JSLinearString *uri;
2825
2826     atom = cx->runtime->atomState.functionNamespaceURIAtom;
2827     uri = qn->getNameURI();
2828     if (uri &&
2829         (uri == ATOM_TO_STRING(atom) ||
2830          EqualStrings(uri, ATOM_TO_STRING(atom)))) {
2831         return JS_ValueToId(cx, STRING_TO_JSVAL(qn->getQNameLocalName()), funidp);
2832     }
2833     *funidp = JSID_VOID;
2834     return JS_TRUE;
2835 }
2836
2837 JSBool
2838 js_IsFunctionQName(JSContext *cx, JSObject *obj, jsid *funidp)
2839 {
2840     if (obj->getClass() == &js_QNameClass)
2841         return IsFunctionQName(cx, obj, funidp);
2842     *funidp = JSID_VOID;
2843     return JS_TRUE;
2844 }
2845
2846 static JSObject *
2847 ToXMLName(JSContext *cx, jsval v, jsid *funidp)
2848 {
2849     JSAtom *atomizedName;
2850     JSString *name;
2851     JSObject *obj;
2852     Class *clasp;
2853     uint32 index;
2854
2855     if (JSVAL_IS_STRING(v)) {
2856         name = JSVAL_TO_STRING(v);
2857     } else {
2858         if (JSVAL_IS_PRIMITIVE(v)) {
2859             ReportBadXMLName(cx, Valueify(v));
2860             return NULL;
2861         }
2862
2863         obj = JSVAL_TO_OBJECT(v);
2864         clasp = obj->getClass();
2865         if (clasp == &js_AttributeNameClass || clasp == &js_QNameClass)
2866             goto out;
2867         if (clasp == &js_AnyNameClass) {
2868             name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
2869             goto construct;
2870         }
2871         name = js_ValueToString(cx, Valueify(v));
2872         if (!name)
2873             return NULL;
2874     }
2875
2876     atomizedName = js_AtomizeString(cx, name, 0);
2877     if (!atomizedName)
2878         return NULL;
2879
2880     /*
2881      * ECMA-357 10.6.1 step 1 seems to be incorrect.  The spec says:
2882      *
2883      * 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
2884      *
2885      * First, _P_ should be _s_, to refer to the given string.
2886      *
2887      * Second, why does ToXMLName applied to the string type throw TypeError
2888      * only for numeric literals without any leading or trailing whitespace?
2889      *
2890      * If the idea is to reject uint32 property names, then the check needs to
2891      * be stricter, to exclude hexadecimal and floating point literals.
2892      */
2893     if (js_IdIsIndex(ATOM_TO_JSID(atomizedName), &index))
2894         goto bad;
2895
2896     if (*atomizedName->chars() == '@') {
2897         name = js_NewDependentString(cx, name, 1, name->length() - 1);
2898         if (!name)
2899             return NULL;
2900         *funidp = JSID_VOID;
2901         return ToAttributeName(cx, STRING_TO_JSVAL(name));
2902     }
2903
2904 construct:
2905     v = STRING_TO_JSVAL(name);
2906     obj = js_ConstructObject(cx, &js_QNameClass, NULL, NULL, 1, Valueify(&v));
2907     if (!obj)
2908         return NULL;
2909
2910 out:
2911     if (!IsFunctionQName(cx, obj, funidp))
2912         return NULL;
2913     return obj;
2914
2915 bad:
2916     JSAutoByteString bytes;
2917     if (js_ValueToPrintable(cx, StringValue(name), &bytes))
2918         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XML_NAME, bytes.ptr());
2919     return NULL;
2920 }
2921
2922 /* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
2923 static JSBool
2924 AddInScopeNamespace(JSContext *cx, JSXML *xml, JSObject *ns)
2925 {
2926     JSLinearString *prefix, *prefix2;
2927     JSObject *match, *ns2;
2928     uint32 i, n, m;
2929
2930     if (xml->xml_class != JSXML_CLASS_ELEMENT)
2931         return JS_TRUE;
2932
2933     /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
2934     prefix = ns->getNamePrefix();
2935     if (!prefix) {
2936         match = NULL;
2937         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
2938             ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
2939             if (ns2 && EqualStrings(ns2->getNameURI(), ns->getNameURI())) {
2940                 match = ns2;
2941                 break;
2942             }
2943         }
2944         if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
2945             return JS_FALSE;
2946     } else {
2947         if (prefix->empty() && xml->name->getNameURI()->empty())
2948             return JS_TRUE;
2949         match = NULL;
2950 #ifdef __GNUC__         /* suppress bogus gcc warnings */
2951         m = XML_NOT_FOUND;
2952 #endif
2953         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
2954             ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
2955             if (ns2 && (prefix2 = ns2->getNamePrefix()) &&
2956                 EqualStrings(prefix2, prefix)) {
2957                 match = ns2;
2958                 m = i;
2959                 break;
2960             }
2961         }
2962         if (match && !EqualStrings(match->getNameURI(), ns->getNameURI())) {
2963             ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
2964                                   JSObject);
2965             JS_ASSERT(ns2 == match);
2966             match->clearNamePrefix();
2967             if (!AddInScopeNamespace(cx, xml, match))
2968                 return JS_FALSE;
2969         }
2970         if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
2971             return JS_FALSE;
2972     }
2973
2974     /* OPTION: enforce that descendants have superset namespaces. */
2975     return JS_TRUE;
2976 }
2977
2978 /* ECMA-357 9.2.1.6 XMLList [[Append]]. */
2979 static JSBool
2980 Append(JSContext *cx, JSXML *list, JSXML *xml)
2981 {
2982     uint32 i, j, k, n;
2983     JSXML *kid;
2984
2985     JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
2986     i = list->xml_kids.length;
2987     n = 1;
2988     if (xml->xml_class == JSXML_CLASS_LIST) {
2989         list->xml_target = xml->xml_target;
2990         list->xml_targetprop = xml->xml_targetprop;
2991         n = JSXML_LENGTH(xml);
2992         k = i + n;
2993         if (!list->xml_kids.setCapacity(cx, k))
2994             return JS_FALSE;
2995         for (j = 0; j < n; j++) {
2996             kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML);
2997             if (kid)
2998                 XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
2999         }
3000         return JS_TRUE;
3001     }
3002
3003     list->xml_target = xml->parent;
3004     if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
3005         list->xml_targetprop = NULL;
3006     else
3007         list->xml_targetprop = xml->name;
3008     if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
3009         return JS_FALSE;
3010     return JS_TRUE;
3011 }
3012
3013 /* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
3014 static JSXML *
3015 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags);
3016
3017 static JSXML *
3018 DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
3019 {
3020     JSXML *copy;
3021
3022     /* Our caller may not be protecting newborns with a local root scope. */
3023     if (!js_EnterLocalRootScope(cx))
3024         return NULL;
3025     copy = DeepCopyInLRS(cx, xml, flags);
3026     if (copy) {
3027         if (obj) {
3028             /* Caller provided the object for this copy, hook 'em up. */
3029             obj->setPrivate(copy);
3030             copy->object = obj;
3031         } else if (!js_GetXMLObject(cx, copy)) {
3032             copy = NULL;
3033         }
3034     }
3035     js_LeaveLocalRootScopeWithResult(cx, copy);
3036     return copy;
3037 }
3038
3039 /*
3040  * (i) We must be in a local root scope (InLRS).
3041  * (ii) parent must have a rooted object.
3042  * (iii) from's owning object must be locked if not thread-local.
3043  */
3044 static JSBool
3045 DeepCopySetInLRS(JSContext *cx, JSXMLArray *from, JSXMLArray *to, JSXML *parent,
3046                  uintN flags)
3047 {
3048     uint32 j, n;
3049     JSXML *kid2;
3050     JSString *str;
3051
3052     n = from->length;
3053     if (!to->setCapacity(cx, n))
3054         return JS_FALSE;
3055
3056     JSXMLArrayCursor cursor(from);
3057     j = 0;
3058     while (JSXML *kid = (JSXML *) cursor.getNext()) {
3059         if ((flags & XSF_IGNORE_COMMENTS) &&
3060             kid->xml_class == JSXML_CLASS_COMMENT) {
3061             continue;
3062         }
3063         if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
3064             kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
3065             continue;
3066         }
3067         if ((flags & XSF_IGNORE_WHITESPACE) &&
3068             (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
3069             continue;
3070         }
3071         kid2 = DeepCopyInLRS(cx, kid, flags);
3072         if (!kid2) {
3073             to->length = j;
3074             return JS_FALSE;
3075         }
3076
3077         if ((flags & XSF_IGNORE_WHITESPACE) &&
3078             n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
3079             str = ChompXMLWhitespace(cx, kid2->xml_value);
3080             if (!str) {
3081                 to->length = j;
3082                 return JS_FALSE;
3083             }
3084             kid2->xml_value = str;
3085         }
3086
3087         XMLARRAY_SET_MEMBER(to, j, kid2);
3088         ++j;
3089         if (parent->xml_class != JSXML_CLASS_LIST)
3090             kid2->parent = parent;
3091     }
3092
3093     if (j < n)
3094         to->trim();
3095     return JS_TRUE;
3096 }
3097
3098 static JSXML *
3099 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
3100 {
3101     JSXML *copy;
3102     JSObject *qn;
3103     JSBool ok;
3104     uint32 i, n;
3105     JSObject *ns, *ns2;
3106
3107     JS_CHECK_RECURSION(cx, return NULL);
3108
3109     copy = js_NewXML(cx, JSXMLClass(xml->xml_class));
3110     if (!copy)
3111         return NULL;
3112     qn = xml->name;
3113     if (qn) {
3114         qn = NewXMLQName(cx, qn->getNameURI(), qn->getNamePrefix(), qn->getQNameLocalName());
3115         if (!qn) {
3116             ok = JS_FALSE;
3117             goto out;
3118         }
3119     }
3120     copy->name = qn;
3121     copy->xml_flags = xml->xml_flags;
3122
3123     if (JSXML_HAS_VALUE(xml)) {
3124         copy->xml_value = xml->xml_value;
3125         ok = JS_TRUE;
3126     } else {
3127         ok = DeepCopySetInLRS(cx, &xml->xml_kids, &copy->xml_kids, copy, flags);
3128         if (!ok)
3129             goto out;
3130
3131         if (xml->xml_class == JSXML_CLASS_LIST) {
3132             copy->xml_target = xml->xml_target;
3133             copy->xml_targetprop = xml->xml_targetprop;
3134         } else {
3135             n = xml->xml_namespaces.length;
3136             ok = copy->xml_namespaces.setCapacity(cx, n);
3137             if (!ok)
3138                 goto out;
3139             for (i = 0; i < n; i++) {
3140                 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3141                 if (!ns)
3142                     continue;
3143                 ns2 = NewXMLNamespace(cx, ns->getNamePrefix(), ns->getNameURI(),
3144                                       IsDeclared(ns));
3145                 if (!ns2) {
3146                     copy->xml_namespaces.length = i;
3147                     ok = JS_FALSE;
3148                     goto out;
3149                 }
3150                 XMLARRAY_SET_MEMBER(&copy->xml_namespaces, i, ns2);
3151             }
3152
3153             ok = DeepCopySetInLRS(cx, &xml->xml_attrs, &copy->xml_attrs, copy,
3154                                   0);
3155             if (!ok)
3156                 goto out;
3157         }
3158     }
3159
3160 out:
3161     if (!ok)
3162         return NULL;
3163     return copy;
3164 }
3165
3166 /* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
3167 static void
3168 DeleteByIndex(JSContext *cx, JSXML *xml, uint32 index)
3169 {
3170     JSXML *kid;
3171
3172     if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
3173         kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3174         if (kid)
3175             kid->parent = NULL;
3176         XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3177     }
3178 }
3179
3180 typedef JSBool (*JSXMLNameMatcher)(JSObject *nameqn, JSXML *xml);
3181
3182 static JSBool
3183 MatchAttrName(JSObject *nameqn, JSXML *attr)
3184 {
3185     JSObject *attrqn = attr->name;
3186     JSLinearString *localName = nameqn->getQNameLocalName();
3187     JSLinearString *uri;
3188
3189     return (IS_STAR(localName) ||
3190             EqualStrings(attrqn->getQNameLocalName(), localName)) &&
3191            (!(uri = nameqn->getNameURI()) ||
3192             EqualStrings(attrqn->getNameURI(), uri));
3193 }
3194
3195 static JSBool
3196 MatchElemName(JSObject *nameqn, JSXML *elem)
3197 {
3198     JSLinearString *localName = nameqn->getQNameLocalName();
3199     JSLinearString *uri;
3200
3201     return (IS_STAR(localName) ||
3202             (elem->xml_class == JSXML_CLASS_ELEMENT &&
3203              EqualStrings(elem->name->getQNameLocalName(), localName))) &&
3204            (!(uri = nameqn->getNameURI()) ||
3205             (elem->xml_class == JSXML_CLASS_ELEMENT &&
3206              EqualStrings(elem->name->getNameURI(), uri)));
3207 }
3208
3209 /* ECMA-357 9.1.1.8 XML [[Descendants]] and 9.2.1.8 XMLList [[Descendants]]. */
3210 static JSBool
3211 DescendantsHelper(JSContext *cx, JSXML *xml, JSObject *nameqn, JSXML *list)
3212 {
3213     uint32 i, n;
3214     JSXML *attr, *kid;
3215
3216     JS_CHECK_RECURSION(cx, return JS_FALSE);
3217
3218     if (xml->xml_class == JSXML_CLASS_ELEMENT &&
3219         nameqn->getClass() == &js_AttributeNameClass) {
3220         for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
3221             attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3222             if (attr && MatchAttrName(nameqn, attr)) {
3223                 if (!Append(cx, list, attr))
3224                     return JS_FALSE;
3225             }
3226         }
3227     }
3228
3229     for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
3230         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3231         if (!kid)
3232             continue;
3233         if (nameqn->getClass() != &js_AttributeNameClass &&
3234             MatchElemName(nameqn, kid)) {
3235             if (!Append(cx, list, kid))
3236                 return JS_FALSE;
3237         }
3238         if (!DescendantsHelper(cx, kid, nameqn, list))
3239             return JS_FALSE;
3240     }
3241     return JS_TRUE;
3242 }
3243
3244 static JSXML *
3245 Descendants(JSContext *cx, JSXML *xml, jsval id)
3246 {
3247     jsid funid;
3248     JSObject *nameqn;
3249     JSObject *listobj;
3250     JSXML *list, *kid;
3251     uint32 i, n;
3252     JSBool ok;
3253
3254     nameqn = ToXMLName(cx, id, &funid);
3255     if (!nameqn)
3256         return NULL;
3257
3258     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3259     if (!listobj)
3260         return NULL;
3261     list = (JSXML *) listobj->getPrivate();
3262     if (!JSID_IS_VOID(funid))
3263         return list;
3264
3265     /*
3266      * Protect nameqn's object and strings from GC by linking list to it
3267      * temporarily.  The newborn GC root for the last allocated object
3268      * protects listobj, which protects list. Any other object allocations
3269      * occurring beneath DescendantsHelper use local roots.
3270      */
3271     list->name = nameqn;
3272     if (!js_EnterLocalRootScope(cx))
3273         return NULL;
3274     if (xml->xml_class == JSXML_CLASS_LIST) {
3275         ok = JS_TRUE;
3276         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
3277             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3278             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3279                 ok = DescendantsHelper(cx, kid, nameqn, list);
3280                 if (!ok)
3281                     break;
3282             }
3283         }
3284     } else {
3285         ok = DescendantsHelper(cx, xml, nameqn, list);
3286     }
3287     js_LeaveLocalRootScopeWithResult(cx, list);
3288     if (!ok)
3289         return NULL;
3290     list->name = NULL;
3291     return list;
3292 }
3293
3294 /* Recursive (JSXML *) parameterized version of Equals. */
3295 static JSBool
3296 XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
3297 {
3298     JSObject *qn, *vqn;
3299     uint32 i, j, n;
3300     JSXML *kid, *vkid, *attr, *vattr;
3301     JSObject *xobj, *vobj;
3302
3303 retry:
3304     if (xml->xml_class != vxml->xml_class) {
3305         if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
3306             xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3307             if (xml)
3308                 goto retry;
3309         }
3310         if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
3311             vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
3312             if (vxml)
3313                 goto retry;
3314         }
3315         *bp = JS_FALSE;
3316         return JS_TRUE;
3317     }
3318
3319     qn = xml->name;
3320     vqn = vxml->name;
3321     if (qn) {
3322         *bp = vqn &&
3323               EqualStrings(qn->getQNameLocalName(), vqn->getQNameLocalName()) &&
3324               EqualStrings(qn->getNameURI(), vqn->getNameURI());
3325     } else {
3326         *bp = vqn == NULL;
3327     }
3328     if (!*bp)
3329         return JS_TRUE;
3330
3331     if (JSXML_HAS_VALUE(xml)) {
3332         if (!EqualStrings(cx, xml->xml_value, vxml->xml_value, bp))
3333             return JS_FALSE;
3334     } else if (xml->xml_kids.length != vxml->xml_kids.length) {
3335         *bp = JS_FALSE;
3336     } else {
3337         {
3338             JSXMLArrayCursor cursor(&xml->xml_kids);
3339             JSXMLArrayCursor vcursor(&vxml->xml_kids);
3340             for (;;) {
3341                 kid = (JSXML *) cursor.getNext();
3342                 vkid = (JSXML *) vcursor.getNext();
3343                 if (!kid || !vkid) {
3344                     *bp = !kid && !vkid;
3345                     break;
3346                 }
3347                 xobj = js_GetXMLObject(cx, kid);
3348                 vobj = js_GetXMLObject(cx, vkid);
3349                 if (!xobj || !vobj ||
3350                     !js_TestXMLEquality(cx, ObjectValue(*xobj), ObjectValue(*vobj), bp))
3351                     return JS_FALSE;
3352                 if (!*bp)
3353                     break;
3354             }
3355         }
3356
3357         if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
3358             n = xml->xml_attrs.length;
3359             if (n != vxml->xml_attrs.length)
3360                 *bp = JS_FALSE;
3361             for (i = 0; *bp && i < n; i++) {
3362                 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3363                 if (!attr)
3364                     continue;
3365                 j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
3366                 if (j == XML_NOT_FOUND) {
3367                     *bp = JS_FALSE;
3368                     break;
3369                 }
3370                 vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
3371                 if (!vattr)
3372                     continue;
3373                 if (!EqualStrings(cx, attr->xml_value, vattr->xml_value, bp))
3374                     return JS_FALSE;
3375             }
3376         }
3377     }
3378
3379     return JS_TRUE;
3380 }
3381
3382 /* ECMA-357 9.1.1.9 XML [[Equals]] and 9.2.1.9 XMLList [[Equals]]. */
3383 static JSBool
3384 Equals(JSContext *cx, JSXML *xml, jsval v, JSBool *bp)
3385 {
3386     JSObject *vobj;
3387     JSXML *vxml;
3388
3389     if (JSVAL_IS_PRIMITIVE(v)) {
3390         *bp = JS_FALSE;
3391         if (xml->xml_class == JSXML_CLASS_LIST) {
3392             if (xml->xml_kids.length == 1) {
3393                 vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3394                 if (!vxml)
3395                     return JS_TRUE;
3396                 vobj = js_GetXMLObject(cx, vxml);
3397                 if (!vobj)
3398                     return JS_FALSE;
3399                 return js_TestXMLEquality(cx, ObjectValue(*vobj), Valueify(v), bp);
3400             }
3401             if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
3402                 *bp = JS_TRUE;
3403         }
3404     } else {
3405         vobj = JSVAL_TO_OBJECT(v);
3406         if (!vobj->isXML()) {
3407             *bp = JS_FALSE;
3408         } else {
3409             vxml = (JSXML *) vobj->getPrivate();
3410             if (!XMLEquals(cx, xml, vxml, bp))
3411                 return JS_FALSE;
3412         }
3413     }
3414     return JS_TRUE;
3415 }
3416
3417 static JSBool
3418 CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
3419 {
3420     JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);
3421
3422     do {
3423         if (xml == kid) {
3424             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3425                                  JSMSG_CYCLIC_VALUE, js_XML_str);
3426             return JS_FALSE;
3427         }
3428     } while ((xml = xml->parent) != NULL);
3429
3430     return JS_TRUE;
3431 }
3432
3433 /* ECMA-357 9.1.1.11 XML [[Insert]]. */
3434 static JSBool
3435 Insert(JSContext *cx, JSXML *xml, uint32 i, jsval v)
3436 {
3437     uint32 j, n;
3438     JSXML *vxml, *kid;
3439     JSObject *vobj;
3440     JSString *str;
3441
3442     if (!JSXML_HAS_KIDS(xml))
3443         return JS_TRUE;
3444
3445     n = 1;
3446     vxml = NULL;
3447     if (!JSVAL_IS_PRIMITIVE(v)) {
3448         vobj = JSVAL_TO_OBJECT(v);
3449         if (vobj->isXML()) {
3450             vxml = (JSXML *) vobj->getPrivate();
3451             if (vxml->xml_class == JSXML_CLASS_LIST) {
3452                 n = vxml->xml_kids.length;
3453                 if (n == 0)
3454                     return JS_TRUE;
3455                 for (j = 0; j < n; j++) {
3456                     kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3457                     if (!kid)
3458                         continue;
3459                     if (!CheckCycle(cx, xml, kid))
3460                         return JS_FALSE;
3461                 }
3462             } else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
3463                 /* OPTION: enforce that descendants have superset namespaces. */
3464                 if (!CheckCycle(cx, xml, vxml))
3465                     return JS_FALSE;
3466             }
3467         }
3468     }
3469     if (!vxml) {
3470         str = js_ValueToString(cx, Valueify(v));
3471         if (!str)
3472             return JS_FALSE;
3473
3474         vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3475         if (!vxml)
3476             return JS_FALSE;
3477         vxml->xml_value = str;
3478     }
3479
3480     if (i > xml->xml_kids.length)
3481         i = xml->xml_kids.length;
3482
3483     if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
3484         return JS_FALSE;
3485
3486     if (vxml->xml_class == JSXML_CLASS_LIST) {
3487         for (j = 0; j < n; j++) {
3488             kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3489             if (!kid)
3490                 continue;
3491             kid->parent = xml;
3492             XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);
3493
3494             /* OPTION: enforce that descendants have superset namespaces. */
3495         }
3496     } else {
3497         vxml->parent = xml;
3498         XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
3499     }
3500     return JS_TRUE;
3501 }
3502
3503 static JSBool
3504 IndexToId(JSContext *cx, uint32 index, jsid *idp)
3505 {
3506     JSAtom *atom;
3507     JSString *str;
3508
3509     if (index <= JSID_INT_MAX) {
3510         *idp = INT_TO_JSID(index);
3511     } else {
3512         str = js_NumberToString(cx, (jsdouble) index);
3513         if (!str)
3514             return JS_FALSE;
3515         atom = js_AtomizeString(cx, str, 0);
3516         if (!atom)
3517             return JS_FALSE;
3518         *idp = ATOM_TO_JSID(atom);
3519     }
3520     return JS_TRUE;
3521 }
3522
3523 /* ECMA-357 9.1.1.12 XML [[Replace]]. */
3524 static JSBool
3525 Replace(JSContext *cx, JSXML *xml, uint32 i, jsval v)
3526 {
3527     uint32 n;
3528     JSXML *vxml, *kid;
3529     JSObject *vobj;
3530     JSString *str;
3531
3532     if (!JSXML_HAS_KIDS(xml))
3533         return JS_TRUE;
3534
3535     /*
3536      * 9.1.1.12
3537      * [[Replace]] handles _i >= x.[[Length]]_ by incrementing _x.[[Length]_.
3538      * It should therefore constrain callers to pass in _i <= x.[[Length]]_.
3539      */
3540     n = xml->xml_kids.length;
3541     if (i > n)
3542         i = n;
3543
3544     vxml = NULL;
3545     if (!JSVAL_IS_PRIMITIVE(v)) {
3546         vobj = JSVAL_TO_OBJECT(v);
3547         if (vobj->isXML())
3548             vxml = (JSXML *) vobj->getPrivate();
3549     }
3550
3551     switch (vxml ? JSXMLClass(vxml->xml_class) : JSXML_CLASS_LIMIT) {
3552       case JSXML_CLASS_ELEMENT:
3553         /* OPTION: enforce that descendants have superset namespaces. */
3554         if (!CheckCycle(cx, xml, vxml))
3555             return JS_FALSE;
3556       case JSXML_CLASS_COMMENT:
3557       case JSXML_CLASS_PROCESSING_INSTRUCTION:
3558       case JSXML_CLASS_TEXT:
3559         goto do_replace;
3560
3561       case JSXML_CLASS_LIST:
3562         if (i < n)
3563             DeleteByIndex(cx, xml, i);
3564         if (!Insert(cx, xml, i, v))
3565             return JS_FALSE;
3566         break;
3567
3568       default:
3569         str = js_ValueToString(cx, Valueify(v));
3570         if (!str)
3571             return JS_FALSE;
3572
3573         vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3574         if (!vxml)
3575             return JS_FALSE;
3576         vxml->xml_value = str;
3577
3578       do_replace:
3579         vxml->parent = xml;
3580         if (i < n) {
3581             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3582             if (kid)
3583                 kid->parent = NULL;
3584         }
3585         if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
3586             return JS_FALSE;
3587         break;
3588     }
3589
3590     return JS_TRUE;
3591 }
3592
3593 /* ECMA-357 9.1.1.3 XML [[Delete]], 9.2.1.3 XML [[Delete]] qname cases. */
3594 static void
3595 DeleteNamedProperty(JSContext *cx, JSXML *xml, JSObject *nameqn,
3596                     JSBool attributes)
3597 {
3598     JSXMLArray *array;
3599     uint32 index, deleteCount;
3600     JSXML *kid;
3601     JSXMLNameMatcher matcher;
3602
3603     if (xml->xml_class == JSXML_CLASS_LIST) {
3604         array = &xml->xml_kids;
3605         for (index = 0; index < array->length; index++) {
3606             kid = XMLARRAY_MEMBER(array, index, JSXML);
3607             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT)
3608                 DeleteNamedProperty(cx, kid, nameqn, attributes);
3609         }
3610     } else if (xml->xml_class == JSXML_CLASS_ELEMENT) {
3611         if (attributes) {
3612             array = &xml->xml_attrs;
3613             matcher = MatchAttrName;
3614         } else {
3615             array = &xml->xml_kids;
3616             matcher = MatchElemName;
3617         }
3618         deleteCount = 0;
3619         for (index = 0; index < array->length; index++) {
3620             kid = XMLARRAY_MEMBER(array, index, JSXML);
3621             if (kid && matcher(nameqn, kid)) {
3622                 kid->parent = NULL;
3623                 XMLArrayDelete(cx, array, index, JS_FALSE);
3624                 ++deleteCount;
3625             } else if (deleteCount != 0) {
3626                 XMLARRAY_SET_MEMBER(array,
3627                                     index - deleteCount,
3628                                     array->vector[index]);
3629             }
3630         }
3631         array->length -= deleteCount;
3632     }
3633 }
3634
3635 /* ECMA-357 9.2.1.3 index case. */
3636 static void
3637 DeleteListElement(JSContext *cx, JSXML *xml, uint32 index)
3638 {
3639     JSXML *kid, *parent;
3640     uint32 kidIndex;
3641
3642     JS_ASSERT(xml->xml_class == JSXML_CLASS_LIST);
3643
3644     if (index < xml->xml_kids.length) {
3645         kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3646         if (kid) {
3647             parent = kid->parent;
3648             if (parent) {
3649                 JS_ASSERT(parent != xml);
3650                 JS_ASSERT(JSXML_HAS_KIDS(parent));
3651
3652                 if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
3653                     DeleteNamedProperty(cx, parent, kid->name, JS_TRUE);
3654                 } else {
3655                     kidIndex = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid,
3656                                                     NULL);
3657                     JS_ASSERT(kidIndex != XML_NOT_FOUND);
3658                     DeleteByIndex(cx, parent, kidIndex);
3659                 }
3660             }
3661             XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3662         }
3663     }
3664 }
3665
3666 static JSBool
3667 SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
3668 {
3669     JSXMLArray *nsarray;
3670     uint32 i, n;
3671     JSObject *ns;
3672
3673     nsarray = &xml->xml_namespaces;
3674     while ((xml = xml->parent) != NULL) {
3675         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3676             ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
3677             if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
3678                 if (!XMLARRAY_APPEND(cx, nsarray, ns))
3679                     return JS_FALSE;
3680             }
3681         }
3682     }
3683     return JS_TRUE;
3684 }
3685
3686 static JSBool
3687 GetNamedProperty(JSContext *cx, JSXML *xml, JSObject* nameqn, JSXML *list)
3688 {
3689     JSXMLArray *array;
3690     JSXMLNameMatcher matcher;
3691     JSBool attrs;
3692
3693     if (xml->xml_class == JSXML_CLASS_LIST) {
3694         JSXMLArrayCursor cursor(&xml->xml_kids);
3695         while (JSXML *kid = (JSXML *) cursor.getNext()) {
3696             if (kid->xml_class == JSXML_CLASS_ELEMENT &&
3697                 !GetNamedProperty(cx, kid, nameqn, list)) {
3698                 return JS_FALSE;
3699             }
3700         }
3701     } else if (xml->xml_class == JSXML_CLASS_ELEMENT) {
3702         attrs = (nameqn->getClass() == &js_AttributeNameClass);
3703         if (attrs) {
3704             array = &xml->xml_attrs;
3705             matcher = MatchAttrName;
3706         } else {
3707             array = &xml->xml_kids;
3708             matcher = MatchElemName;
3709         }
3710
3711         JSXMLArrayCursor cursor(array);
3712         while (JSXML *kid = (JSXML *) cursor.getNext()) {
3713             if (matcher(nameqn, kid)) {
3714                 if (!attrs &&
3715                     kid->xml_class == JSXML_CLASS_ELEMENT &&
3716                     !SyncInScopeNamespaces(cx, kid)) {
3717                     return JS_FALSE;
3718                 }
3719                 if (!Append(cx, list, kid))
3720                     return JS_FALSE;
3721             }
3722         }
3723     }
3724
3725     return JS_TRUE;
3726 }
3727
3728 /* ECMA-357 9.1.1.1 XML [[Get]] and 9.2.1.1 XMLList [[Get]]. */
3729 static JSBool
3730 GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
3731 {
3732     JSXML *xml, *list, *kid;
3733     uint32 index;
3734     JSObject *kidobj, *listobj;
3735     JSObject *nameqn;
3736     jsid funid;
3737
3738     xml = (JSXML *) GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
3739     if (!xml)
3740         return true;
3741
3742     if (js_IdIsIndex(id, &index)) {
3743         if (!JSXML_HAS_KIDS(xml)) {
3744             *vp = (index == 0) ? OBJECT_TO_JSVAL(obj) : JSVAL_VOID;
3745         } else {
3746             /*
3747              * ECMA-357 9.2.1.1 starts here.
3748              *
3749              * Erratum: 9.2 is not completely clear that indexed properties
3750              * correspond to kids, but that's what it seems to say, and it's
3751              * what any sane user would want.
3752              */
3753             if (index < xml->xml_kids.length) {
3754                 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3755                 if (!kid) {
3756                     *vp = JSVAL_VOID;
3757                     return true;
3758                 }
3759                 kidobj = js_GetXMLObject(cx, kid);
3760                 if (!kidobj)
3761                     return false;
3762
3763                 *vp = OBJECT_TO_JSVAL(kidobj);
3764             } else {
3765                 *vp = JSVAL_VOID;
3766             }
3767         }
3768         return true;
3769     }
3770
3771     /*
3772      * ECMA-357 9.2.1.1/9.1.1.1 qname case.
3773      */
3774     nameqn = ToXMLName(cx, IdToJsval(id), &funid);
3775     if (!nameqn)
3776         return false;
3777     if (!JSID_IS_VOID(funid))
3778         return GetXMLFunction(cx, obj, funid, vp);
3779
3780     jsval roots[2] = { OBJECT_TO_JSVAL(nameqn), JSVAL_NULL };
3781     AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(roots), Valueify(roots));
3782
3783     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3784     if (!listobj)
3785         return false;
3786
3787     roots[1] = OBJECT_TO_JSVAL(listobj);
3788
3789     list = (JSXML *) listobj->getPrivate();
3790     if (!GetNamedProperty(cx, xml, nameqn, list))
3791         return false;
3792
3793     /*
3794      * Erratum: ECMA-357 9.1.1.1 misses that [[Append]] sets the
3795      * given list's [[TargetProperty]] to the property that is being
3796      * appended. This means that any use of the internal [[Get]]
3797      * property returns a list which, when used by e.g. [[Insert]]
3798      * duplicates the last element matched by id. See bug 336921.
3799      */
3800     list->xml_target = xml;
3801     list->xml_targetprop = nameqn;
3802     *vp = OBJECT_TO_JSVAL(listobj);
3803     return true;
3804 }
3805
3806 static JSXML *
3807 CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
3808 {
3809     JS_ASSERT(xml->object != obj);
3810
3811     xml = DeepCopy(cx, xml, obj, 0);
3812     if (!xml)
3813         return NULL;
3814
3815     JS_ASSERT(xml->object == obj);
3816     return xml;
3817 }
3818
3819 #define CHECK_COPY_ON_WRITE(cx,xml,obj)                                       \
3820     (xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))
3821
3822 static JSString *
3823 KidToString(JSContext *cx, JSXML *xml, uint32 index)
3824 {
3825     JSXML *kid;
3826     JSObject *kidobj;
3827
3828     kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3829     if (!kid)
3830         return cx->runtime->emptyString;
3831     kidobj = js_GetXMLObject(cx, kid);
3832     if (!kidobj)
3833         return NULL;
3834     return js_ValueToString(cx, ObjectValue(*kidobj));
3835 }
3836
3837 /* Forward declared -- its implementation uses other statics that call it. */
3838 static JSBool
3839 ResolveValue(JSContext *cx, JSXML *list, JSXML **result);
3840
3841 /* ECMA-357 9.1.1.2 XML [[Put]] and 9.2.1.2 XMLList [[Put]]. */
3842 static JSBool
3843 PutProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
3844 {
3845     JSBool ok, primitiveAssign;
3846     enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
3847     JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
3848     JSObject *vobj, *nameobj, *attrobj, *parentobj, *kidobj, *copyobj;
3849     JSObject *targetprop, *nameqn, *attrqn;
3850     uint32 index, i, j, k, n, q, matchIndex;
3851     jsval attrval, nsval;
3852     jsid funid;
3853     JSObject *ns;
3854
3855     xml = (JSXML *) GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
3856     if (!xml)
3857         return JS_TRUE;
3858
3859     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
3860     if (!xml)
3861         return JS_FALSE;
3862
3863     /* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
3864     vxml = NULL;
3865     if (!JSVAL_IS_PRIMITIVE(*vp)) {
3866         vobj = JSVAL_TO_OBJECT(*vp);
3867         if (vobj->isXML())
3868             vxml = (JSXML *) vobj->getPrivate();
3869     }
3870
3871     ok = js_EnterLocalRootScope(cx);
3872     if (!ok)
3873         return JS_FALSE;
3874
3875     MUST_FLOW_THROUGH("out");
3876     jsval roots[3];
3877     roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
3878     roots[ID_ROOT] = IdToJsval(id);
3879     roots[VAL_ROOT] = *vp;
3880     AutoArrayRooter tvr(cx, JS_ARRAY_LENGTH(roots), Valueify(roots));
3881
3882     if (js_IdIsIndex(id, &index)) {
3883         if (xml->xml_class != JSXML_CLASS_LIST) {
3884             /* See NOTE in spec: this variation is reserved for future use. */
3885             ReportBadXMLName(cx, IdToValue(id));
3886             goto bad;
3887         }
3888
3889         /*
3890          * Step 1 of ECMA-357 9.2.1.2 index case sets i to the property index.
3891          */
3892         i = index;
3893
3894         /* 2(a-b). */
3895         if (xml->xml_target) {
3896             ok = ResolveValue(cx, xml->xml_target, &rxml);
3897             if (!ok)
3898                 goto out;
3899             if (!rxml)
3900                 goto out;
3901             JS_ASSERT(rxml->object);
3902         } else {
3903             rxml = NULL;
3904         }
3905
3906         /* 2(c). */
3907         if (index >= xml->xml_kids.length) {
3908             /* 2(c)(i). */
3909             if (rxml) {
3910                 if (rxml->xml_class == JSXML_CLASS_LIST) {
3911                     if (rxml->xml_kids.length != 1)
3912                         goto out;
3913                     rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
3914                     if (!rxml)
3915                         goto out;
3916                     ok = js_GetXMLObject(cx, rxml) != NULL;
3917                     if (!ok)
3918                         goto out;
3919                 }
3920
3921                 /*
3922                  * Erratum: ECMA-357 9.2.1.2 step 2(c)(ii) sets
3923                  * _y.[[Parent]] = r_ where _r_ is the result of
3924                  * [[ResolveValue]] called on _x.[[TargetObject]] in
3925                  * 2(a)(i).  This can result in text parenting text:
3926                  *
3927                  *    var MYXML = new XML();
3928                  *    MYXML.appendChild(new XML("<TEAM>Giants</TEAM>"));
3929                  *
3930                  * (testcase from Werner Sharp <wsharp@macromedia.com>).
3931                  *
3932                  * To match insertChildAfter, insertChildBefore,
3933                  * prependChild, and setChildren, we should silently
3934                  * do nothing in this case.
3935                  */
3936                 if (!JSXML_HAS_KIDS(rxml))
3937                     goto out;
3938             }
3939
3940             /* 2(c)(ii) is distributed below as several js_NewXML calls. */
3941             targetprop = xml->xml_targetprop;
3942             if (!targetprop || IS_STAR(targetprop->getQNameLocalName())) {
3943                 /* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
3944                 kid = js_NewXML(cx, JSXML_CLASS_TEXT);
3945                 if (!kid)
3946                     goto bad;
3947             } else {
3948                 nameobj = targetprop;
3949                 if (nameobj->getClass() == &js_AttributeNameClass) {
3950                     /*
3951                      * 2(c)(iii)(1-3).
3952                      * Note that rxml can't be null here, because target
3953                      * and targetprop are non-null.
3954                      */
3955                     ok = GetProperty(cx, rxml->object, id, &attrval);
3956                     if (!ok)
3957                         goto out;
3958                     if (JSVAL_IS_PRIMITIVE(attrval))    /* no such attribute */
3959                         goto out;
3960                     attrobj = JSVAL_TO_OBJECT(attrval);
3961                     attr = (JSXML *) attrobj->getPrivate();
3962                     if (JSXML_LENGTH(attr) != 0)
3963                         goto out;
3964
3965                     kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
3966                 } else {
3967                     /* 2(c)(v). */
3968                     kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
3969                 }
3970                 if (!kid)
3971                     goto bad;
3972
3973                 /* An important bit of 2(c)(ii). */
3974                 kid->name = targetprop;
3975             }
3976
3977             /* Final important bit of 2(c)(ii). */
3978             kid->parent = rxml;
3979
3980             /* 2(c)(vi-vii). */
3981             i = xml->xml_kids.length;
3982             if (kid->xml_class != JSXML_CLASS_ATTRIBUTE) {
3983                 /*
3984                  * 2(c)(vii)(1) tests whether _y.[[Parent]]_ is not null.
3985                  * y.[[Parent]] is here called kid->parent, which we know
3986                  * from 2(c)(ii) is _r_, here called rxml.  So let's just
3987                  * test that!  Erratum, the spec should be simpler here.
3988                  */
3989                 if (rxml) {
3990                     JS_ASSERT(JSXML_HAS_KIDS(rxml));
3991                     n = rxml->xml_kids.length;
3992                     j = n - 1;
3993                     if (n != 0 && i != 0) {
3994                         for (n = j, j = 0; j < n; j++) {
3995                             if (rxml->xml_kids.vector[j] ==
3996                                 xml->xml_kids.vector[i-1]) {
3997                                 break;
3998                             }
3999                         }
4000                     }
4001
4002                     kidobj = js_GetXMLObject(cx, kid);
4003                     if (!kidobj)
4004                         goto bad;
4005                     ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
4006                     if (!ok)
4007                         goto out;
4008                 }
4009
4010                 /*
4011                  * 2(c)(vii)(2-3).
4012                  * Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
4013                  * typo for [[TargetProperty]].
4014                  */
4015                 if (vxml) {
4016                     kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
4017                         ? vxml->xml_targetprop
4018                         : vxml->name;
4019                 }
4020             }
4021
4022             /* 2(c)(viii). */
4023             ok = Append(cx, xml, kid);
4024             if (!ok)
4025                 goto out;
4026         }
4027
4028         /* 2(d). */
4029         if (!vxml ||
4030             vxml->xml_class == JSXML_CLASS_TEXT ||
4031             vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4032             ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4033             if (!ok)
4034                 goto out;
4035             roots[VAL_ROOT] = *vp;
4036         }
4037
4038         /* 2(e). */
4039         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
4040         if (!kid)
4041             goto out;
4042         parent = kid->parent;
4043         if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
4044             nameobj = kid->name;
4045             if (nameobj->getClass() != &js_AttributeNameClass) {
4046                 nameobj = NewXMLAttributeName(cx, nameobj->getNameURI(), nameobj->getNamePrefix(),
4047                                               nameobj->getQNameLocalName());
4048                 if (!nameobj)
4049                     goto bad;
4050             }
4051             id = OBJECT_TO_JSID(nameobj);
4052
4053             if (parent) {
4054                 /* 2(e)(i). */
4055                 parentobj = js_GetXMLObject(cx, parent);
4056                 if (!parentobj)
4057                     goto bad;
4058                 ok = PutProperty(cx, parentobj, id, strict, vp);
4059                 if (!ok)
4060                     goto out;
4061
4062                 /* 2(e)(ii). */
4063                 ok = GetProperty(cx, parentobj, id, vp);
4064                 if (!ok)
4065                     goto out;
4066                 attr = (JSXML *) JSVAL_TO_OBJECT(*vp)->getPrivate();
4067
4068                 /* 2(e)(iii) - the length check comes from the bug 375406. */
4069                 if (attr->xml_kids.length != 0)
4070                     xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
4071             }
4072         }
4073
4074         /* 2(f). */
4075         else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4076             /*
4077              * 2(f)(i)
4078              *
4079              * Erratum: the spec says to create a shallow copy _c_ of _V_, but
4080              * if we do that we never change the parent of each child in the
4081              * list.  Since [[Put]] when called on an XML object deeply copies
4082              * the provided list _V_, we also do so here.  Perhaps the shallow
4083              * copy was a misguided optimization?
4084              */
4085             copy = DeepCopyInLRS(cx, vxml, 0);
4086             if (!copy)
4087                 goto bad;
4088             copyobj = js_GetXMLObject(cx, copy);
4089             if (!copyobj)
4090                 goto bad;
4091
4092             JS_ASSERT(parent != xml);
4093             if (parent) {
4094                 q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4095                 JS_ASSERT(q != XML_NOT_FOUND);
4096                 ok = Replace(cx, parent, q, OBJECT_TO_JSVAL(copyobj));
4097                 if (!ok)
4098                     goto out;
4099
4100 #ifdef DEBUG
4101                 /* Erratum: this loop in the spec is useless. */
4102                 for (j = 0, n = copy->xml_kids.length; j < n; j++) {
4103                     kid2 = XMLARRAY_MEMBER(&parent->xml_kids, q + j, JSXML);
4104                     JS_ASSERT(XMLARRAY_MEMBER(&copy->xml_kids, j, JSXML)
4105                               == kid2);
4106                 }
4107 #endif
4108             }
4109
4110             /*
4111              * 2(f)(iv-vi).
4112              * Erratum: notice the unhandled zero-length V basis case and
4113              * the off-by-one errors for the n != 0 cases in the spec.
4114              */
4115             n = copy->xml_kids.length;
4116             if (n == 0) {
4117                 XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
4118             } else {
4119                 ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
4120                 if (!ok)
4121                     goto out;
4122
4123                 for (j = 0; j < n; j++)
4124                     xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
4125             }
4126         }
4127
4128         /* 2(g). */
4129         else if (vxml || JSXML_HAS_VALUE(kid)) {
4130             if (parent) {
4131                 q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4132                 JS_ASSERT(q != XML_NOT_FOUND);
4133                 ok = Replace(cx, parent, q, *vp);
4134                 if (!ok)
4135                     goto out;
4136
4137                 vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
4138                 if (!vxml)
4139                     goto out;
4140                 roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vxml->object);
4141             }
4142
4143             /*
4144              * 2(g)(iii).
4145              * Erratum: _V_ may not be of type XML, but all index-named
4146              * properties _x[i]_ in an XMLList _x_ must be of type XML,
4147              * according to 9.2.1.1 Overview and other places in the spec.
4148              *
4149              * Thanks to 2(d), we know _V_ (*vp here) is either a string
4150              * or an XML/XMLList object.  If *vp is a string, call ToXML
4151              * on it to satisfy the constraint.
4152              */
4153             if (!vxml) {
4154                 JS_ASSERT(JSVAL_IS_STRING(*vp));
4155                 vobj = ToXML(cx, *vp);
4156                 if (!vobj)
4157                     goto bad;
4158                 roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vobj);
4159                 vxml = (JSXML *) vobj->getPrivate();
4160             }
4161             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
4162         }
4163
4164         /* 2(h). */
4165         else {
4166             kidobj = js_GetXMLObject(cx, kid);
4167             if (!kidobj)
4168                 goto bad;
4169             id = ATOM_TO_JSID(cx->runtime->atomState.starAtom);
4170             ok = PutProperty(cx, kidobj, id, strict, vp);
4171             if (!ok)
4172                 goto out;
4173         }
4174     } else {
4175         /*
4176          * ECMA-357 9.2.1.2/9.1.1.2 qname case.
4177          */
4178         nameqn = ToXMLName(cx, IdToJsval(id), &funid);
4179         if (!nameqn)
4180             goto bad;
4181         if (!JSID_IS_VOID(funid)) {
4182             ok = js_SetProperty(cx, obj, funid, Valueify(vp), false);
4183             goto out;
4184         }
4185         nameobj = nameqn;
4186         roots[ID_ROOT] = OBJECT_TO_JSVAL(nameobj);
4187
4188         if (xml->xml_class == JSXML_CLASS_LIST) {
4189             /*
4190              * Step 3 of 9.2.1.2.
4191              * Erratum: if x.[[Length]] > 1 or [[ResolveValue]] returns null
4192              * or an r with r.[[Length]] != 1, throw TypeError.
4193              */
4194             n = JSXML_LENGTH(xml);
4195             if (n > 1)
4196                 goto type_error;
4197             if (n == 0) {
4198                 ok = ResolveValue(cx, xml, &rxml);
4199                 if (!ok)
4200                     goto out;
4201                 if (!rxml || JSXML_LENGTH(rxml) != 1)
4202                     goto type_error;
4203                 ok = Append(cx, xml, rxml);
4204                 if (!ok)
4205                     goto out;
4206             }
4207             JS_ASSERT(JSXML_LENGTH(xml) == 1);
4208             xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
4209             if (!xml)
4210                 goto out;
4211             JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
4212             obj = js_GetXMLObject(cx, xml);
4213             if (!obj)
4214                 goto bad;
4215             roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
4216
4217             /* FALL THROUGH to non-list case */
4218         }
4219
4220         /*
4221          * ECMA-357 9.1.1.2.
4222          * Erratum: move steps 3 and 4 to before 1 and 2, to avoid wasted
4223          * effort in ToString or [[DeepCopy]].
4224          */
4225
4226         if (JSXML_HAS_VALUE(xml))
4227             goto out;
4228
4229         if (!vxml ||
4230             vxml->xml_class == JSXML_CLASS_TEXT ||
4231             vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4232             ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4233             if (!ok)
4234                 goto out;
4235         } else {
4236             rxml = DeepCopyInLRS(cx, vxml, 0);
4237             if (!rxml || !js_GetXMLObject(cx, rxml))
4238                 goto bad;
4239             vxml = rxml;
4240             *vp = OBJECT_TO_JSVAL(vxml->object);
4241         }
4242         roots[VAL_ROOT] = *vp;
4243
4244         /*
4245          * 6.
4246          * Erratum: why is this done here, so early? use is way later....
4247          */
4248         ok = js_GetDefaultXMLNamespace(cx, &nsval);
4249         if (!ok)
4250             goto out;
4251
4252         if (nameobj->getClass() == &js_AttributeNameClass) {
4253             /* 7(a). */
4254             if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
4255                 goto out;
4256
4257             /* 7(b-c). */
4258             if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4259                 n = vxml->xml_kids.length;
4260                 if (n == 0) {
4261                     *vp = STRING_TO_JSVAL(cx->runtime->emptyString);
4262                 } else {
4263                     JSString *left = KidToString(cx, vxml, 0);
4264                     if (!left)
4265                         goto bad;
4266
4267                     JSString *space = cx->runtime->atomState.spaceAtom;
4268                     for (i = 1; i < n; i++) {
4269                         left = js_ConcatStrings(cx, left, space);
4270                         if (!left)
4271                             goto bad;
4272                         JSString *right = KidToString(cx, vxml, i);
4273                         if (!right)
4274                             goto bad;
4275                         left = js_ConcatStrings(cx, left, right);
4276                         if (!left)
4277                             goto bad;
4278                     }
4279
4280                     roots[VAL_ROOT] = *vp = STRING_TO_JSVAL(left);
4281                 }
4282             } else {
4283                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4284                 if (!ok)
4285                     goto out;
4286                 roots[VAL_ROOT] = *vp;
4287             }
4288
4289             /* 7(d-e). */
4290             match = NULL;
4291             for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
4292                 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
4293                 if (!attr)
4294                     continue;
4295                 attrqn = attr->name;
4296                 if (EqualStrings(attrqn->getQNameLocalName(), nameqn->getQNameLocalName())) {
4297                     JSLinearString *uri = nameqn->getNameURI();
4298                     if (!uri || EqualStrings(attrqn->getNameURI(), uri)) {
4299                         if (!match) {
4300                             match = attr;
4301                         } else {
4302                             DeleteNamedProperty(cx, xml, attrqn, JS_TRUE);
4303                             --i;
4304                         }
4305                     }
4306                 }
4307             }
4308
4309             /* 7(f). */
4310             attr = match;
4311             if (!attr) {
4312                 /* 7(f)(i-ii). */
4313                 JSLinearString *uri = nameqn->getNameURI();
4314                 JSLinearString *left, *right;
4315                 if (!uri) {
4316                     left = right = cx->runtime->emptyString;
4317                 } else {
4318                     left = uri;
4319                     right = nameqn->getNamePrefix();
4320                 }
4321                 nameqn = NewXMLQName(cx, left, right, nameqn->getQNameLocalName());
4322                 if (!nameqn)
4323                     goto bad;
4324
4325                 /* 7(f)(iii). */
4326                 attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4327                 if (!attr)
4328                     goto bad;
4329                 attr->parent = xml;
4330                 attr->name = nameqn;
4331
4332                 /* 7(f)(iv). */
4333                 ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
4334                 if (!ok)
4335                     goto out;
4336
4337                 /* 7(f)(v-vi). */
4338                 ns = GetNamespace(cx, nameqn, NULL);
4339                 if (!ns)
4340                     goto bad;
4341                 ok = AddInScopeNamespace(cx, xml, ns);
4342                 if (!ok)
4343                     goto out;
4344             }
4345
4346             /* 7(g). */
4347             attr->xml_value = JSVAL_TO_STRING(*vp);
4348             goto out;
4349         }
4350
4351         /* 8-9. */
4352         if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
4353             !IS_STAR(nameqn->getQNameLocalName())) {
4354             goto out;
4355         }
4356
4357         /* 10-11. */
4358         id = JSID_VOID;
4359         primitiveAssign = !vxml && !IS_STAR(nameqn->getQNameLocalName());
4360
4361         /* 12. */
4362         k = n = xml->xml_kids.length;
4363         matchIndex = XML_NOT_FOUND;
4364         kid2 = NULL;
4365         while (k != 0) {
4366             --k;
4367             kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
4368             if (kid && MatchElemName(nameqn, kid)) {
4369                 if (matchIndex != XML_NOT_FOUND)
4370                     DeleteByIndex(cx, xml, matchIndex);
4371                 matchIndex = k;
4372                 kid2 = kid;
4373             }
4374         }
4375
4376         /*
4377          * Erratum: ECMA-357 specified child insertion inconsistently:
4378          * insertChildBefore and insertChildAfter insert an arbitrary XML
4379          * instance, and therefore can create cycles, but appendChild as
4380          * specified by the "Overview" of 13.4.4.3 calls [[DeepCopy]] on
4381          * its argument.  But the "Semantics" in 13.4.4.3 do not include
4382          * any [[DeepCopy]] call.
4383          *
4384          * Fixing this (https://bugzilla.mozilla.org/show_bug.cgi?id=312692)
4385          * required adding cycle detection, and allowing duplicate kids to
4386          * be created (see comment 6 in the bug).  Allowing duplicate kid
4387          * references means the loop above will delete all but the lowest
4388          * indexed reference, and each [[DeleteByIndex]] nulls the kid's
4389          * parent.  Thus the need to restore parent here.  This is covered
4390          * by https://bugzilla.mozilla.org/show_bug.cgi?id=327564.
4391          */
4392         if (kid2) {
4393             JS_ASSERT(kid2->parent == xml || !kid2->parent);
4394             if (!kid2->parent)
4395                 kid2->parent = xml;
4396         }
4397
4398         /* 13. */
4399         if (matchIndex == XML_NOT_FOUND) {
4400             /* 13(a). */
4401             matchIndex = n;
4402
4403             /* 13(b). */
4404             if (primitiveAssign) {
4405                 JSLinearString *uri = nameqn->getNameURI();
4406                 JSLinearString *left, *right;
4407                 if (!uri) {
4408                     ns = JSVAL_TO_OBJECT(nsval);
4409                     left = ns->getNameURI();
4410                     right = ns->getNamePrefix();
4411                 } else {
4412                     left = uri;
4413                     right = nameqn->getNamePrefix();
4414                 }
4415                 nameqn = NewXMLQName(cx, left, right, nameqn->getQNameLocalName());
4416                 if (!nameqn)
4417                     goto bad;
4418
4419                 /* 13(b)(iii). */
4420                 vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
4421                 if (!vobj)
4422                     goto bad;
4423                 vxml = (JSXML *) vobj->getPrivate();
4424                 vxml->parent = xml;
4425                 vxml->name = nameqn;
4426
4427                 /* 13(b)(iv-vi). */
4428                 ns = GetNamespace(cx, nameqn, NULL);
4429                 if (!ns)
4430                     goto bad;
4431                 ok = Replace(cx, xml, matchIndex, OBJECT_TO_JSVAL(vobj));
4432                 if (!ok)
4433                     goto out;
4434                 ok = AddInScopeNamespace(cx, vxml, ns);
4435                 if (!ok)
4436                     goto out;
4437             }
4438         }
4439
4440         /* 14. */
4441         if (primitiveAssign) {
4442             JSXMLArrayCursor cursor(&xml->xml_kids);
4443             cursor.index = matchIndex;
4444             kid = (JSXML *) cursor.getCurrent();
4445             if (JSXML_HAS_KIDS(kid)) {
4446                 kid->xml_kids.finish(cx);
4447                 kid->xml_kids.init();
4448                 ok = kid->xml_kids.setCapacity(cx, 1);
4449             }
4450
4451             /* 14(b-c). */
4452             /* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
4453             if (ok) {
4454                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4455                 if (ok && !JSVAL_TO_STRING(*vp)->empty()) {
4456                     roots[VAL_ROOT] = *vp;
4457                     if ((JSXML *) cursor.getCurrent() == kid)
4458                         ok = Replace(cx, kid, 0, *vp);
4459                 }
4460             }
4461         } else {
4462             /* 15(a). */
4463             ok = Replace(cx, xml, matchIndex, *vp);
4464         }
4465     }
4466
4467 out:
4468     js_LeaveLocalRootScope(cx);
4469     return ok;
4470
4471 type_error:
4472     {
4473         JSAutoByteString bytes;
4474         if (js_ValueToPrintable(cx, IdToValue(id), &bytes))
4475             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_XMLLIST_PUT, bytes.ptr());
4476     }
4477 bad:
4478     ok = JS_FALSE;
4479     goto out;
4480 }
4481
4482 /* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
4483 static JSBool
4484 ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
4485 {
4486     JSXML *target, *base;
4487     JSObject *targetprop;
4488     jsid id;
4489     jsval tv;
4490
4491     if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
4492         if (!js_GetXMLObject(cx, list))
4493             return JS_FALSE;
4494         *result = list;
4495         return JS_TRUE;
4496     }
4497
4498     target = list->xml_target;
4499     targetprop = list->xml_targetprop;
4500     if (!target || !targetprop || IS_STAR(targetprop->getQNameLocalName())) {
4501         *result = NULL;
4502         return JS_TRUE;
4503     }
4504
4505     if (targetprop->getClass() == &js_AttributeNameClass) {
4506         *result = NULL;
4507         return JS_TRUE;
4508     }
4509
4510     if (!ResolveValue(cx, target, &base))
4511         return JS_FALSE;
4512     if (!base) {
4513         *result = NULL;
4514         return JS_TRUE;
4515     }
4516     if (!js_GetXMLObject(cx, base))
4517         return JS_FALSE;
4518
4519     id = OBJECT_TO_JSID(targetprop);
4520     if (!GetProperty(cx, base->object, id, &tv))
4521         return JS_FALSE;
4522     target = (JSXML *) JSVAL_TO_OBJECT(tv)->getPrivate();
4523
4524     if (JSXML_LENGTH(target) == 0) {
4525         if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
4526             *result = NULL;
4527             return JS_TRUE;
4528         }
4529         tv = STRING_TO_JSVAL(cx->runtime->emptyString);
4530         if (!PutProperty(cx, base->object, id, false, &tv))
4531             return JS_FALSE;
4532         if (!GetProperty(cx, base->object, id, &tv))
4533             return JS_FALSE;
4534         target = (JSXML *) JSVAL_TO_OBJECT(tv)->getPrivate();
4535     }
4536
4537     *result = target;
4538     return JS_TRUE;
4539 }
4540
4541 static JSBool
4542 HasNamedProperty(JSXML *xml, JSObject *nameqn)
4543 {
4544     JSBool found;
4545     JSXMLArray *array;
4546     JSXMLNameMatcher matcher;
4547     uint32 i, n;
4548
4549     if (xml->xml_class == JSXML_CLASS_LIST) {
4550         found = JS_FALSE;
4551         JSXMLArrayCursor cursor(&xml->xml_kids);
4552         while (JSXML *kid = (JSXML *) cursor.getNext()) {
4553             found = HasNamedProperty(kid, nameqn);
4554             if (found)
4555                 break;
4556         }
4557         return found;
4558     }
4559
4560     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
4561         if (nameqn->getClass() == &js_AttributeNameClass) {
4562             array = &xml->xml_attrs;
4563             matcher = MatchAttrName;
4564         } else {
4565             array = &xml->xml_kids;
4566             matcher = MatchElemName;
4567         }
4568         for (i = 0, n = array->length; i < n; i++) {
4569             JSXML *kid = XMLARRAY_MEMBER(array, i, JSXML);
4570             if (kid && matcher(nameqn, kid))
4571                 return JS_TRUE;
4572         }
4573     }
4574
4575     return JS_FALSE;
4576 }
4577
4578 static JSBool
4579 HasIndexedProperty(JSXML *xml, uint32 i)
4580 {
4581     if (xml->xml_class == JSXML_CLASS_LIST)
4582         return i < JSXML_LENGTH(xml);
4583
4584     if (xml->xml_class == JSXML_CLASS_ELEMENT)
4585         return i == 0;
4586
4587     return JS_FALSE;
4588 }
4589
4590 static JSBool
4591 HasSimpleContent(JSXML *xml);
4592
4593 static JSBool
4594 HasFunctionProperty(JSContext *cx, JSObject *obj, jsid funid, JSBool *found)
4595 {
4596     JSObject *pobj;
4597     JSProperty *prop;
4598     JSXML *xml;
4599
4600     JS_ASSERT(obj->getClass() == &js_XMLClass);
4601
4602     if (!js_LookupProperty(cx, obj, funid, &pobj, &prop))
4603         return false;
4604     if (!prop) {
4605         xml = (JSXML *) obj->getPrivate();
4606         if (HasSimpleContent(xml)) {
4607             AutoObjectRooter tvr(cx);
4608
4609             /*
4610              * Search in String.prototype to set found whenever
4611              * GetXMLFunction returns existing function.
4612              */
4613             if (!js_GetClassPrototype(cx, NULL, JSProto_String, tvr.addr()))
4614                 return false;
4615
4616             JS_ASSERT(tvr.object());
4617             if (!js_LookupProperty(cx, tvr.object(), funid, &pobj, &prop))
4618                 return false;
4619         }
4620     }
4621     *found = (prop != NULL);
4622     return true;
4623 }
4624
4625 /* ECMA-357 9.1.1.6 XML [[HasProperty]] and 9.2.1.5 XMLList [[HasProperty]]. */
4626 static JSBool
4627 HasProperty(JSContext *cx, JSObject *obj, jsval id, JSBool *found)
4628 {
4629     JSXML *xml;
4630     bool isIndex;
4631     uint32 i;
4632     JSObject *qn;
4633     jsid funid;
4634
4635     xml = (JSXML *) obj->getPrivate();
4636     if (!js_IdValIsIndex(cx, id, &i, &isIndex))
4637         return JS_FALSE;
4638
4639     if (isIndex) {
4640         *found = HasIndexedProperty(xml, i);
4641     } else {
4642         qn = ToXMLName(cx, id, &funid);
4643         if (!qn)
4644             return JS_FALSE;
4645         if (!JSID_IS_VOID(funid)) {
4646             if (!HasFunctionProperty(cx, obj, funid, found))
4647                 return JS_FALSE;
4648         } else {
4649             *found = HasNamedProperty(xml, qn);
4650         }
4651     }
4652     return JS_TRUE;
4653 }
4654
4655 static void
4656 xml_finalize(JSContext *cx, JSObject *obj)
4657 {
4658     JSXML *xml = (JSXML *) obj->getPrivate();
4659     if (!xml)
4660         return;
4661     if (xml->object == obj)
4662         xml->object = NULL;
4663 }
4664
4665 static void
4666 xml_trace_vector(JSTracer *trc, JSXML **vec, uint32 len)
4667 {
4668     uint32 i;
4669     JSXML *xml;
4670
4671     for (i = 0; i < len; i++) {
4672         xml = vec[i];
4673         if (xml) {
4674             JS_SET_TRACING_INDEX(trc, "xml_vector", i);
4675             Mark(trc, xml);
4676         }
4677     }
4678 }
4679
4680 /*
4681  * XML objects are native. Thus xml_lookupProperty must return a valid
4682  * Shape pointer parameter via *propp to signify "property found". Since the
4683  * only call to xml_lookupProperty is via JSObject::lookupProperty, and then
4684  * only from js_FindProperty (in jsobj.c, called from jsinterp.c) or from
4685  * JSOP_IN case in the interpreter, the only time we add a Shape here is when
4686  * an unqualified name is being accessed or when "name in xml" is called.
4687  *
4688  * This scope property keeps the JSOP_NAME code in js_Interpret happy by
4689  * giving it an shape with (getter, setter) == (GetProperty, PutProperty).
4690  *
4691  * NB: xml_deleteProperty must take care to remove any property added here.
4692  *
4693  * FIXME This clashes with the function namespace implementation which also
4694  * uses native properties. Effectively after xml_lookupProperty any property
4695  * stored previously using assignments to xml.function::name will be removed.
4696  * We partially workaround the problem in GetXMLFunction. There we take
4697  * advantage of the fact that typically function:: is used to access the
4698  * functions from XML.prototype. So when js_GetProperty returns a non-function
4699  * property, we assume that it represents the result of GetProperty setter
4700  * hiding the function and use an extra prototype chain lookup to recover it.
4701  * For a proper solution see bug 355257.
4702 */
4703 static JSBool
4704 xml_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
4705                    JSProperty **propp)
4706 {
4707     JSBool found;
4708     JSXML *xml;
4709     uint32 i;
4710     JSObject *qn;
4711     jsid funid;
4712
4713     xml = (JSXML *) obj->getPrivate();
4714     if (js_IdIsIndex(id, &i)) {
4715         found = HasIndexedProperty(xml, i);
4716     } else {
4717         qn = ToXMLName(cx, IdToJsval(id), &funid);
4718         if (!qn)
4719             return JS_FALSE;
4720         if (!JSID_IS_VOID(funid))
4721             return js_LookupProperty(cx, obj, funid, objp, propp);
4722         found = HasNamedProperty(xml, qn);
4723     }
4724     if (!found) {
4725         *objp = NULL;
4726         *propp = NULL;
4727     } else {
4728         const Shape *shape =
4729             js_AddNativeProperty(cx, obj, id,
4730                                  Valueify(GetProperty), Valueify(PutProperty),
4731                                  SHAPE_INVALID_SLOT, JSPROP_ENUMERATE,
4732                                  0, 0);
4733         if (!shape)
4734             return JS_FALSE;
4735
4736         *objp = obj;
4737         *propp = (JSProperty *) shape;
4738     }
4739     return JS_TRUE;
4740 }
4741
4742 static JSBool
4743 xml_defineProperty(JSContext *cx, JSObject *obj, jsid id, const Value *v,
4744                    PropertyOp getter, StrictPropertyOp setter, uintN attrs)
4745 {
4746     if (IsFunctionObject(*v) || getter || setter ||
4747         (attrs & JSPROP_ENUMERATE) == 0 ||
4748         (attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
4749         return js_DefineProperty(cx, obj, id, v, getter, setter, attrs);
4750     }
4751
4752     jsval tmp = Jsvalify(*v);
4753     return PutProperty(cx, obj, id, false, &tmp);
4754 }
4755
4756 static JSBool
4757 xml_getProperty(JSContext *cx, JSObject *obj, JSObject *receiver, jsid id, Value *vp)
4758 {
4759     if (JSID_IS_DEFAULT_XML_NAMESPACE(id)) {
4760         vp->setUndefined();
4761         return JS_TRUE;
4762     }
4763
4764     return GetProperty(cx, obj, id, Jsvalify(vp));
4765 }
4766
4767 static JSBool
4768 xml_setProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool strict)
4769 {
4770     return PutProperty(cx, obj, id, strict, Jsvalify(vp));
4771 }
4772
4773 static JSBool
4774 xml_getAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
4775 {
4776     JSBool found;
4777     if (!HasProperty(cx, obj, IdToJsval(id), &found))
4778         return false;
4779
4780     *attrsp = found ? JSPROP_ENUMERATE : 0;
4781     return JS_TRUE;
4782 }
4783
4784 static JSBool
4785 xml_setAttributes(JSContext *cx, JSObject *obj, jsid id, uintN *attrsp)
4786 {
4787     JSBool found;
4788     if (!HasProperty(cx, obj, IdToJsval(id), &found))
4789         return false;
4790
4791     if (found) {
4792         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4793                              JSMSG_CANT_SET_XML_ATTRS);
4794         return false;
4795     }
4796     return true;
4797 }
4798
4799 static JSBool
4800 xml_deleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool strict)
4801 {
4802     JSXML *xml;
4803     jsval idval;
4804     uint32 index;
4805     JSObject *nameqn;
4806     jsid funid;
4807
4808     idval = IdToJsval(id);
4809     xml = (JSXML *) obj->getPrivate();
4810     if (js_IdIsIndex(id, &index)) {
4811         if (xml->xml_class != JSXML_CLASS_LIST) {
4812             /* See NOTE in spec: this variation is reserved for future use. */
4813             ReportBadXMLName(cx, IdToValue(id));
4814             return false;
4815         }
4816
4817         /* ECMA-357 9.2.1.3. */
4818         DeleteListElement(cx, xml, index);
4819     } else {
4820         nameqn = ToXMLName(cx, idval, &funid);
4821         if (!nameqn)
4822             return false;
4823         if (!JSID_IS_VOID(funid))
4824             return js_DeleteProperty(cx, obj, funid, rval, false);
4825
4826         DeleteNamedProperty(cx, xml, nameqn,
4827                             nameqn->getClass() == &js_AttributeNameClass);
4828     }
4829
4830     /*
4831      * If this object has its own (mutable) scope,  then we may have added a
4832      * property to the scope in xml_lookupProperty for it to return to mean
4833      * "found" and to provide a handle for access operations to call the
4834      * property's getter or setter. But now it's time to remove any such
4835      * property, to purge the property cache and remove the scope entry.
4836      */
4837     if (!obj->nativeEmpty() && !js_DeleteProperty(cx, obj, id, rval, false))
4838         return false;
4839
4840     rval->setBoolean(true);
4841     return true;
4842 }
4843
4844 JSBool
4845 xml_convert(JSContext *cx, JSObject *obj, JSType type, Value *rval)
4846 {
4847     return js_TryMethod(cx, obj, cx->runtime->atomState.toStringAtom, 0, NULL, rval);
4848 }
4849
4850 static JSBool
4851 xml_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, Value *statep, jsid *idp)
4852 {
4853     JSXML *xml;
4854     uint32 length, index;
4855     JSXMLArrayCursor *cursor;
4856
4857     xml = (JSXML *)obj->getPrivate();
4858     length = JSXML_LENGTH(xml);
4859
4860     switch (enum_op) {
4861       case JSENUMERATE_INIT:
4862       case JSENUMERATE_INIT_ALL:
4863         if (length == 0) {
4864             statep->setInt32(0);
4865         } else {
4866             cursor = cx->create<JSXMLArrayCursor>(&xml->xml_kids);
4867             if (!cursor)
4868                 return JS_FALSE;
4869             statep->setPrivate(cursor);
4870         }
4871         if (idp)
4872             *idp = INT_TO_JSID(length);
4873         break;
4874
4875       case JSENUMERATE_NEXT:
4876         if (statep->isInt32(0)) {
4877             statep->setNull();
4878             break;
4879         }
4880         cursor = (JSXMLArrayCursor *) statep->toPrivate();
4881         if (cursor && cursor->array && (index = cursor->index) < length) {
4882             *idp = INT_TO_JSID(index);
4883             cursor->index = index + 1;
4884             break;
4885         }
4886         /* FALL THROUGH */
4887
4888       case JSENUMERATE_DESTROY:
4889         if (!statep->isInt32(0)) {
4890             cursor = (JSXMLArrayCursor *) statep->toPrivate();
4891             if (cursor)
4892                 cx->destroy(cursor);
4893         }
4894         statep->setNull();
4895         break;
4896     }
4897     return JS_TRUE;
4898 }
4899
4900 static JSType
4901 xml_typeOf(JSContext *cx, JSObject *obj)
4902 {
4903     return JSTYPE_XML;
4904 }
4905
4906 static JSBool
4907 xml_hasInstance(JSContext *cx, JSObject *obj, const Value *, JSBool *bp)
4908 {
4909     return JS_TRUE;
4910 }
4911
4912 static void
4913 xml_trace(JSTracer *trc, JSObject *obj)
4914 {
4915     JSXML *xml = (JSXML *) obj->getPrivate();
4916     if (xml)
4917         JS_CALL_TRACER(trc, xml, JSTRACE_XML, "private");
4918 }
4919
4920 static JSBool
4921 xml_fix(JSContext *cx, JSObject *obj, bool *success, AutoIdVector *props)
4922 {
4923     JS_ASSERT(obj->isExtensible());
4924     *success = false;
4925     return true;
4926 }
4927
4928 static void
4929 xml_clear(JSContext *cx, JSObject *obj)
4930 {
4931 }
4932
4933 static JSBool
4934 HasSimpleContent(JSXML *xml)
4935 {
4936     JSXML *kid;
4937     JSBool simple;
4938     uint32 i, n;
4939
4940 again:
4941     switch (xml->xml_class) {
4942       case JSXML_CLASS_COMMENT:
4943       case JSXML_CLASS_PROCESSING_INSTRUCTION:
4944         return JS_FALSE;
4945       case JSXML_CLASS_LIST:
4946         if (xml->xml_kids.length == 0)
4947             return JS_TRUE;
4948         if (xml->xml_kids.length == 1) {
4949             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
4950             if (kid) {
4951                 xml = kid;
4952                 goto again;
4953             }
4954         }
4955         /* FALL THROUGH */
4956       default:
4957         simple = JS_TRUE;
4958         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
4959             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
4960             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
4961                 simple = JS_FALSE;
4962                 break;
4963             }
4964         }
4965         return simple;
4966     }
4967 }
4968
4969 /*
4970  * 11.2.2.1 Step 3(d) onward.
4971  */
4972 JSBool
4973 js_GetXMLMethod(JSContext *cx, JSObject *obj, jsid id, Value *vp)
4974 {
4975     JS_ASSERT(JS_InstanceOf(cx, obj, Jsvalify(&js_XMLClass), NULL));
4976
4977     if (JSID_IS_OBJECT(id)) {
4978         jsid funid;
4979
4980         if (!js_IsFunctionQName(cx, JSID_TO_OBJECT(id), &funid))
4981             return JS_FALSE;
4982         if (!JSID_IS_VOID(funid))
4983             id = funid;
4984     }
4985
4986     /*
4987      * As our callers have a bad habit of passing a pointer to an unrooted
4988      * local value as vp, we use a proper root here.
4989      */
4990     AutoValueRooter tvr(cx);
4991     JSBool ok = GetXMLFunction(cx, obj, id, Jsvalify(tvr.addr()));
4992     *vp = tvr.value();
4993     return ok;
4994 }
4995
4996 JSBool
4997 js_TestXMLEquality(JSContext *cx, const Value &v1, const Value &v2, JSBool *bp)
4998 {
4999     JSXML *xml, *vxml;
5000     JSObject *vobj;
5001     JSBool ok;
5002     JSString *str, *vstr;
5003     jsdouble d, d2;
5004
5005     JSObject *obj;
5006     jsval v;
5007     if (v1.isObject() && v1.toObject().isXML()) {
5008         obj = &v1.toObject();
5009         v = Jsvalify(v2);
5010     } else {
5011         v = Jsvalify(v1);
5012         obj = &v2.toObject();
5013     }
5014
5015     JS_ASSERT(JS_InstanceOf(cx, obj, Jsvalify(&js_XMLClass), NULL));
5016
5017     xml = (JSXML *) obj->getPrivate();
5018     vxml = NULL;
5019     if (!JSVAL_IS_PRIMITIVE(v)) {
5020         vobj = JSVAL_TO_OBJECT(v);
5021         if (vobj->isXML())
5022             vxml = (JSXML *) vobj->getPrivate();
5023     }
5024
5025     if (xml->xml_class == JSXML_CLASS_LIST) {
5026         ok = Equals(cx, xml, v, bp);
5027     } else if (vxml) {
5028         if (vxml->xml_class == JSXML_CLASS_LIST) {
5029             ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
5030         } else {
5031             if (((xml->xml_class == JSXML_CLASS_TEXT ||
5032                   xml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5033                  HasSimpleContent(vxml)) ||
5034                 ((vxml->xml_class == JSXML_CLASS_TEXT ||
5035                   vxml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5036                  HasSimpleContent(xml))) {
5037                 ok = js_EnterLocalRootScope(cx);
5038                 if (ok) {
5039                     ok = (str = js_ValueToString(cx, ObjectValue(*obj))) &&
5040                          (vstr = js_ValueToString(cx, Valueify(v)));
5041                     if (ok)
5042                         ok = EqualStrings(cx, str, vstr, bp);
5043                     js_LeaveLocalRootScope(cx);
5044                 }
5045             } else {
5046                 ok = XMLEquals(cx, xml, vxml, bp);
5047             }
5048         }
5049     } else {
5050         ok = js_EnterLocalRootScope(cx);
5051         if (ok) {
5052             if (HasSimpleContent(xml)) {
5053                 ok = (str = js_ValueToString(cx, ObjectValue(*obj))) &&
5054                      (vstr = js_ValueToString(cx, Valueify(v)));
5055                 if (ok)
5056                     ok = EqualStrings(cx, str, vstr, bp);
5057             } else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
5058                 str = js_ValueToString(cx, ObjectValue(*obj));
5059                 if (!str) {
5060                     ok = JS_FALSE;
5061                 } else if (JSVAL_IS_STRING(v)) {
5062                     ok = EqualStrings(cx, str, JSVAL_TO_STRING(v), bp);
5063                 } else {
5064                     ok = JS_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
5065                     if (ok) {
5066                         d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
5067                                              : JSVAL_TO_DOUBLE(v);
5068                         *bp = JSDOUBLE_COMPARE(d, ==, d2, JS_FALSE);
5069                     }
5070                 }
5071             } else {
5072                 *bp = JS_FALSE;
5073             }
5074             js_LeaveLocalRootScope(cx);
5075         }
5076     }
5077     return ok;
5078 }
5079
5080 JSBool
5081 js_ConcatenateXML(JSContext *cx, JSObject *obj, JSObject *robj, Value *vp)
5082 {
5083     JSBool ok;
5084     JSObject *listobj;
5085     JSXML *list, *lxml, *rxml;
5086
5087     JS_ASSERT(JS_InstanceOf(cx, obj, Jsvalify(&js_XMLClass), NULL));
5088     ok = js_EnterLocalRootScope(cx);
5089     if (!ok)
5090         return JS_FALSE;
5091
5092     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5093     if (!listobj) {
5094         ok = JS_FALSE;
5095         goto out;
5096     }
5097
5098     list = (JSXML *) listobj->getPrivate();
5099     lxml = (JSXML *) obj->getPrivate();
5100     ok = Append(cx, list, lxml);
5101     if (!ok)
5102         goto out;
5103
5104     JS_ASSERT(robj->isXML());
5105     rxml = (JSXML *) robj->getPrivate();
5106     ok = Append(cx, list, rxml);
5107     if (!ok)
5108         goto out;
5109
5110     vp->setObject(*listobj);
5111 out:
5112     js_LeaveLocalRootScopeWithResult(cx, *vp);
5113     return ok;
5114 }
5115
5116 JS_FRIEND_DATA(Class) js_XMLClass = {
5117     js_XML_str,
5118     JSCLASS_HAS_PRIVATE | JSCLASS_MARK_IS_TRACE |
5119     JSCLASS_HAS_CACHED_PROTO(JSProto_XML),
5120     PropertyStub,         /* addProperty */
5121     PropertyStub,         /* delProperty */
5122     PropertyStub,         /* getProperty */
5123     StrictPropertyStub,   /* setProperty */
5124     EnumerateStub,
5125     ResolveStub,
5126     xml_convert,
5127     xml_finalize,
5128     NULL,                 /* reserved0   */
5129     NULL,                 /* checkAccess */
5130     NULL,                 /* call        */
5131     NULL,                 /* construct   */
5132     NULL,                 /* xdrObject   */
5133     xml_hasInstance,
5134     JS_CLASS_TRACE(xml_trace),
5135     JS_NULL_CLASS_EXT,
5136     {
5137         xml_lookupProperty,
5138         xml_defineProperty,
5139         xml_getProperty,
5140         xml_setProperty,
5141         xml_getAttributes,
5142         xml_setAttributes,
5143         xml_deleteProperty,
5144         xml_enumerate,
5145         xml_typeOf,
5146         NULL,       /* trace */
5147         xml_fix,
5148         NULL,       /* thisObject     */
5149         xml_clear
5150     }
5151 };
5152
5153 static JSXML *
5154 StartNonListXMLMethod(JSContext *cx, jsval *vp, JSObject **objp)
5155 {
5156     JSXML *xml;
5157     JSFunction *fun;
5158     char numBuf[12];
5159
5160     JS_ASSERT(VALUE_IS_FUNCTION(cx, *vp));
5161
5162     *objp = ToObject(cx, Valueify(&vp[1]));
5163     if (!*objp)
5164         return NULL;
5165     xml = (JSXML *) GetInstancePrivate(cx, *objp, &js_XMLClass, Valueify(vp + 2));
5166     if (!xml || xml->xml_class != JSXML_CLASS_LIST)
5167         return xml;
5168
5169     if (xml->xml_kids.length == 1) {
5170         xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5171         if (xml) {
5172             *objp = js_GetXMLObject(cx, xml);
5173             if (!*objp)
5174                 return NULL;
5175             vp[1] = OBJECT_TO_JSVAL(*objp);
5176             return xml;
5177         }
5178     }
5179
5180     fun = GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(*vp));
5181     JS_snprintf(numBuf, sizeof numBuf, "%u", xml->xml_kids.length);
5182     JSAutoByteString funNameBytes;
5183     if (const char *funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
5184         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NON_LIST_XML_METHOD,
5185                              funName, numBuf);
5186     }
5187     return NULL;
5188 }
5189
5190 /* Beware: these two are not bracketed by JS_BEGIN/END_MACRO. */
5191 #define XML_METHOD_PROLOG                                                     \
5192     JSObject *obj = ToObject(cx, Valueify(&vp[1]));                           \
5193     if (!obj)                                                                 \
5194         return JS_FALSE;                                                      \
5195     JSXML *xml = (JSXML *)GetInstancePrivate(cx, obj, &js_XMLClass, Valueify(vp+2)); \
5196     if (!xml)                                                                 \
5197         return JS_FALSE
5198
5199 #define NON_LIST_XML_METHOD_PROLOG                                            \
5200     JSObject *obj;                                                            \
5201     JSXML *xml = StartNonListXMLMethod(cx, vp, &obj);                         \
5202     if (!xml)                                                                 \
5203         return JS_FALSE;                                                      \
5204     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST)
5205
5206 static JSBool
5207 xml_addNamespace(JSContext *cx, uintN argc, jsval *vp)
5208 {
5209     JSObject *ns;
5210
5211     NON_LIST_XML_METHOD_PROLOG;
5212     if (xml->xml_class != JSXML_CLASS_ELEMENT)
5213         goto done;
5214     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5215     if (!xml)
5216         return JS_FALSE;
5217
5218     if (!NamespaceHelper(cx, NULL, argc == 0 ? -1 : 1, vp + 2, vp))
5219         return JS_FALSE;
5220     JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
5221
5222     ns = JSVAL_TO_OBJECT(*vp);
5223     if (!AddInScopeNamespace(cx, xml, ns))
5224         return JS_FALSE;
5225     ns->setNamespaceDeclared(JSVAL_TRUE);
5226
5227   done:
5228     *vp = OBJECT_TO_JSVAL(obj);
5229     return JS_TRUE;
5230 }
5231
5232 static JSBool
5233 xml_appendChild(JSContext *cx, uintN argc, jsval *vp)
5234 {
5235     jsval v;
5236     JSObject *vobj;
5237     JSXML *vxml;
5238
5239     NON_LIST_XML_METHOD_PROLOG;
5240     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5241     if (!xml)
5242         return JS_FALSE;
5243
5244     jsid name;
5245     if (!js_GetAnyName(cx, &name))
5246         return JS_FALSE;
5247
5248     if (!GetProperty(cx, obj, name, &v))
5249         return JS_FALSE;
5250
5251     JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5252     vobj = JSVAL_TO_OBJECT(v);
5253     JS_ASSERT(vobj->isXML());
5254     vxml = (JSXML *) vobj->getPrivate();
5255     JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);
5256
5257     if (!IndexToId(cx, vxml->xml_kids.length, &name))
5258         return JS_FALSE;
5259     *vp = (argc != 0) ? vp[2] : JSVAL_VOID;
5260
5261     if (!PutProperty(cx, JSVAL_TO_OBJECT(v), name, false, vp))
5262         return JS_FALSE;
5263
5264     *vp = OBJECT_TO_JSVAL(obj);
5265     return JS_TRUE;
5266 }
5267
5268 /* XML and XMLList */
5269 static JSBool
5270 xml_attribute(JSContext *cx, uintN argc, jsval *vp)
5271 {
5272     JSObject *qn;
5273
5274     if (argc == 0) {
5275         js_ReportMissingArg(cx, Valueify(*vp), 0);
5276         return JS_FALSE;
5277     }
5278
5279     qn = ToAttributeName(cx, vp[2]);
5280     if (!qn)
5281         return JS_FALSE;
5282     vp[2] = OBJECT_TO_JSVAL(qn);        /* local root */
5283
5284     jsid id = OBJECT_TO_JSID(qn);
5285     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
5286     if (!obj)
5287         return JS_FALSE;
5288     return GetProperty(cx, obj, id, vp);
5289 }
5290
5291 /* XML and XMLList */
5292 static JSBool
5293 xml_attributes(JSContext *cx, uintN argc, jsval *vp)
5294 {
5295     jsval name = ATOM_TO_JSVAL(cx->runtime->atomState.starAtom);
5296     JSObject *qn = ToAttributeName(cx, name);
5297     if (!qn)
5298         return JS_FALSE;
5299
5300     AutoObjectRooter tvr(cx, qn);
5301     jsid id = OBJECT_TO_JSID(qn);
5302     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
5303     if (!obj)
5304         return JS_FALSE;
5305     return GetProperty(cx, obj, id, vp);
5306 }
5307
5308 static JSXML *
5309 xml_list_helper(JSContext *cx, JSXML *xml, jsval *rval)
5310 {
5311     JSObject *listobj;
5312     JSXML *list;
5313
5314     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5315     if (!listobj)
5316         return NULL;
5317
5318     *rval = OBJECT_TO_JSVAL(listobj);
5319     list = (JSXML *) listobj->getPrivate();
5320     list->xml_target = xml;
5321     return list;
5322 }
5323
5324 static JSBool
5325 ValueToId(JSContext *cx, jsval v, AutoIdRooter *idr)
5326 {
5327     if (JSVAL_IS_INT(v)) {
5328         jsint i = JSVAL_TO_INT(v);
5329         if (INT_FITS_IN_JSID(i))
5330             *idr->addr() = INT_TO_JSID(i);
5331         else if (!js_ValueToStringId(cx, Valueify(v), idr->addr()))
5332             return JS_FALSE;
5333     } else if (JSVAL_IS_STRING(v)) {
5334         JSAtom *atom = js_AtomizeString(cx, JSVAL_TO_STRING(v), 0);
5335         if (!atom)
5336             return JS_FALSE;
5337         *idr->addr() = ATOM_TO_JSID(atom);
5338     } else if (!JSVAL_IS_PRIMITIVE(v)) {
5339         *idr->addr() = OBJECT_TO_JSID(JSVAL_TO_OBJECT(v));
5340     } else {
5341         ReportBadXMLName(cx, Valueify(v));
5342         return JS_FALSE;
5343     }
5344     return JS_TRUE;
5345 }
5346
5347 static JSBool
5348 xml_child_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval name,
5349                  jsval *rval)
5350 {
5351     bool isIndex;
5352     uint32 index;
5353     JSXML *kid;
5354     JSObject *kidobj;
5355
5356     /* ECMA-357 13.4.4.6 */
5357     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
5358
5359     if (!js_IdValIsIndex(cx, name, &index, &isIndex))
5360         return JS_FALSE;
5361
5362     if (isIndex) {
5363         if (index >= JSXML_LENGTH(xml)) {
5364             *rval = JSVAL_VOID;
5365         } else {
5366             kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
5367             if (!kid) {
5368                 *rval = JSVAL_VOID;
5369             } else {
5370                 kidobj = js_GetXMLObject(cx, kid);
5371                 if (!kidobj)
5372                     return JS_FALSE;
5373                 *rval = OBJECT_TO_JSVAL(kidobj);
5374             }
5375         }
5376         return JS_TRUE;
5377     }
5378
5379     AutoIdRooter idr(cx);
5380     if (!ValueToId(cx, name, &idr))
5381         return JS_FALSE;
5382
5383     return GetProperty(cx, obj, idr.id(), rval);
5384 }
5385
5386 /* XML and XMLList */
5387 static JSBool
5388 xml_child(JSContext *cx, uintN argc, jsval *vp)
5389 {
5390     jsval v;
5391     JSXML *list, *vxml;
5392     JSObject *kidobj;
5393
5394     XML_METHOD_PROLOG;
5395     jsval name = argc != 0 ? vp[2] : JSVAL_VOID;
5396     if (xml->xml_class == JSXML_CLASS_LIST) {
5397         /* ECMA-357 13.5.4.4 */
5398         list = xml_list_helper(cx, xml, vp);
5399         if (!list)
5400             return JS_FALSE;
5401
5402         JSXMLArrayCursor cursor(&xml->xml_kids);
5403         while (JSXML *kid = (JSXML *) cursor.getNext()) {
5404             kidobj = js_GetXMLObject(cx, kid);
5405             if (!kidobj)
5406                 return JS_FALSE;
5407             if (!xml_child_helper(cx, kidobj, kid, name, &v))
5408                 return JS_FALSE;
5409             if (JSVAL_IS_VOID(v)) {
5410                 /* The property didn't exist in this kid. */
5411                 continue;
5412             }
5413
5414             JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5415             vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
5416             if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
5417                 !Append(cx, list, vxml)) {
5418                 return JS_FALSE;
5419             }
5420         }
5421         return JS_TRUE;
5422     }
5423
5424     /* ECMA-357 Edition 2 13.3.4.6 (note 13.3, not 13.4 as in Edition 1). */
5425     if (!xml_child_helper(cx, obj, xml, name, vp))
5426         return JS_FALSE;
5427     if (JSVAL_IS_VOID(*vp) && !xml_list_helper(cx, xml, vp))
5428         return JS_FALSE;
5429     return JS_TRUE;
5430 }
5431
5432 static JSBool
5433 xml_childIndex(JSContext *cx, uintN argc, jsval *vp)
5434 {
5435     JSXML *parent;
5436     uint32 i, n;
5437
5438     NON_LIST_XML_METHOD_PROLOG;
5439     parent = xml->parent;
5440     if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
5441         *vp = DOUBLE_TO_JSVAL(js_NaN);
5442         return JS_TRUE;
5443     }
5444     for (i = 0, n = JSXML_LENGTH(parent); i < n; i++) {
5445         if (XMLARRAY_MEMBER(&parent->xml_kids, i, JSXML) == xml)
5446             break;
5447     }
5448     JS_ASSERT(i < n);
5449     if (i <= JSVAL_INT_MAX)
5450         *vp = INT_TO_JSVAL(i);
5451     else
5452         *vp = DOUBLE_TO_JSVAL(i);
5453     return JS_TRUE;
5454 }
5455
5456 /* XML and XMLList */
5457 static JSBool
5458 xml_children(JSContext *cx, uintN argc, jsval *vp)
5459 {
5460     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
5461     if (!obj)
5462         return false;
5463     jsid name = ATOM_TO_JSID(cx->runtime->atomState.starAtom);
5464     return GetProperty(cx, obj, name, vp);
5465 }
5466
5467 /* XML and XMLList */
5468 static JSBool
5469 xml_comments_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval *vp)
5470 {
5471     JSXML *list, *kid, *vxml;
5472     JSBool ok;
5473     uint32 i, n;
5474     JSObject *kidobj;
5475     jsval v;
5476
5477     list = xml_list_helper(cx, xml, vp);
5478     if (!list)
5479         return JS_FALSE;
5480
5481     ok = JS_TRUE;
5482
5483     if (xml->xml_class == JSXML_CLASS_LIST) {
5484         /* 13.5.4.6 Step 2. */
5485         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5486             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5487             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5488                 ok = js_EnterLocalRootScope(cx);
5489                 if (!ok)
5490                     break;
5491                 kidobj = js_GetXMLObject(cx, kid);
5492                 if (kidobj) {
5493                     ok = xml_comments_helper(cx, kidobj, kid, &v);
5494                 } else {
5495                     ok = JS_FALSE;
5496                     v = JSVAL_NULL;
5497                 }
5498                 js_LeaveLocalRootScopeWithResult(cx, Valueify(v));
5499                 if (!ok)
5500                     break;
5501                 vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
5502                 if (JSXML_LENGTH(vxml) != 0) {
5503                     ok = Append(cx, list, vxml);
5504                     if (!ok)
5505                         break;
5506                 }
5507             }
5508         }
5509     } else {
5510         /* 13.4.4.9 Step 2. */
5511         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5512             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5513             if (kid && kid->xml_class == JSXML_CLASS_COMMENT) {
5514                 ok = Append(cx, list, kid);
5515                 if (!ok)
5516                     break;
5517             }
5518         }
5519     }
5520
5521     return ok;
5522 }
5523
5524 static JSBool
5525 xml_comments(JSContext *cx, uintN argc, jsval *vp)
5526 {
5527     XML_METHOD_PROLOG;
5528     return xml_comments_helper(cx, obj, xml, vp);
5529 }
5530
5531 /* XML and XMLList */
5532 static JSBool
5533 xml_contains(JSContext *cx, uintN argc, jsval *vp)
5534 {
5535     jsval value;
5536     JSBool eq;
5537     JSObject *kidobj;
5538
5539     XML_METHOD_PROLOG;
5540     value = argc != 0 ? vp[2] : JSVAL_VOID;
5541     if (xml->xml_class == JSXML_CLASS_LIST) {
5542         eq = JS_FALSE;
5543         JSXMLArrayCursor cursor(&xml->xml_kids);
5544         while (JSXML *kid = (JSXML *) cursor.getNext()) {
5545             kidobj = js_GetXMLObject(cx, kid);
5546             if (!kidobj || !js_TestXMLEquality(cx, ObjectValue(*kidobj), Valueify(value), &eq))
5547                 return JS_FALSE;
5548             if (eq)
5549                 break;
5550         }
5551     } else {
5552         if (!js_TestXMLEquality(cx, ObjectValue(*obj), Valueify(value), &eq))
5553             return JS_FALSE;
5554     }
5555     *vp = BOOLEAN_TO_JSVAL(eq);
5556     return JS_TRUE;
5557 }
5558
5559 /* XML and XMLList */
5560 static JSBool
5561 xml_copy(JSContext *cx, uintN argc, jsval *vp)
5562 {
5563     JSXML *copy;
5564
5565     XML_METHOD_PROLOG;
5566     copy = DeepCopy(cx, xml, NULL, 0);
5567     if (!copy)
5568         return JS_FALSE;
5569     *vp = OBJECT_TO_JSVAL(copy->object);
5570     return JS_TRUE;
5571 }
5572
5573 /* XML and XMLList */
5574 static JSBool
5575 xml_descendants(JSContext *cx, uintN argc, jsval *vp)
5576 {
5577     jsval name;
5578     JSXML *list;
5579
5580     XML_METHOD_PROLOG;
5581     name = argc == 0 ? ATOM_TO_JSVAL(cx->runtime->atomState.starAtom) : vp[2];
5582     list = Descendants(cx, xml, name);
5583     if (!list)
5584         return JS_FALSE;
5585     *vp = OBJECT_TO_JSVAL(list->object);
5586     return JS_TRUE;
5587 }
5588
5589 /* XML and XMLList */
5590 static JSBool
5591 xml_elements_helper(JSContext *cx, JSObject *obj, JSXML *xml,
5592                     JSObject *nameqn, jsval *vp)
5593 {
5594     JSXML *list, *vxml;
5595     jsval v;
5596     JSBool ok;
5597     JSObject *kidobj;
5598     uint32 i, n;
5599
5600     list = xml_list_helper(cx, xml, vp);
5601     if (!list)
5602         return JS_FALSE;
5603
5604     list->xml_targetprop = nameqn;
5605     ok = JS_TRUE;
5606
5607     if (xml->xml_class == JSXML_CLASS_LIST) {
5608         /* 13.5.4.6 */
5609         JSXMLArrayCursor cursor(&xml->xml_kids);
5610         while (JSXML *kid = (JSXML *) cursor.getNext()) {
5611             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
5612                 ok = js_EnterLocalRootScope(cx);
5613                 if (!ok)
5614                     break;
5615                 kidobj = js_GetXMLObject(cx, kid);
5616                 if (kidobj) {
5617                     ok = xml_elements_helper(cx, kidobj, kid, nameqn, &v);
5618                 } else {
5619                     ok = JS_FALSE;
5620                     v = JSVAL_NULL;
5621                 }
5622                 js_LeaveLocalRootScopeWithResult(cx, Valueify(v));
5623                 if (!ok)
5624                     break;
5625                 vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
5626                 if (JSXML_LENGTH(vxml) != 0) {
5627                     ok = Append(cx, list, vxml);
5628                     if (!ok)
5629                         break;
5630                 }
5631             }
5632         }
5633     } else {
5634         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5635             JSXML *kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5636             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT &&
5637                 MatchElemName(nameqn, kid)) {
5638                 ok = Append(cx, list, kid);
5639                 if (!ok)
5640                     break;
5641             }
5642         }
5643     }
5644
5645     return ok;
5646 }
5647
5648 static JSBool
5649 xml_elements(JSContext *cx, uintN argc, jsval *vp)
5650 {
5651     jsval name;
5652     JSObject *nameqn;
5653     jsid funid;
5654
5655     XML_METHOD_PROLOG;
5656
5657     name = (argc == 0) ? ATOM_TO_JSVAL(cx->runtime->atomState.starAtom) : vp[2];
5658     nameqn = ToXMLName(cx, name, &funid);
5659     if (!nameqn)
5660         return JS_FALSE;
5661
5662     if (!JSID_IS_VOID(funid))
5663         return xml_list_helper(cx, xml, vp) != NULL;
5664
5665     return xml_elements_helper(cx, obj, xml, nameqn, vp);
5666 }
5667
5668 /* XML and XMLList */
5669 static JSBool
5670 xml_hasOwnProperty(JSContext *cx, uintN argc, jsval *vp)
5671 {
5672     jsval name;
5673     JSBool found;
5674
5675     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
5676     if (!obj)
5677         return JS_FALSE;
5678     if (!InstanceOf(cx, obj, &js_XMLClass, Valueify(vp + 2)))
5679         return JS_FALSE;
5680
5681     name = argc != 0 ? vp[2] : JSVAL_VOID;
5682     if (!HasProperty(cx, obj, name, &found))
5683         return JS_FALSE;
5684     if (found) {
5685         *vp = JSVAL_TRUE;
5686         return JS_TRUE;
5687     }
5688     return js_HasOwnPropertyHelper(cx, js_LookupProperty, argc, Valueify(vp));
5689 }
5690
5691 /* XML and XMLList */
5692 static JSBool
5693 xml_hasComplexContent(JSContext *cx, uintN argc, jsval *vp)
5694 {
5695     JSXML *kid;
5696     JSObject *kidobj;
5697     uint32 i, n;
5698
5699     XML_METHOD_PROLOG;
5700 again:
5701     switch (xml->xml_class) {
5702       case JSXML_CLASS_ATTRIBUTE:
5703       case JSXML_CLASS_COMMENT:
5704       case JSXML_CLASS_PROCESSING_INSTRUCTION:
5705       case JSXML_CLASS_TEXT:
5706         *vp = JSVAL_FALSE;
5707         break;
5708       case JSXML_CLASS_LIST:
5709         if (xml->xml_kids.length == 0) {
5710             *vp = JSVAL_TRUE;
5711         } else if (xml->xml_kids.length == 1) {
5712             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5713             if (kid) {
5714                 kidobj = js_GetXMLObject(cx, kid);
5715                 if (!kidobj)
5716                     return JS_FALSE;
5717                 obj = kidobj;
5718                 xml = (JSXML *) obj->getPrivate();
5719                 goto again;
5720             }
5721         }
5722         /* FALL THROUGH */
5723       default:
5724         *vp = JSVAL_FALSE;
5725         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
5726             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5727             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5728                 *vp = JSVAL_TRUE;
5729                 break;
5730             }
5731         }
5732         break;
5733     }
5734     return JS_TRUE;
5735 }
5736
5737 /* XML and XMLList */
5738 static JSBool
5739 xml_hasSimpleContent(JSContext *cx, uintN argc, jsval *vp)
5740 {
5741     XML_METHOD_PROLOG;
5742     *vp = BOOLEAN_TO_JSVAL(HasSimpleContent(xml));
5743     return JS_TRUE;
5744 }
5745
5746 static JSBool
5747 FindInScopeNamespaces(JSContext *cx, JSXML *xml, JSXMLArray *nsarray)
5748 {
5749     uint32 length, i, j, n;
5750     JSObject *ns, *ns2;
5751     JSLinearString *prefix, *prefix2;
5752
5753     length = nsarray->length;
5754     do {
5755         if (xml->xml_class != JSXML_CLASS_ELEMENT)
5756             continue;
5757         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
5758             ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
5759             if (!ns)
5760                 continue;
5761
5762             prefix = ns->getNamePrefix();
5763             for (j = 0; j < length; j++) {
5764                 ns2 = XMLARRAY_MEMBER(nsarray, j, JSObject);
5765                 if (ns2) {
5766                     prefix2 = ns2->getNamePrefix();
5767                     if ((prefix2 && prefix)
5768                         ? EqualStrings(prefix2, prefix)
5769                         : EqualStrings(ns2->getNameURI(), ns->getNameURI())) {
5770                         break;
5771                     }
5772                 }
5773             }
5774
5775             if (j == length) {
5776                 if (!XMLARRAY_APPEND(cx, nsarray, ns))
5777                     return JS_FALSE;
5778                 ++length;
5779             }
5780         }
5781     } while ((xml = xml->parent) != NULL);
5782     JS_ASSERT(length == nsarray->length);
5783
5784     return JS_TRUE;
5785 }
5786
5787 /*
5788  * Populate a new JS array with elements of array and place the result into
5789  * rval.  rval must point to a rooted location.
5790  */
5791 static bool
5792 NamespacesToJSArray(JSContext *cx, JSXMLArray *array, jsval *rval)
5793 {
5794     JSObject *arrayobj = NewDenseEmptyArray(cx);
5795     if (!arrayobj)
5796         return false;
5797     *rval = OBJECT_TO_JSVAL(arrayobj);
5798
5799     AutoValueRooter tvr(cx);
5800     for (uint32 i = 0, n = array->length; i < n; i++) {
5801         JSObject *ns = XMLARRAY_MEMBER(array, i, JSObject);
5802         if (!ns)
5803             continue;
5804         tvr.set(ObjectValue(*ns));
5805         if (!arrayobj->setProperty(cx, INT_TO_JSID(i), tvr.addr(), false))
5806             return false;
5807     }
5808     return true;
5809 }
5810
5811 static JSBool
5812 xml_inScopeNamespaces(JSContext *cx, uintN argc, jsval *vp)
5813 {
5814     NON_LIST_XML_METHOD_PROLOG;
5815
5816     AutoNamespaceArray namespaces(cx);
5817     return FindInScopeNamespaces(cx, xml, &namespaces.array) &&
5818            NamespacesToJSArray(cx, &namespaces.array, vp);
5819 }
5820
5821 static JSBool
5822 xml_insertChildAfter(JSContext *cx, uintN argc, jsval *vp)
5823 {
5824     jsval arg;
5825     JSXML *kid;
5826     uint32 i;
5827
5828     NON_LIST_XML_METHOD_PROLOG;
5829     *vp = OBJECT_TO_JSVAL(obj);
5830     if (!JSXML_HAS_KIDS(xml) || argc == 0)
5831         return JS_TRUE;
5832
5833     arg = vp[2];
5834     if (JSVAL_IS_NULL(arg)) {
5835         kid = NULL;
5836         i = 0;
5837     } else {
5838         if (!VALUE_IS_XML(arg))
5839             return JS_TRUE;
5840         kid = (JSXML *) JSVAL_TO_OBJECT(arg)->getPrivate();
5841         i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
5842         if (i == XML_NOT_FOUND)
5843             return JS_TRUE;
5844         ++i;
5845     }
5846
5847     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5848     if (!xml)
5849         return JS_FALSE;
5850     return Insert(cx, xml, i, argc >= 2 ? vp[3] : JSVAL_VOID);
5851 }
5852
5853 static JSBool
5854 xml_insertChildBefore(JSContext *cx, uintN argc, jsval *vp)
5855 {
5856     jsval arg;
5857     JSXML *kid;
5858     uint32 i;
5859
5860     NON_LIST_XML_METHOD_PROLOG;
5861     *vp = OBJECT_TO_JSVAL(obj);
5862     if (!JSXML_HAS_KIDS(xml) || argc == 0)
5863         return JS_TRUE;
5864
5865     arg = vp[2];
5866     if (JSVAL_IS_NULL(arg)) {
5867         kid = NULL;
5868         i = xml->xml_kids.length;
5869     } else {
5870         if (!VALUE_IS_XML(arg))
5871             return JS_TRUE;
5872         kid = (JSXML *) JSVAL_TO_OBJECT(arg)->getPrivate();
5873         i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
5874         if (i == XML_NOT_FOUND)
5875             return JS_TRUE;
5876     }
5877
5878     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5879     if (!xml)
5880         return JS_FALSE;
5881     return Insert(cx, xml, i, argc >= 2 ? vp[3] : JSVAL_VOID);
5882 }
5883
5884 /* XML and XMLList */
5885 static JSBool
5886 xml_length(JSContext *cx, uintN argc, jsval *vp)
5887 {
5888     XML_METHOD_PROLOG;
5889     if (xml->xml_class != JSXML_CLASS_LIST) {
5890         *vp = JSVAL_ONE;
5891     } else {
5892         uint32 l = xml->xml_kids.length;
5893         if (l <= JSVAL_INT_MAX)
5894             *vp = INT_TO_JSVAL(l);
5895         else
5896             *vp = DOUBLE_TO_JSVAL(l);
5897     }
5898     return JS_TRUE;
5899 }
5900
5901 static JSBool
5902 xml_localName(JSContext *cx, uintN argc, jsval *vp)
5903 {
5904     NON_LIST_XML_METHOD_PROLOG;
5905     *vp = xml->name ? xml->name->getQNameLocalNameVal() : JSVAL_NULL;
5906     return JS_TRUE;
5907 }
5908
5909 static JSBool
5910 xml_name(JSContext *cx, uintN argc, jsval *vp)
5911 {
5912     NON_LIST_XML_METHOD_PROLOG;
5913     *vp = OBJECT_TO_JSVAL(xml->name);
5914     return JS_TRUE;
5915 }
5916
5917 static JSBool
5918 xml_namespace(JSContext *cx, uintN argc, jsval *vp)
5919 {
5920     JSLinearString *prefix, *nsprefix;
5921     jsuint i, length;
5922     JSObject *ns;
5923
5924     NON_LIST_XML_METHOD_PROLOG;
5925     if (argc == 0 && !JSXML_HAS_NAME(xml)) {
5926         *vp = JSVAL_NULL;
5927         return true;
5928     }
5929
5930     if (argc == 0) {
5931         prefix = NULL;
5932     } else {
5933         JSString *str = js_ValueToString(cx, Valueify(vp[2]));
5934         if (!str)
5935             return false;
5936         prefix = str->ensureLinear(cx);
5937         if (!prefix)
5938             return false;
5939         vp[2] = STRING_TO_JSVAL(prefix);      /* local root */
5940     }
5941
5942     AutoNamespaceArray inScopeNSes(cx);
5943     if (!FindInScopeNamespaces(cx, xml, &inScopeNSes.array))
5944         return false;
5945
5946     if (!prefix) {
5947         ns = GetNamespace(cx, xml->name, &inScopeNSes.array);
5948         if (!ns)
5949             return false;
5950     } else {
5951         ns = NULL;
5952         for (i = 0, length = inScopeNSes.array.length; i < length; i++) {
5953             ns = XMLARRAY_MEMBER(&inScopeNSes.array, i, JSObject);
5954             if (ns) {
5955                 nsprefix = ns->getNamePrefix();
5956                 if (nsprefix && EqualStrings(nsprefix, prefix))
5957                     break;
5958                 ns = NULL;
5959             }
5960         }
5961     }
5962
5963     *vp = (!ns) ? JSVAL_VOID : OBJECT_TO_JSVAL(ns);
5964     return true;
5965 }
5966
5967 static JSBool
5968 xml_namespaceDeclarations(JSContext *cx, uintN argc, jsval *vp)
5969 {
5970     NON_LIST_XML_METHOD_PROLOG;
5971     if (JSXML_HAS_VALUE(xml))
5972         return true;
5973
5974     AutoNamespaceArray ancestors(cx);
5975     AutoNamespaceArray declared(cx);
5976
5977     JSXML *yml = xml;
5978     while ((yml = yml->parent) != NULL) {
5979         JS_ASSERT(yml->xml_class == JSXML_CLASS_ELEMENT);
5980         for (uint32 i = 0, n = yml->xml_namespaces.length; i < n; i++) {
5981             JSObject *ns = XMLARRAY_MEMBER(&yml->xml_namespaces, i, JSObject);
5982             if (ns && !XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
5983                 if (!XMLARRAY_APPEND(cx, &ancestors.array, ns))
5984                     return false;
5985             }
5986         }
5987     }
5988
5989     for (uint32 i = 0, n = xml->xml_namespaces.length; i < n; i++) {
5990         JSObject *ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSObject);
5991         if (!ns)
5992             continue;
5993         if (!IsDeclared(ns))
5994             continue;
5995         if (!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
5996             if (!XMLARRAY_APPEND(cx, &declared.array, ns))
5997                 return false;
5998         }
5999     }
6000
6001     return NamespacesToJSArray(cx, &declared.array, vp);
6002 }
6003
6004 static const char js_attribute_str[] = "attribute";
6005 static const char js_text_str[]      = "text";
6006
6007 /* Exported to jsgc.c #ifdef DEBUG. */
6008 const char *js_xml_class_str[] = {
6009     "list",
6010     "element",
6011     js_attribute_str,
6012     "processing-instruction",
6013     js_text_str,
6014     "comment"
6015 };
6016
6017 static JSBool
6018 xml_nodeKind(JSContext *cx, uintN argc, jsval *vp)
6019 {
6020     JSString *str;
6021
6022     NON_LIST_XML_METHOD_PROLOG;
6023     str = JS_InternString(cx, js_xml_class_str[xml->xml_class]);
6024     if (!str)
6025         return JS_FALSE;
6026     *vp = STRING_TO_JSVAL(str);
6027     return JS_TRUE;
6028 }
6029
6030 static void
6031 NormalizingDelete(JSContext *cx, JSXML *xml, uint32 index)
6032 {
6033     if (xml->xml_class == JSXML_CLASS_LIST)
6034         DeleteListElement(cx, xml, index);
6035     else
6036         DeleteByIndex(cx, xml, index);
6037 }
6038
6039 /* XML and XMLList */
6040 static JSBool
6041 xml_normalize_helper(JSContext *cx, JSObject *obj, JSXML *xml)
6042 {
6043     JSXML *kid, *kid2;
6044     uint32 i, n;
6045     JSObject *kidobj;
6046     JSString *str;
6047
6048     if (!JSXML_HAS_KIDS(xml))
6049         return JS_TRUE;
6050
6051     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6052     if (!xml)
6053         return JS_FALSE;
6054
6055     for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6056         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6057         if (!kid)
6058             continue;
6059         if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6060             kidobj = js_GetXMLObject(cx, kid);
6061             if (!kidobj || !xml_normalize_helper(cx, kidobj, kid))
6062                 return JS_FALSE;
6063         } else if (kid->xml_class == JSXML_CLASS_TEXT) {
6064             while (i + 1 < n &&
6065                    (kid2 = XMLARRAY_MEMBER(&xml->xml_kids, i + 1, JSXML)) &&
6066                    kid2->xml_class == JSXML_CLASS_TEXT) {
6067                 str = js_ConcatStrings(cx, kid->xml_value, kid2->xml_value);
6068                 if (!str)
6069                     return JS_FALSE;
6070                 NormalizingDelete(cx, xml, i + 1);
6071                 n = xml->xml_kids.length;
6072                 kid->xml_value = str;
6073             }
6074             if (kid->xml_value->empty()) {
6075                 NormalizingDelete(cx, xml, i);
6076                 n = xml->xml_kids.length;
6077                 --i;
6078             }
6079         }
6080     }
6081
6082     return JS_TRUE;
6083 }
6084
6085 static JSBool
6086 xml_normalize(JSContext *cx, uintN argc, jsval *vp)
6087 {
6088     XML_METHOD_PROLOG;
6089     *vp = OBJECT_TO_JSVAL(obj);
6090     return xml_normalize_helper(cx, obj, xml);
6091 }
6092
6093 /* XML and XMLList */
6094 static JSBool
6095 xml_parent(JSContext *cx, uintN argc, jsval *vp)
6096 {
6097     JSXML *parent, *kid;
6098     uint32 i, n;
6099     JSObject *parentobj;
6100
6101     XML_METHOD_PROLOG;
6102     parent = xml->parent;
6103     if (xml->xml_class == JSXML_CLASS_LIST) {
6104         *vp = JSVAL_VOID;
6105         n = xml->xml_kids.length;
6106         if (n == 0)
6107             return JS_TRUE;
6108
6109         kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6110         if (!kid)
6111             return JS_TRUE;
6112         parent = kid->parent;
6113         for (i = 1; i < n; i++) {
6114             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6115             if (kid && kid->parent != parent)
6116                 return JS_TRUE;
6117         }
6118     }
6119
6120     if (!parent) {
6121         *vp = JSVAL_NULL;
6122         return JS_TRUE;
6123     }
6124
6125     parentobj = js_GetXMLObject(cx, parent);
6126     if (!parentobj)
6127         return JS_FALSE;
6128     *vp = OBJECT_TO_JSVAL(parentobj);
6129     return JS_TRUE;
6130 }
6131
6132 /* XML and XMLList */
6133 static JSBool
6134 xml_processingInstructions_helper(JSContext *cx, JSObject *obj, JSXML *xml,
6135                                   JSObject *nameqn, jsval *vp)
6136 {
6137     JSXML *list, *vxml;
6138     JSBool ok;
6139     JSObject *kidobj;
6140     jsval v;
6141     uint32 i, n;
6142
6143     list = xml_list_helper(cx, xml, vp);
6144     if (!list)
6145         return JS_FALSE;
6146
6147     list->xml_targetprop = nameqn;
6148     ok = JS_TRUE;
6149
6150     if (xml->xml_class == JSXML_CLASS_LIST) {
6151         /* 13.5.4.17 Step 4 (misnumbered 9 -- Erratum?). */
6152         JSXMLArrayCursor cursor(&xml->xml_kids);
6153         while (JSXML *kid = (JSXML *) cursor.getNext()) {
6154             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6155                 ok = js_EnterLocalRootScope(cx);
6156                 if (!ok)
6157                     break;
6158                 kidobj = js_GetXMLObject(cx, kid);
6159                 if (kidobj) {
6160                     ok = xml_processingInstructions_helper(cx, kidobj, kid,
6161                                                            nameqn, &v);
6162                 } else {
6163                     ok = JS_FALSE;
6164                     v = JSVAL_NULL;
6165                 }
6166                 js_LeaveLocalRootScopeWithResult(cx, Valueify(v));
6167                 if (!ok)
6168                     break;
6169                 vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
6170                 if (JSXML_LENGTH(vxml) != 0) {
6171                     ok = Append(cx, list, vxml);
6172                     if (!ok)
6173                         break;
6174                 }
6175             }
6176         }
6177     } else {
6178         /* 13.4.4.28 Step 4. */
6179         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6180             JSXML *kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6181             if (kid && kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
6182                 JSLinearString *localName = nameqn->getQNameLocalName();
6183                 if (IS_STAR(localName) ||
6184                     EqualStrings(localName, kid->name->getQNameLocalName())) {
6185                     ok = Append(cx, list, kid);
6186                     if (!ok)
6187                         break;
6188                 }
6189             }
6190         }
6191     }
6192
6193     return ok;
6194 }
6195
6196 static JSBool
6197 xml_processingInstructions(JSContext *cx, uintN argc, jsval *vp)
6198 {
6199     jsval name;
6200     JSObject *nameqn;
6201     jsid funid;
6202
6203     XML_METHOD_PROLOG;
6204
6205     name = (argc == 0) ? ATOM_TO_JSVAL(cx->runtime->atomState.starAtom) : vp[2];
6206     nameqn = ToXMLName(cx, name, &funid);
6207     if (!nameqn)
6208         return JS_FALSE;
6209     vp[2] = OBJECT_TO_JSVAL(nameqn);
6210
6211     if (!JSID_IS_VOID(funid))
6212         return xml_list_helper(cx, xml, vp) != NULL;
6213
6214     return xml_processingInstructions_helper(cx, obj, xml, nameqn, vp);
6215 }
6216
6217 static JSBool
6218 xml_prependChild(JSContext *cx, uintN argc, jsval *vp)
6219 {
6220     NON_LIST_XML_METHOD_PROLOG;
6221     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6222     if (!xml)
6223         return JS_FALSE;
6224     *vp = OBJECT_TO_JSVAL(obj);
6225     return Insert(cx, xml, 0, argc != 0 ? vp[2] : JSVAL_VOID);
6226 }
6227
6228 /* XML and XMLList */
6229 static JSBool
6230 xml_propertyIsEnumerable(JSContext *cx, uintN argc, jsval *vp)
6231 {
6232     bool isIndex;
6233     uint32 index;
6234
6235     XML_METHOD_PROLOG;
6236     *vp = JSVAL_FALSE;
6237     if (argc != 0) {
6238         if (!js_IdValIsIndex(cx, vp[2], &index, &isIndex))
6239             return JS_FALSE;
6240
6241         if (isIndex) {
6242             if (xml->xml_class == JSXML_CLASS_LIST) {
6243                 /* 13.5.4.18. */
6244                 *vp = BOOLEAN_TO_JSVAL(index < xml->xml_kids.length);
6245             } else {
6246                 /* 13.4.4.30. */
6247                 *vp = BOOLEAN_TO_JSVAL(index == 0);
6248             }
6249         }
6250     }
6251     return JS_TRUE;
6252 }
6253
6254 static JSBool
6255 namespace_full_match(const void *a, const void *b)
6256 {
6257     const JSObject *nsa = (const JSObject *) a;
6258     const JSObject *nsb = (const JSObject *) b;
6259     JSLinearString *prefixa = nsa->getNamePrefix();
6260     JSLinearString *prefixb;
6261
6262     if (prefixa) {
6263         prefixb = nsb->getNamePrefix();
6264         if (prefixb && !EqualStrings(prefixa, prefixb))
6265             return JS_FALSE;
6266     }
6267     return EqualStrings(nsa->getNameURI(), nsb->getNameURI());
6268 }
6269
6270 static JSBool
6271 xml_removeNamespace_helper(JSContext *cx, JSXML *xml, JSObject *ns)
6272 {
6273     JSObject *thisns, *attrns;
6274     uint32 i, n;
6275     JSXML *attr, *kid;
6276
6277     thisns = GetNamespace(cx, xml->name, &xml->xml_namespaces);
6278     JS_ASSERT(thisns);
6279     if (thisns == ns)
6280         return JS_TRUE;
6281
6282     for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
6283         attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
6284         if (!attr)
6285             continue;
6286         attrns = GetNamespace(cx, attr->name, &xml->xml_namespaces);
6287         JS_ASSERT(attrns);
6288         if (attrns == ns)
6289             return JS_TRUE;
6290     }
6291
6292     i = XMLARRAY_FIND_MEMBER(&xml->xml_namespaces, ns, namespace_full_match);
6293     if (i != XML_NOT_FOUND)
6294         XMLArrayDelete(cx, &xml->xml_namespaces, i, JS_TRUE);
6295
6296     for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6297         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6298         if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6299             if (!xml_removeNamespace_helper(cx, kid, ns))
6300                 return JS_FALSE;
6301         }
6302     }
6303     return JS_TRUE;
6304 }
6305
6306 static JSBool
6307 xml_removeNamespace(JSContext *cx, uintN argc, jsval *vp)
6308 {
6309     JSObject *ns;
6310
6311     NON_LIST_XML_METHOD_PROLOG;
6312     if (xml->xml_class != JSXML_CLASS_ELEMENT)
6313         goto done;
6314     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6315     if (!xml)
6316         return JS_FALSE;
6317
6318     if (!NamespaceHelper(cx, NULL, argc == 0 ? -1 : 1, vp + 2, vp))
6319         return JS_FALSE;
6320     JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
6321     ns = JSVAL_TO_OBJECT(*vp);
6322
6323     /* NOTE: remove ns from each ancestor if not used by that ancestor. */
6324     if (!xml_removeNamespace_helper(cx, xml, ns))
6325         return JS_FALSE;
6326   done:
6327     *vp = OBJECT_TO_JSVAL(obj);
6328     return JS_TRUE;
6329 }
6330
6331 static JSBool
6332 xml_replace(JSContext *cx, uintN argc, jsval *vp)
6333 {
6334     jsval value;
6335     JSXML *vxml, *kid;
6336     uint32 index, i;
6337     JSObject *nameqn;
6338
6339     NON_LIST_XML_METHOD_PROLOG;
6340     if (xml->xml_class != JSXML_CLASS_ELEMENT)
6341         goto done;
6342
6343     if (argc <= 1) {
6344         value = ATOM_TO_JSVAL(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
6345     } else {
6346         value = vp[3];
6347         vxml = VALUE_IS_XML(value)
6348                ? (JSXML *) JSVAL_TO_OBJECT(value)->getPrivate()
6349                : NULL;
6350         if (!vxml) {
6351             if (!JS_ConvertValue(cx, value, JSTYPE_STRING, &vp[3]))
6352                 return JS_FALSE;
6353             value = vp[3];
6354         } else {
6355             vxml = DeepCopy(cx, vxml, NULL, 0);
6356             if (!vxml)
6357                 return JS_FALSE;
6358             value = vp[3] = OBJECT_TO_JSVAL(vxml->object);
6359         }
6360     }
6361
6362     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6363     if (!xml)
6364         return JS_FALSE;
6365
6366     bool haveIndex;
6367     if (argc == 0) {
6368         haveIndex = false;
6369     } else {
6370         if (!js_IdValIsIndex(cx, vp[2], &index, &haveIndex))
6371             return JS_FALSE;
6372     }
6373
6374     if (!haveIndex) {
6375         /*
6376          * Call function QName per spec, not ToXMLName, to avoid attribute
6377          * names.
6378          */
6379         if (!QNameHelper(cx, NULL, argc == 0 ? -1 : 1, vp + 2, vp))
6380             return JS_FALSE;
6381         JS_ASSERT(!JSVAL_IS_PRIMITIVE(*vp));
6382         nameqn = JSVAL_TO_OBJECT(*vp);
6383
6384         i = xml->xml_kids.length;
6385         index = XML_NOT_FOUND;
6386         while (i != 0) {
6387             --i;
6388             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6389             if (kid && MatchElemName(nameqn, kid)) {
6390                 if (i != XML_NOT_FOUND)
6391                     DeleteByIndex(cx, xml, i);
6392                 index = i;
6393             }
6394         }
6395
6396         if (index == XML_NOT_FOUND)
6397             goto done;
6398     }
6399
6400     if (!Replace(cx, xml, index, value))
6401         return JS_FALSE;
6402
6403   done:
6404     *vp = OBJECT_TO_JSVAL(obj);
6405     return JS_TRUE;
6406 }
6407
6408 static JSBool
6409 xml_setChildren(JSContext *cx, uintN argc, jsval *vp)
6410 {
6411     JSObject *obj;
6412
6413     if (!StartNonListXMLMethod(cx, vp, &obj))
6414         return JS_FALSE;
6415
6416     *vp = argc != 0 ? vp[2] : JSVAL_VOID;     /* local root */
6417     if (!PutProperty(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.starAtom), false, vp))
6418         return JS_FALSE;
6419
6420     *vp = OBJECT_TO_JSVAL(obj);
6421     return JS_TRUE;
6422 }
6423
6424 static JSBool
6425 xml_setLocalName(JSContext *cx, uintN argc, jsval *vp)
6426 {
6427     jsval name;
6428     JSObject *nameqn;
6429     JSLinearString *namestr;
6430
6431     NON_LIST_XML_METHOD_PROLOG;
6432     if (!JSXML_HAS_NAME(xml))
6433         return JS_TRUE;
6434
6435     if (argc == 0) {
6436         namestr = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
6437     } else {
6438         name = vp[2];
6439         if (!JSVAL_IS_PRIMITIVE(name) &&
6440             JSVAL_TO_OBJECT(name)->getClass() == &js_QNameClass) {
6441             nameqn = JSVAL_TO_OBJECT(name);
6442             namestr = nameqn->getQNameLocalName();
6443         } else {
6444             if (!JS_ConvertValue(cx, name, JSTYPE_STRING, &vp[2]))
6445                 return JS_FALSE;
6446             name = vp[2];
6447             namestr = JSVAL_TO_STRING(name)->ensureLinear(cx);
6448             if (!namestr)
6449                 return JS_FALSE;
6450         }
6451     }
6452
6453     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6454     if (!xml)
6455         return JS_FALSE;
6456     if (namestr)
6457         xml->name->setQNameLocalName(namestr);
6458     return JS_TRUE;
6459 }
6460
6461 static JSBool
6462 xml_setName(JSContext *cx, uintN argc, jsval *vp)
6463 {
6464     jsval name;
6465     JSObject *nameqn;
6466     JSXML *nsowner;
6467     JSXMLArray *nsarray;
6468     uint32 i, n;
6469     JSObject *ns;
6470
6471     NON_LIST_XML_METHOD_PROLOG;
6472     if (!JSXML_HAS_NAME(xml))
6473         return JS_TRUE;
6474
6475     if (argc == 0) {
6476         name = ATOM_TO_JSVAL(cx->runtime->atomState.typeAtoms[JSTYPE_VOID]);
6477     } else {
6478         name = vp[2];
6479         if (!JSVAL_IS_PRIMITIVE(name) &&
6480             JSVAL_TO_OBJECT(name)->getClass() == &js_QNameClass &&
6481             !(nameqn = JSVAL_TO_OBJECT(name))->getNameURI()) {
6482             name = vp[2] = nameqn->getQNameLocalNameVal();
6483         }
6484     }
6485
6486     nameqn = js_ConstructObject(cx, &js_QNameClass, NULL, NULL, 1, Valueify(&name));
6487     if (!nameqn)
6488         return JS_FALSE;
6489
6490     /* ECMA-357 13.4.4.35 Step 4. */
6491     if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
6492         nameqn->setNameURI(cx->runtime->emptyString);
6493
6494     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6495     if (!xml)
6496         return JS_FALSE;
6497     xml->name = nameqn;
6498
6499     /*
6500      * Erratum: nothing in 13.4.4.35 talks about making the name match the
6501      * in-scope namespaces, either by finding an in-scope namespace with a
6502      * matching uri and setting the new name's prefix to that namespace's
6503      * prefix, or by extending the in-scope namespaces for xml (which are in
6504      * xml->parent if xml is an attribute or a PI).
6505      */
6506     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
6507         nsowner = xml;
6508     } else {
6509         if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
6510             return JS_TRUE;
6511         nsowner = xml->parent;
6512     }
6513
6514     if (nameqn->getNamePrefix()) {
6515         /*
6516          * The name being set has a prefix, which originally came from some
6517          * namespace object (which may be the null namespace, where both the
6518          * prefix and uri are the empty string).  We must go through a full
6519          * GetNamespace in case that namespace is in-scope in nsowner.
6520          *
6521          * If we find such an in-scope namespace, we return true right away,
6522          * in this block.  Otherwise, we fall through to the final return of
6523          * AddInScopeNamespace(cx, nsowner, ns).
6524          */
6525         ns = GetNamespace(cx, nameqn, &nsowner->xml_namespaces);
6526         if (!ns)
6527             return JS_FALSE;
6528
6529         /* XXXbe have to test membership to see whether GetNamespace added */
6530         if (XMLARRAY_HAS_MEMBER(&nsowner->xml_namespaces, ns, NULL))
6531             return JS_TRUE;
6532     } else {
6533         /*
6534          * At this point, we know prefix of nameqn is null, so its uri can't
6535          * be the empty string (the null namespace always uses the empty string
6536          * for both prefix and uri).
6537          *
6538          * This means we must inline GetNamespace and specialize it to match
6539          * uri only, never prefix.  If we find a namespace with nameqn's uri
6540          * already in nsowner->xml_namespaces, then all that we need do is set
6541          * prefix of nameqn to that namespace's prefix.
6542          *
6543          * If no such namespace exists, we can create one without going through
6544          * the constructor, because we know uri of nameqn is non-empty (so
6545          * prefix does not need to be converted from null to empty by QName).
6546          */
6547         JS_ASSERT(!nameqn->getNameURI()->empty());
6548
6549         nsarray = &nsowner->xml_namespaces;
6550         for (i = 0, n = nsarray->length; i < n; i++) {
6551             ns = XMLARRAY_MEMBER(nsarray, i, JSObject);
6552             if (ns && EqualStrings(ns->getNameURI(), nameqn->getNameURI())) {
6553                 nameqn->setNamePrefix(ns->getNamePrefix());
6554                 return JS_TRUE;
6555             }
6556         }
6557
6558         ns = NewXMLNamespace(cx, NULL, nameqn->getNameURI(), JS_TRUE);
6559         if (!ns)
6560             return JS_FALSE;
6561     }
6562
6563     if (!AddInScopeNamespace(cx, nsowner, ns))
6564         return JS_FALSE;
6565     vp[0] = JSVAL_VOID;
6566     return JS_TRUE;
6567 }
6568
6569 static JSBool
6570 xml_setNamespace(JSContext *cx, uintN argc, jsval *vp)
6571 {
6572     JSObject *qn;
6573     JSObject *ns;
6574     jsval qnargv[2];
6575     JSXML *nsowner;
6576
6577     NON_LIST_XML_METHOD_PROLOG;
6578     if (!JSXML_HAS_NAME(xml))
6579         return JS_TRUE;
6580
6581     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6582     if (!xml)
6583         return JS_FALSE;
6584
6585     ns = js_ConstructObject(cx, &js_NamespaceClass, NULL, obj,
6586                             argc == 0 ? 0 : 1, Valueify(vp + 2));
6587     if (!ns)
6588         return JS_FALSE;
6589     vp[0] = OBJECT_TO_JSVAL(ns);
6590     ns->setNamespaceDeclared(JSVAL_TRUE);
6591
6592     qnargv[0] = OBJECT_TO_JSVAL(ns);
6593     qnargv[1] = OBJECT_TO_JSVAL(xml->name);
6594     qn = js_ConstructObject(cx, &js_QNameClass, NULL, NULL, 2, Valueify(qnargv));
6595     if (!qn)
6596         return JS_FALSE;
6597
6598     xml->name = qn;
6599
6600     /*
6601      * Erratum: the spec fails to update the governing in-scope namespaces.
6602      * See the erratum noted in xml_setName, above.
6603      */
6604     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
6605         nsowner = xml;
6606     } else {
6607         if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
6608             return JS_TRUE;
6609         nsowner = xml->parent;
6610     }
6611     if (!AddInScopeNamespace(cx, nsowner, ns))
6612         return JS_FALSE;
6613     vp[0] = JSVAL_VOID;
6614     return JS_TRUE;
6615 }
6616
6617 /* XML and XMLList */
6618 static JSBool
6619 xml_text_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval *vp)
6620 {
6621     JSXML *list, *kid, *vxml;
6622     uint32 i, n;
6623     JSBool ok;
6624     JSObject *kidobj;
6625     jsval v;
6626
6627     list = xml_list_helper(cx, xml, vp);
6628     if (!list)
6629         return JS_FALSE;
6630
6631     if (xml->xml_class == JSXML_CLASS_LIST) {
6632         ok = JS_TRUE;
6633         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6634             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6635             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6636                 ok = js_EnterLocalRootScope(cx);
6637                 if (!ok)
6638                     break;
6639                 kidobj = js_GetXMLObject(cx, kid);
6640                 if (kidobj) {
6641                     ok = xml_text_helper(cx, kidobj, kid, &v);
6642                 } else {
6643                     ok = JS_FALSE;
6644                     v = JSVAL_NULL;
6645                 }
6646                 js_LeaveLocalRootScopeWithResult(cx, Valueify(v));
6647                 if (!ok)
6648                     return JS_FALSE;
6649                 vxml = (JSXML *) JSVAL_TO_OBJECT(v)->getPrivate();
6650                 if (JSXML_LENGTH(vxml) != 0 && !Append(cx, list, vxml))
6651                     return JS_FALSE;
6652             }
6653         }
6654     } else {
6655         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6656             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6657             if (kid && kid->xml_class == JSXML_CLASS_TEXT) {
6658                 if (!Append(cx, list, kid))
6659                     return JS_FALSE;
6660             }
6661         }
6662     }
6663     return JS_TRUE;
6664 }
6665
6666 static JSBool
6667 xml_text(JSContext *cx, uintN argc, jsval *vp)
6668 {
6669     XML_METHOD_PROLOG;
6670     return xml_text_helper(cx, obj, xml, vp);
6671 }
6672
6673 /* XML and XMLList */
6674 static JSString *
6675 xml_toString_helper(JSContext *cx, JSXML *xml)
6676 {
6677     JSString *str, *kidstr;
6678
6679     if (xml->xml_class == JSXML_CLASS_ATTRIBUTE ||
6680         xml->xml_class == JSXML_CLASS_TEXT) {
6681         return xml->xml_value;
6682     }
6683
6684     if (!HasSimpleContent(xml))
6685         return ToXMLString(cx, OBJECT_TO_JSVAL(xml->object), 0);
6686
6687     str = cx->runtime->emptyString;
6688     if (!js_EnterLocalRootScope(cx))
6689         return NULL;
6690     JSXMLArrayCursor cursor(&xml->xml_kids);
6691     while (JSXML *kid = (JSXML *) cursor.getNext()) {
6692         if (kid->xml_class != JSXML_CLASS_COMMENT &&
6693             kid->xml_class != JSXML_CLASS_PROCESSING_INSTRUCTION) {
6694             kidstr = xml_toString_helper(cx, kid);
6695             if (!kidstr) {
6696                 str = NULL;
6697                 break;
6698             }
6699             str = js_ConcatStrings(cx, str, kidstr);
6700             if (!str)
6701                 break;
6702         }
6703     }
6704     js_LeaveLocalRootScopeWithResult(cx, str);
6705     return str;
6706 }
6707
6708 static JSBool
6709 xml_toSource(JSContext *cx, uintN argc, jsval *vp)
6710 {
6711     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
6712     if (!obj)
6713         return JS_FALSE;
6714     JSString *str = ToXMLString(cx, OBJECT_TO_JSVAL(obj), TO_SOURCE_FLAG);
6715     if (!str)
6716         return JS_FALSE;
6717     *vp = STRING_TO_JSVAL(str);
6718     return JS_TRUE;
6719 }
6720
6721 static JSBool
6722 xml_toString(JSContext *cx, uintN argc, jsval *vp)
6723 {
6724     JSString *str;
6725
6726     XML_METHOD_PROLOG;
6727     str = xml_toString_helper(cx, xml);
6728     if (!str)
6729         return JS_FALSE;
6730     *vp = STRING_TO_JSVAL(str);
6731     return JS_TRUE;
6732 }
6733
6734 /* XML and XMLList */
6735 static JSBool
6736 xml_toXMLString(JSContext *cx, uintN argc, jsval *vp)
6737 {
6738     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
6739     if (!obj)
6740         return JS_FALSE;
6741     JSString *str = ToXMLString(cx, OBJECT_TO_JSVAL(obj), 0);
6742     if (!str)
6743         return JS_FALSE;
6744     *vp = STRING_TO_JSVAL(str);
6745     return JS_TRUE;
6746 }
6747
6748 /* XML and XMLList */
6749 static JSBool
6750 xml_valueOf(JSContext *cx, uintN argc, jsval *vp)
6751 {
6752     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
6753     if (!obj)
6754         return false;
6755     *vp = OBJECT_TO_JSVAL(obj);
6756     return true;
6757 }
6758
6759 static JSFunctionSpec xml_methods[] = {
6760     JS_FN("addNamespace",          xml_addNamespace,          1,0),
6761     JS_FN("appendChild",           xml_appendChild,           1,0),
6762     JS_FN(js_attribute_str,        xml_attribute,             1,0),
6763     JS_FN("attributes",            xml_attributes,            0,0),
6764     JS_FN("child",                 xml_child,                 1,0),
6765     JS_FN("childIndex",            xml_childIndex,            0,0),
6766     JS_FN("children",              xml_children,              0,0),
6767     JS_FN("comments",              xml_comments,              0,0),
6768     JS_FN("contains",              xml_contains,              1,0),
6769     JS_FN("copy",                  xml_copy,                  0,0),
6770     JS_FN("descendants",           xml_descendants,           1,0),
6771     JS_FN("elements",              xml_elements,              1,0),
6772     JS_FN("hasOwnProperty",        xml_hasOwnProperty,        1,0),
6773     JS_FN("hasComplexContent",     xml_hasComplexContent,     1,0),
6774     JS_FN("hasSimpleContent",      xml_hasSimpleContent,      1,0),
6775     JS_FN("inScopeNamespaces",     xml_inScopeNamespaces,     0,0),
6776     JS_FN("insertChildAfter",      xml_insertChildAfter,      2,0),
6777     JS_FN("insertChildBefore",     xml_insertChildBefore,     2,0),
6778     JS_FN(js_length_str,           xml_length,                0,0),
6779     JS_FN(js_localName_str,        xml_localName,             0,0),
6780     JS_FN(js_name_str,             xml_name,                  0,0),
6781     JS_FN(js_namespace_str,        xml_namespace,             1,0),
6782     JS_FN("namespaceDeclarations", xml_namespaceDeclarations, 0,0),
6783     JS_FN("nodeKind",              xml_nodeKind,              0,0),
6784     JS_FN("normalize",             xml_normalize,             0,0),
6785     JS_FN(js_xml_parent_str,       xml_parent,                0,0),
6786     JS_FN("processingInstructions",xml_processingInstructions,1,0),
6787     JS_FN("prependChild",          xml_prependChild,          1,0),
6788     JS_FN("propertyIsEnumerable",  xml_propertyIsEnumerable,  1,0),
6789     JS_FN("removeNamespace",       xml_removeNamespace,       1,0),
6790     JS_FN("replace",               xml_replace,               2,0),
6791     JS_FN("setChildren",           xml_setChildren,           1,0),
6792     JS_FN("setLocalName",          xml_setLocalName,          1,0),
6793     JS_FN("setName",               xml_setName,               1,0),
6794     JS_FN("setNamespace",          xml_setNamespace,          1,0),
6795     JS_FN(js_text_str,             xml_text,                  0,0),
6796     JS_FN(js_toSource_str,         xml_toSource,              0,0),
6797     JS_FN(js_toString_str,         xml_toString,              0,0),
6798     JS_FN(js_toXMLString_str,      xml_toXMLString,           0,0),
6799     JS_FN(js_valueOf_str,          xml_valueOf,               0,0),
6800     JS_FS_END
6801 };
6802
6803 static JSBool
6804 CopyXMLSettings(JSContext *cx, JSObject *from, JSObject *to)
6805 {
6806     int i;
6807     const char *name;
6808     jsval v;
6809
6810     /* Note: PRETTY_INDENT is not a boolean setting. */
6811     for (i = 0; xml_static_props[i].name; i++) {
6812         name = xml_static_props[i].name;
6813         if (!JS_GetProperty(cx, from, name, &v))
6814             return false;
6815         if (name == js_prettyIndent_str) {
6816             if (!JSVAL_IS_NUMBER(v))
6817                 continue;
6818         } else {
6819             if (!JSVAL_IS_BOOLEAN(v))
6820                 continue;
6821         }
6822         if (!JS_SetProperty(cx, to, name, &v))
6823             return false;
6824     }
6825
6826     return true;
6827 }
6828
6829 static JSBool
6830 SetDefaultXMLSettings(JSContext *cx, JSObject *obj)
6831 {
6832     int i;
6833     jsval v;
6834
6835     /* Note: PRETTY_INDENT is not a boolean setting. */
6836     for (i = 0; xml_static_props[i].name; i++) {
6837         v = (xml_static_props[i].name != js_prettyIndent_str)
6838             ? JSVAL_TRUE : INT_TO_JSVAL(2);
6839         if (!JS_SetProperty(cx, obj, xml_static_props[i].name, &v))
6840             return JS_FALSE;
6841     }
6842     return true;
6843 }
6844
6845 static JSBool
6846 xml_settings(JSContext *cx, uintN argc, jsval *vp)
6847 {
6848     JSObject *settings = JS_NewObject(cx, NULL, NULL, NULL);
6849     if (!settings)
6850         return false;
6851     *vp = OBJECT_TO_JSVAL(settings);
6852     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
6853     if (!obj)
6854         return false;
6855     return CopyXMLSettings(cx, obj, settings);
6856 }
6857
6858 static JSBool
6859 xml_setSettings(JSContext *cx, uintN argc, jsval *vp)
6860 {
6861     JSObject *settings;
6862     jsval v;
6863     JSBool ok;
6864
6865     JSObject *obj = ToObject(cx, Valueify(&vp[1]));
6866     if (!obj)
6867         return JS_FALSE;
6868     v = (argc == 0) ? JSVAL_VOID : vp[2];
6869     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
6870         ok = SetDefaultXMLSettings(cx, obj);
6871     } else {
6872         if (JSVAL_IS_PRIMITIVE(v))
6873             return JS_TRUE;
6874         settings = JSVAL_TO_OBJECT(v);
6875         ok = CopyXMLSettings(cx, settings, obj);
6876     }
6877     return ok;
6878 }
6879
6880 static JSBool
6881 xml_defaultSettings(JSContext *cx, uintN argc, jsval *vp)
6882 {
6883     JSObject *settings;
6884
6885     settings = JS_NewObject(cx, NULL, NULL, NULL);
6886     if (!settings)
6887         return JS_FALSE;
6888     *vp = OBJECT_TO_JSVAL(settings);
6889     return SetDefaultXMLSettings(cx, settings);
6890 }
6891
6892 static JSFunctionSpec xml_static_methods[] = {
6893     JS_FN("settings",         xml_settings,          0,0),
6894     JS_FN("setSettings",      xml_setSettings,       1,0),
6895     JS_FN("defaultSettings",  xml_defaultSettings,   0,0),
6896     JS_FS_END
6897 };
6898
6899 static JSBool
6900 XML(JSContext *cx, uintN argc, Value *vp)
6901 {
6902     JSXML *xml, *copy;
6903     JSObject *xobj, *vobj;
6904     Class *clasp;
6905
6906     jsval v = argc ? Jsvalify(vp[2]) : JSVAL_VOID;
6907
6908     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
6909         v = STRING_TO_JSVAL(cx->runtime->emptyString);
6910
6911     xobj = ToXML(cx, v);
6912     if (!xobj)
6913         return JS_FALSE;
6914     xml = (JSXML *) xobj->getPrivate();
6915
6916     if (IsConstructing(vp) && !JSVAL_IS_PRIMITIVE(v)) {
6917         vobj = JSVAL_TO_OBJECT(v);
6918         clasp = vobj->getClass();
6919         if (clasp == &js_XMLClass ||
6920             (clasp->flags & JSCLASS_DOCUMENT_OBSERVER)) {
6921             copy = DeepCopy(cx, xml, NULL, 0);
6922             if (!copy)
6923                 return JS_FALSE;
6924             vp->setObject(*copy->object);
6925             return JS_TRUE;
6926         }
6927     }
6928
6929     vp->setObject(*xobj);
6930     return JS_TRUE;
6931 }
6932
6933 static JSBool
6934 XMLList(JSContext *cx, uintN argc, jsval *vp)
6935 {
6936     JSObject *vobj, *listobj;
6937     JSXML *xml, *list;
6938
6939     jsval v = argc ? vp[2] : JSVAL_VOID;
6940
6941     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
6942         v = STRING_TO_JSVAL(cx->runtime->emptyString);
6943
6944     if (IsConstructing(Valueify(vp)) && !JSVAL_IS_PRIMITIVE(v)) {
6945         vobj = JSVAL_TO_OBJECT(v);
6946         if (vobj->isXML()) {
6947             xml = (JSXML *) vobj->getPrivate();
6948             if (xml->xml_class == JSXML_CLASS_LIST) {
6949                 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6950                 if (!listobj)
6951                     return JS_FALSE;
6952                 *vp = OBJECT_TO_JSVAL(listobj);
6953
6954                 list = (JSXML *) listobj->getPrivate();
6955                 if (!Append(cx, list, xml))
6956                     return JS_FALSE;
6957                 return JS_TRUE;
6958             }
6959         }
6960     }
6961
6962     /* Toggle on XML support since the script has explicitly requested it. */
6963     listobj = ToXMLList(cx, v);
6964     if (!listobj)
6965         return JS_FALSE;
6966
6967     *vp = OBJECT_TO_JSVAL(listobj);
6968     return JS_TRUE;
6969 }
6970
6971 #ifdef DEBUG_notme
6972 JSCList xml_leaks = JS_INIT_STATIC_CLIST(&xml_leaks);
6973 uint32  xml_serial;
6974 #endif
6975
6976 JSXML *
6977 js_NewXML(JSContext *cx, JSXMLClass xml_class)
6978 {
6979     JSXML *xml = js_NewGCXML(cx);
6980     if (!xml)
6981         return NULL;
6982
6983     xml->object = NULL;
6984     xml->domnode = NULL;
6985     xml->parent = NULL;
6986     xml->name = NULL;
6987     xml->xml_class = xml_class;
6988     xml->xml_flags = 0;
6989     if (JSXML_CLASS_HAS_VALUE(xml_class)) {
6990         xml->xml_value = cx->runtime->emptyString;
6991     } else {
6992         xml->xml_kids.init();
6993         if (xml_class == JSXML_CLASS_LIST) {
6994             xml->xml_target = NULL;
6995             xml->xml_targetprop = NULL;
6996         } else {
6997             xml->xml_namespaces.init();
6998             xml->xml_attrs.init();
6999         }
7000     }
7001
7002 #ifdef DEBUG_notme
7003     JS_APPEND_LINK(&xml->links, &xml_leaks);
7004     xml->serial = xml_serial++;
7005 #endif
7006     METER(xml_stats.xml);
7007     return xml;
7008 }
7009
7010 void
7011 js_TraceXML(JSTracer *trc, JSXML *xml)
7012 {
7013     if (xml->object)
7014         MarkObject(trc, *xml->object, "object");
7015     if (xml->name)
7016         MarkObject(trc, *xml->name, "name");
7017     if (xml->parent)
7018         JS_CALL_TRACER(trc, xml->parent, JSTRACE_XML, "xml_parent");
7019
7020     if (JSXML_HAS_VALUE(xml)) {
7021         if (xml->xml_value)
7022             MarkString(trc, xml->xml_value, "value");
7023         return;
7024     }
7025
7026     xml_trace_vector(trc,
7027                      (JSXML **) xml->xml_kids.vector,
7028                      xml->xml_kids.length);
7029     XMLArrayCursorTrace(trc, xml->xml_kids.cursors);
7030     if (IS_GC_MARKING_TRACER(trc))
7031         xml->xml_kids.trim();
7032
7033     if (xml->xml_class == JSXML_CLASS_LIST) {
7034         if (xml->xml_target)
7035             JS_CALL_TRACER(trc, xml->xml_target, JSTRACE_XML, "target");
7036         if (xml->xml_targetprop)
7037             MarkObject(trc, *xml->xml_targetprop, "targetprop");
7038     } else {
7039         MarkObjectRange(trc, xml->xml_namespaces.length,
7040                         (JSObject **) xml->xml_namespaces.vector,
7041                         "xml_namespaces");
7042         XMLArrayCursorTrace(trc, xml->xml_namespaces.cursors);
7043         if (IS_GC_MARKING_TRACER(trc))
7044             xml->xml_namespaces.trim();
7045
7046         xml_trace_vector(trc,
7047                          (JSXML **) xml->xml_attrs.vector,
7048                          xml->xml_attrs.length);
7049         XMLArrayCursorTrace(trc, xml->xml_attrs.cursors);
7050         if (IS_GC_MARKING_TRACER(trc))
7051             xml->xml_attrs.trim();
7052     }
7053 }
7054
7055 JSObject *
7056 js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
7057 {
7058     JSXML *xml = js_NewXML(cx, xml_class);
7059     if (!xml)
7060         return NULL;
7061
7062     AutoXMLRooter root(cx, xml);
7063     return js_GetXMLObject(cx, xml);
7064 }
7065
7066 static JSObject *
7067 NewXMLObject(JSContext *cx, JSXML *xml)
7068 {
7069     JSObject *obj;
7070
7071     obj = NewNonFunction<WithProto::Class>(cx, &js_XMLClass, NULL, NULL);
7072     if (!obj)
7073         return NULL;
7074     obj->setPrivate(xml);
7075     METER(xml_stats.xmlobj);
7076     return obj;
7077 }
7078
7079 JSObject *
7080 js_GetXMLObject(JSContext *cx, JSXML *xml)
7081 {
7082     JSObject *obj;
7083
7084     obj = xml->object;
7085     if (obj) {
7086         JS_ASSERT(obj->getPrivate() == xml);
7087         return obj;
7088     }
7089
7090     obj = NewXMLObject(cx, xml);
7091     if (!obj)
7092         return NULL;
7093     xml->object = obj;
7094     return obj;
7095 }
7096
7097 JSObject *
7098 js_InitNamespaceClass(JSContext *cx, JSObject *obj)
7099 {
7100     return js_InitClass(cx, obj, NULL, &js_NamespaceClass, Namespace, 2,
7101                         namespace_props, namespace_methods, NULL, NULL);
7102 }
7103
7104 JSObject *
7105 js_InitQNameClass(JSContext *cx, JSObject *obj)
7106 {
7107     return js_InitClass(cx, obj, NULL, &js_QNameClass, QName, 2,
7108                         qname_props, qname_methods, NULL, NULL);
7109 }
7110
7111 JSObject *
7112 js_InitXMLClass(JSContext *cx, JSObject *obj)
7113 {
7114     JSObject *proto, *pobj;
7115     JSFunction *fun;
7116     JSXML *xml;
7117     JSProperty *prop;
7118     Shape *shape;
7119     jsval cval, vp[3];
7120
7121     /* Define the isXMLName function. */
7122     if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
7123         return NULL;
7124
7125     /* Define the XML class constructor and prototype. */
7126     proto = js_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1,
7127                          NULL, xml_methods,
7128                          xml_static_props, xml_static_methods);
7129     if (!proto)
7130         return NULL;
7131
7132     xml = js_NewXML(cx, JSXML_CLASS_TEXT);
7133     if (!xml)
7134         return NULL;
7135     proto->setPrivate(xml);
7136     xml->object = proto;
7137     METER(xml_stats.xmlobj);
7138
7139     /*
7140      * Prepare to set default settings on the XML constructor we just made.
7141      * NB: We can't use JS_GetConstructor, because it calls
7142      * JSObject::getProperty, which is xml_getProperty, which creates a new
7143      * XMLList every time!  We must instead call js_LookupProperty directly.
7144      */
7145     if (!js_LookupProperty(cx, proto,
7146                            ATOM_TO_JSID(cx->runtime->atomState.constructorAtom),
7147                            &pobj, &prop)) {
7148         return NULL;
7149     }
7150     JS_ASSERT(prop);
7151     shape = (Shape *) prop;
7152     cval = Jsvalify(pobj->nativeGetSlot(shape->slot));
7153     JS_ASSERT(VALUE_IS_FUNCTION(cx, cval));
7154
7155     /* Set default settings. */
7156     vp[0] = JSVAL_NULL;
7157     vp[1] = cval;
7158     vp[2] = JSVAL_VOID;
7159     if (!xml_setSettings(cx, 1, vp))
7160         return NULL;
7161
7162     /* Define the XMLList function and give it the same prototype as XML. */
7163     fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1, JSFUN_CONSTRUCTOR);
7164     if (!fun)
7165         return NULL;
7166     if (!js_SetClassPrototype(cx, FUN_OBJECT(fun), proto,
7167                               JSPROP_READONLY | JSPROP_PERMANENT)) {
7168         return NULL;
7169     }
7170     return proto;
7171 }
7172
7173 JSObject *
7174 js_InitXMLClasses(JSContext *cx, JSObject *obj)
7175 {
7176     if (!js_InitNamespaceClass(cx, obj))
7177         return NULL;
7178     if (!js_InitQNameClass(cx, obj))
7179         return NULL;
7180     return js_InitXMLClass(cx, obj);
7181 }
7182
7183 JSBool
7184 js_GetFunctionNamespace(JSContext *cx, Value *vp)
7185 {
7186     JSObject *global = cx->hasfp() ? cx->fp()->scopeChain().getGlobal() : cx->globalObject;
7187
7188     *vp = global->getReservedSlot(JSRESERVED_GLOBAL_FUNCTION_NS);
7189     if (vp->isUndefined()) {
7190         JSRuntime *rt = cx->runtime;
7191         JSLinearString *prefix = rt->atomState.typeAtoms[JSTYPE_FUNCTION];
7192         JSLinearString *uri = rt->atomState.functionNamespaceURIAtom;
7193         JSObject *obj = NewXMLNamespace(cx, prefix, uri, JS_FALSE);
7194         if (!obj)
7195             return false;
7196
7197         /*
7198          * Avoid entraining any in-scope Object.prototype.  The loss of
7199          * Namespace.prototype is not detectable, as there is no way to
7200          * refer to this instance in scripts.  When used to qualify method
7201          * names, its prefix and uri references are copied to the QName.
7202          * The parent remains set and links back to global.
7203          */
7204         obj->clearProto();
7205
7206         vp->setObject(*obj);
7207         if (!js_SetReservedSlot(cx, global, JSRESERVED_GLOBAL_FUNCTION_NS, *vp))
7208             return false;
7209     }
7210
7211     return true;
7212 }
7213
7214 /*
7215  * Note the asymmetry between js_GetDefaultXMLNamespace and js_SetDefaultXML-
7216  * Namespace.  Get searches fp->scopeChain for JS_DEFAULT_XML_NAMESPACE_ID,
7217  * while Set sets JS_DEFAULT_XML_NAMESPACE_ID in fp->varobj. There's no
7218  * requirement that fp->varobj lie directly on fp->scopeChain, although
7219  * it should be reachable using the prototype chain from a scope object (cf.
7220  * JSOPTION_VAROBJFIX in jsapi.h).
7221  *
7222  * If Get can't find JS_DEFAULT_XML_NAMESPACE_ID along the scope chain, it
7223  * creates a default namespace via 'new Namespace()'.  In contrast, Set uses
7224  * its v argument as the uri of a new Namespace, with "" as the prefix.  See
7225  * ECMA-357 12.1 and 12.1.1.  Note that if Set is called with a Namespace n,
7226  * the default XML namespace will be set to ("", n.uri).  So the uri string
7227  * is really the only usefully stored value of the default namespace.
7228  */
7229 JSBool
7230 js_GetDefaultXMLNamespace(JSContext *cx, jsval *vp)
7231 {
7232     JSObject *ns, *obj, *tmp;
7233     jsval v;
7234
7235     JSObject *scopeChain = GetScopeChain(cx);
7236
7237     obj = NULL;
7238     for (tmp = scopeChain; tmp; tmp = tmp->getParent()) {
7239         Class *clasp = tmp->getClass();
7240         if (clasp == &js_BlockClass || clasp == &js_WithClass)
7241             continue;
7242         if (!tmp->getProperty(cx, JS_DEFAULT_XML_NAMESPACE_ID, Valueify(&v)))
7243             return JS_FALSE;
7244         if (!JSVAL_IS_PRIMITIVE(v)) {
7245             *vp = v;
7246             return JS_TRUE;
7247         }
7248         obj = tmp;
7249     }
7250
7251     ns = js_ConstructObject(cx, &js_NamespaceClass, NULL, obj, 0, NULL);
7252     if (!ns)
7253         return JS_FALSE;
7254     v = OBJECT_TO_JSVAL(ns);
7255     if (!obj->defineProperty(cx, JS_DEFAULT_XML_NAMESPACE_ID, Valueify(v),
7256                              PropertyStub, StrictPropertyStub, JSPROP_PERMANENT)) {
7257         return JS_FALSE;
7258     }
7259     *vp = v;
7260     return JS_TRUE;
7261 }
7262
7263 JSBool
7264 js_SetDefaultXMLNamespace(JSContext *cx, const Value &v)
7265 {
7266     Value argv[2];
7267     argv[0].setString(cx->runtime->emptyString);
7268     argv[1] = v;
7269     JSObject *ns = js_ConstructObject(cx, &js_NamespaceClass, NULL, NULL, 2, argv);
7270     if (!ns)
7271         return JS_FALSE;
7272
7273     JSStackFrame *fp = js_GetTopStackFrame(cx);
7274     JSObject &varobj = fp->varobj(cx);
7275     if (!varobj.defineProperty(cx, JS_DEFAULT_XML_NAMESPACE_ID, ObjectValue(*ns),
7276                                PropertyStub, StrictPropertyStub, JSPROP_PERMANENT)) {
7277         return JS_FALSE;
7278     }
7279     return JS_TRUE;
7280 }
7281
7282 JSBool
7283 js_ToAttributeName(JSContext *cx, Value *vp)
7284 {
7285     JSObject *qn;
7286
7287     qn = ToAttributeName(cx, Jsvalify(*vp));
7288     if (!qn)
7289         return JS_FALSE;
7290     vp->setObject(*qn);
7291     return JS_TRUE;
7292 }
7293
7294 JSFlatString *
7295 js_EscapeAttributeValue(JSContext *cx, JSString *str, JSBool quote)
7296 {
7297     StringBuffer sb(cx);
7298     return EscapeAttributeValue(cx, sb, str, quote);
7299 }
7300
7301 JSString *
7302 js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
7303 {
7304     size_t len = str->length();
7305     const jschar *chars = str->getChars(cx);
7306     if (!chars)
7307         return NULL;
7308
7309     size_t len2 = str2->length();
7310     const jschar *chars2 = str2->getChars(cx);
7311     if (!chars2)
7312         return NULL;
7313
7314     size_t newlen = (isName) ? len + 1 + len2 : len + 2 + len2 + 1;
7315     jschar *newchars = (jschar *) cx->malloc((newlen+1) * sizeof(jschar));
7316     if (!newchars)
7317         return NULL;
7318
7319     js_strncpy(newchars, chars, len);
7320     newchars += len;
7321     if (isName) {
7322         *newchars++ = ' ';
7323         js_strncpy(newchars, chars2, len2);
7324         newchars += len2;
7325     } else {
7326         *newchars++ = '=';
7327         *newchars++ = '"';
7328         js_strncpy(newchars, chars2, len2);
7329         newchars += len2;
7330         *newchars++ = '"';
7331     }
7332     *newchars = 0;
7333     return js_NewString(cx, newchars - newlen, newlen);
7334 }
7335
7336 JSFlatString *
7337 js_EscapeElementValue(JSContext *cx, JSString *str)
7338 {
7339     StringBuffer sb(cx);
7340     return EscapeElementValue(cx, sb, str, 0);
7341 }
7342
7343 JSString *
7344 js_ValueToXMLString(JSContext *cx, const Value &v)
7345 {
7346     return ToXMLString(cx, Jsvalify(v), 0);
7347 }
7348
7349 JSBool
7350 js_GetAnyName(JSContext *cx, jsid *idp)
7351 {
7352     JSObject *global = cx->hasfp() ? cx->fp()->scopeChain().getGlobal() : cx->globalObject;
7353     Value v = global->getReservedSlot(JSProto_AnyName);
7354     if (v.isUndefined()) {
7355         JSObject *obj = NewNonFunction<WithProto::Given>(cx, &js_AnyNameClass, NULL, global);
7356         if (!obj)
7357             return false;
7358
7359         JS_ASSERT(!obj->getProto());
7360
7361         JSRuntime *rt = cx->runtime;
7362         InitXMLQName(obj, rt->emptyString, rt->emptyString,
7363                      ATOM_TO_STRING(rt->atomState.starAtom));
7364         METER(xml_stats.qname);
7365
7366         v.setObject(*obj);
7367         if (!js_SetReservedSlot(cx, global, JSProto_AnyName, v))
7368             return false;
7369     }
7370     *idp = OBJECT_TO_JSID(&v.toObject());
7371     return true;
7372 }
7373
7374 JSBool
7375 js_FindXMLProperty(JSContext *cx, const Value &nameval, JSObject **objp, jsid *idp)
7376 {
7377     JSObject *nameobj;
7378     jsval v;
7379     JSObject *qn;
7380     jsid funid;
7381     JSObject *obj, *target, *proto, *pobj;
7382     JSXML *xml;
7383     JSBool found;
7384     JSProperty *prop;
7385
7386     JS_ASSERT(nameval.isObject());
7387     nameobj = &nameval.toObject();
7388     if (nameobj->getClass() == &js_AnyNameClass) {
7389         v = ATOM_TO_JSVAL(cx->runtime->atomState.starAtom);
7390         nameobj = js_ConstructObject(cx, &js_QNameClass, NULL, NULL, 1,
7391                                      Valueify(&v));
7392         if (!nameobj)
7393             return JS_FALSE;
7394     } else {
7395         JS_ASSERT(nameobj->getClass() == &js_AttributeNameClass ||
7396                   nameobj->getClass() == &js_QNameClass);
7397     }
7398
7399     qn = nameobj;
7400     if (!IsFunctionQName(cx, qn, &funid))
7401         return JS_FALSE;
7402
7403     obj = &js_GetTopStackFrame(cx)->scopeChain();
7404     do {
7405         /* Skip any With object that can wrap XML. */
7406         target = obj;
7407         while (target->getClass() == &js_WithClass) {
7408              proto = target->getProto();
7409              if (!proto)
7410                  break;
7411              target = proto;
7412         }
7413
7414         if (target->isXML()) {
7415             if (JSID_IS_VOID(funid)) {
7416                 xml = (JSXML *) target->getPrivate();
7417                 found = HasNamedProperty(xml, qn);
7418             } else {
7419                 if (!HasFunctionProperty(cx, target, funid, &found))
7420                     return JS_FALSE;
7421             }
7422             if (found) {
7423                 *idp = OBJECT_TO_JSID(nameobj);
7424                 *objp = target;
7425                 return JS_TRUE;
7426             }
7427         } else if (!JSID_IS_VOID(funid)) {
7428             if (!target->lookupProperty(cx, funid, &pobj, &prop))
7429                 return JS_FALSE;
7430             if (prop) {
7431                 *idp = funid;
7432                 *objp = target;
7433                 return JS_TRUE;
7434             }
7435         }
7436     } while ((obj = obj->getParent()) != NULL);
7437
7438     JSAutoByteString printable;
7439     JSString *str = ConvertQNameToString(cx, nameobj);
7440     if (str && js_ValueToPrintable(cx, StringValue(str), &printable)) {
7441         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
7442                                      JSMSG_UNDEFINED_XML_NAME, printable.ptr());
7443     }
7444     return JS_FALSE;
7445 }
7446
7447 static JSBool
7448 GetXMLFunction(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
7449 {
7450     JS_ASSERT(obj->isXML());
7451
7452     /*
7453      * See comments before xml_lookupProperty about the need for the proto
7454      * chain lookup.
7455      */
7456     JSObject *target = obj;
7457     AutoObjectRooter tvr(cx);
7458     for (;;) {
7459         if (!js_GetProperty(cx, target, id, Valueify(vp)))
7460             return false;
7461         if (VALUE_IS_FUNCTION(cx, *vp))
7462             return true;
7463         target = target->getProto();
7464         if (target == NULL || !target->isNative())
7465             break;
7466         tvr.setObject(target);
7467     }
7468
7469     JSXML *xml = (JSXML *) obj->getPrivate();
7470     if (!HasSimpleContent(xml))
7471         return true;
7472
7473     /* Search in String.prototype to implement 11.2.2.1 Step 3(f). */
7474     if (!js_GetClassPrototype(cx, NULL, JSProto_String, tvr.addr()))
7475         return false;
7476
7477     JS_ASSERT(tvr.object());
7478     return tvr.object()->getProperty(cx, id, Valueify(vp));
7479 }
7480
7481 static JSXML *
7482 GetPrivate(JSContext *cx, JSObject *obj, const char *method)
7483 {
7484     JSXML *xml;
7485
7486     xml = (JSXML *) GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
7487     if (!xml) {
7488         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
7489                              JSMSG_INCOMPATIBLE_METHOD,
7490                              js_XML_str, method, obj->getClass()->name);
7491     }
7492     return xml;
7493 }
7494
7495 JSBool
7496 js_GetXMLDescendants(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
7497 {
7498     JSXML *xml, *list;
7499
7500     xml = GetPrivate(cx, obj, "descendants internal method");
7501     if (!xml)
7502         return JS_FALSE;
7503
7504     list = Descendants(cx, xml, id);
7505     if (!list)
7506         return JS_FALSE;
7507     *vp = OBJECT_TO_JSVAL(list->object);
7508     return JS_TRUE;
7509 }
7510
7511 JSBool
7512 js_DeleteXMLListElements(JSContext *cx, JSObject *listobj)
7513 {
7514     JSXML *list;
7515     uint32 n;
7516
7517     list = (JSXML *) listobj->getPrivate();
7518     for (n = list->xml_kids.length; n != 0; --n)
7519         DeleteListElement(cx, list, 0);
7520
7521     return JS_TRUE;
7522 }
7523
7524 struct JSXMLFilter
7525 {
7526     JSXML               *list;
7527     JSXML               *result;
7528     JSXML               *kid;
7529     JSXMLArrayCursor    cursor;
7530
7531     JSXMLFilter(JSXML *list, JSXMLArray *array)
7532       : list(list), result(NULL), kid(NULL), cursor(array) {}
7533
7534     ~JSXMLFilter() {}
7535 };
7536
7537 static void
7538 xmlfilter_trace(JSTracer *trc, JSObject *obj)
7539 {
7540     JSXMLFilter *filter = (JSXMLFilter *) obj->getPrivate();
7541     if (!filter)
7542         return;
7543
7544     JS_ASSERT(filter->list);
7545     JS_CALL_TRACER(trc, filter->list, JSTRACE_XML, "list");
7546     if (filter->result)
7547         JS_CALL_TRACER(trc, filter->result, JSTRACE_XML, "result");
7548     if (filter->kid)
7549         JS_CALL_TRACER(trc, filter->kid, JSTRACE_XML, "kid");
7550
7551     /*
7552      * We do not need to trace the cursor as that would be done when
7553      * tracing the filter->list.
7554      */
7555 }
7556
7557 static void
7558 xmlfilter_finalize(JSContext *cx, JSObject *obj)
7559 {
7560     JSXMLFilter *filter = (JSXMLFilter *) obj->getPrivate();
7561     if (!filter)
7562         return;
7563
7564     cx->destroy(filter);
7565 }
7566
7567 Class js_XMLFilterClass = {
7568     "XMLFilter",
7569     JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS | JSCLASS_MARK_IS_TRACE,
7570     PropertyStub,         /* addProperty */
7571     PropertyStub,         /* delProperty */
7572     PropertyStub,         /* getProperty */
7573     StrictPropertyStub,   /* setProperty */
7574     EnumerateStub,
7575     ResolveStub,
7576     ConvertStub,
7577     xmlfilter_finalize,
7578     NULL,                 /* reserved0   */
7579     NULL,                 /* checkAccess */
7580     NULL,                 /* call        */
7581     NULL,                 /* construct   */
7582     NULL,                 /* xdrObject   */
7583     NULL,                 /* hasInstance */
7584     JS_CLASS_TRACE(xmlfilter_trace)
7585 };
7586
7587 JSBool
7588 js_StepXMLListFilter(JSContext *cx, JSBool initialized)
7589 {
7590     jsval *sp;
7591     JSObject *obj, *filterobj, *resobj, *kidobj;
7592     JSXML *xml, *list;
7593     JSXMLFilter *filter;
7594
7595     LeaveTrace(cx);
7596     sp = Jsvalify(cx->regs->sp);
7597     if (!initialized) {
7598         /*
7599          * We haven't iterated yet, so initialize the filter based on the
7600          * value stored in sp[-2].
7601          */
7602         if (!VALUE_IS_XML(sp[-2])) {
7603             js_ReportValueError(cx, JSMSG_NON_XML_FILTER, -2, Valueify(sp[-2]), NULL);
7604             return JS_FALSE;
7605         }
7606         obj = JSVAL_TO_OBJECT(sp[-2]);
7607         xml = (JSXML *) obj->getPrivate();
7608
7609         if (xml->xml_class == JSXML_CLASS_LIST) {
7610             list = xml;
7611         } else {
7612             obj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7613             if (!obj)
7614                 return JS_FALSE;
7615
7616             /*
7617              * Root just-created obj. sp[-2] cannot be used yet for rooting
7618              * as it may be the only root holding xml.
7619              */
7620             sp[-1] = OBJECT_TO_JSVAL(obj);
7621             list = (JSXML *) obj->getPrivate();
7622             if (!Append(cx, list, xml))
7623                 return JS_FALSE;
7624         }
7625
7626         filterobj = NewNonFunction<WithProto::Given>(cx, &js_XMLFilterClass, NULL, NULL);
7627         if (!filterobj)
7628             return JS_FALSE;
7629
7630         /*
7631          * Init all filter fields before setPrivate exposes it to
7632          * xmlfilter_trace or xmlfilter_finalize.
7633          */
7634         filter = cx->create<JSXMLFilter>(list, &list->xml_kids);
7635         if (!filter)
7636             return JS_FALSE;
7637         filterobj->setPrivate(filter);
7638
7639         /* Store filterobj to use in the later iterations. */
7640         sp[-2] = OBJECT_TO_JSVAL(filterobj);
7641
7642         resobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7643         if (!resobj)
7644             return JS_FALSE;
7645
7646         /* This also roots resobj. */
7647         filter->result = (JSXML *) resobj->getPrivate();
7648     } else {
7649         /* We have iterated at least once. */
7650         JS_ASSERT(!JSVAL_IS_PRIMITIVE(sp[-2]));
7651         JS_ASSERT(JSVAL_TO_OBJECT(sp[-2])->getClass() == &js_XMLFilterClass);
7652         filter = (JSXMLFilter *) JSVAL_TO_OBJECT(sp[-2])->getPrivate();
7653         JS_ASSERT(filter->kid);
7654
7655         /* Check if the filter expression wants to append the element. */
7656         if (js_ValueToBoolean(Valueify(sp[-1])) &&
7657             !Append(cx, filter->result, filter->kid)) {
7658             return JS_FALSE;
7659         }
7660     }
7661
7662     /* Do the iteration. */
7663     filter->kid = (JSXML *) filter->cursor.getNext();
7664     if (!filter->kid) {
7665         /*
7666          * Do not defer finishing the cursor until the next GC cycle to avoid
7667          * accumulation of dead cursors associated with filter->list.
7668          */
7669         filter->cursor.disconnect();
7670         JS_ASSERT(filter->result->object);
7671         sp[-2] = OBJECT_TO_JSVAL(filter->result->object);
7672         kidobj = NULL;
7673     } else {
7674         kidobj = js_GetXMLObject(cx, filter->kid);
7675         if (!kidobj)
7676             return JS_FALSE;
7677     }
7678
7679     /* Null as kidobj at sp[-1] signals filter termination. */
7680     sp[-1] = OBJECT_TO_JSVAL(kidobj);
7681     return JS_TRUE;
7682 }
7683
7684 JSObject *
7685 js_ValueToXMLObject(JSContext *cx, const Value &v)
7686 {
7687     return ToXML(cx, Jsvalify(v));
7688 }
7689
7690 JSObject *
7691 js_ValueToXMLListObject(JSContext *cx, const Value &v)
7692 {
7693     return ToXMLList(cx, Jsvalify(v));
7694 }
7695
7696 JSObject *
7697 js_NewXMLSpecialObject(JSContext *cx, JSXMLClass xml_class, JSString *name,
7698                        JSString *value)
7699 {
7700     uintN flags;
7701     JSObject *obj;
7702     JSXML *xml;
7703     JSObject *qn;
7704
7705     if (!GetXMLSettingFlags(cx, &flags))
7706         return NULL;
7707
7708     if ((xml_class == JSXML_CLASS_COMMENT &&
7709          (flags & XSF_IGNORE_COMMENTS)) ||
7710         (xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
7711          (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS))) {
7712         return js_NewXMLObject(cx, JSXML_CLASS_TEXT);
7713     }
7714
7715     obj = js_NewXMLObject(cx, xml_class);
7716     if (!obj)
7717         return NULL;
7718     xml = (JSXML *) obj->getPrivate();
7719     if (name) {
7720         JSLinearString *linearName = name->ensureLinear(cx);
7721         if (!linearName)
7722             return NULL;
7723         qn = NewXMLQName(cx, cx->runtime->emptyString, NULL, linearName);
7724         if (!qn)
7725             return NULL;
7726         xml->name = qn;
7727     }
7728     xml->xml_value = value;
7729     return obj;
7730 }
7731
7732 JSString *
7733 js_MakeXMLCDATAString(JSContext *cx, JSString *str)
7734 {
7735     StringBuffer sb(cx);
7736     return MakeXMLCDATAString(cx, sb, str);
7737 }
7738
7739 JSString *
7740 js_MakeXMLCommentString(JSContext *cx, JSString *str)
7741 {
7742     StringBuffer sb(cx);
7743     return MakeXMLCommentString(cx, sb, str);
7744 }
7745
7746 JSString *
7747 js_MakeXMLPIString(JSContext *cx, JSString *name, JSString *str)
7748 {
7749     StringBuffer sb(cx);
7750     return MakeXMLPIString(cx, sb, name, str);
7751 }
7752
7753 #endif /* JS_HAS_XML_SUPPORT */