Code

update JS
[inkscape.git] / src / dom / js / jsxml.c
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 ***** */
40 #include "jsstddef.h"
41 #include "jsconfig.h"
43 #if JS_HAS_XML_SUPPORT
45 #include <math.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include "jstypes.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"
71 #ifdef DEBUG
72 #include <string.h>     /* for #ifdef DEBUG memset calls */
73 #endif
75 /*
76  * NOTES
77  * - in the js shell, you must use the -x command line option, or call
78  *   options('xml') before compiling anything that uses XML literals
79  *
80  * TODO
81  * - XXXbe patrol
82  * - Fuse objects and their JSXML* private data into single GC-things
83  * - fix function::foo vs. x.(foo == 42) collision using proper namespacing
84  * - fix the !TCF_HAS_DEFXMLNS optimization in js_FoldConstants
85  * - JSCLASS_DOCUMENT_OBSERVER support -- live two-way binding to Gecko's DOM!
86  * - JS_TypeOfValue sure could use a cleaner interface to "types"
87  */
89 #ifdef DEBUG_brendan
90 #define METERING        1
91 #endif
93 #ifdef METERING
94 static struct {
95     jsrefcount  qname;
96     jsrefcount  qnameobj;
97     jsrefcount  liveqname;
98     jsrefcount  liveqnameobj;
99     jsrefcount  namespace;
100     jsrefcount  namespaceobj;
101     jsrefcount  livenamespace;
102     jsrefcount  livenamespaceobj;
103     jsrefcount  xml;
104     jsrefcount  xmlobj;
105     jsrefcount  livexml;
106     jsrefcount  livexmlobj;
107 } xml_stats;
109 #define METER(x)        JS_ATOMIC_INCREMENT(&(x))
110 #define UNMETER(x)      JS_ATOMIC_DECREMENT(&(x))
111 #else
112 #define METER(x)        /* nothing */
113 #define UNMETER(x)      /* nothing */
114 #endif
116 /*
117  * Random utilities and global functions.
118  */
119 const char js_AnyName_str[]       = "AnyName";
120 const char js_AttributeName_str[] = "AttributeName";
121 const char js_isXMLName_str[]     = "isXMLName";
122 const char js_XMLList_str[]       = "XMLList";
123 const char js_localName_str[]     = "localName";
124 const char js_xml_parent_str[]    = "parent";
125 const char js_prefix_str[]        = "prefix";
126 const char js_toXMLString_str[]   = "toXMLString";
127 const char js_uri_str[]           = "uri";
129 const char js_amp_entity_str[]    = "&amp;";
130 const char js_gt_entity_str[]     = "&gt;";
131 const char js_lt_entity_str[]     = "&lt;";
132 const char js_quot_entity_str[]   = "&quot;";
134 #define IS_EMPTY(str) (JSSTRING_LENGTH(str) == 0)
135 #define IS_STAR(str)  (JSSTRING_LENGTH(str) == 1 && *JSSTRING_CHARS(str) == '*')
137 static JSBool
138 xml_isXMLName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
139               jsval *rval)
141     *rval = BOOLEAN_TO_JSVAL(js_IsXMLName(cx, argv[0]));
142     return JS_TRUE;
145 /*
146  * Namespace class and library functions.
147  */
148 enum namespace_tinyid {
149     NAMESPACE_PREFIX = -1,
150     NAMESPACE_URI = -2
151 };
153 static JSBool
154 namespace_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
156     JSXMLNamespace *ns;
158     if (!JSVAL_IS_INT(id))
159         return JS_TRUE;
161     ns = (JSXMLNamespace *)
162          JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, NULL);
163     if (!ns)
164         return JS_TRUE;
166     switch (JSVAL_TO_INT(id)) {
167       case NAMESPACE_PREFIX:
168         *vp = ns->prefix ? STRING_TO_JSVAL(ns->prefix) : JSVAL_VOID;
169         break;
170       case NAMESPACE_URI:
171         *vp = STRING_TO_JSVAL(ns->uri);
172         break;
173     }
174     return JS_TRUE;
177 static void
178 namespace_finalize(JSContext *cx, JSObject *obj)
180     JSXMLNamespace *ns;
181     JSRuntime *rt;
183     ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
184     if (!ns)
185         return;
186     JS_ASSERT(ns->object == obj);
187     ns->object = NULL;
188     UNMETER(xml_stats.livenamespaceobj);
190     rt = cx->runtime;
191     if (rt->functionNamespaceObject == obj)
192         rt->functionNamespaceObject = NULL;
195 static void
196 namespace_mark_vector(JSContext *cx, JSXMLNamespace **vec, uint32 len,
197                       void *arg)
199     uint32 i;
200     JSXMLNamespace *ns;
202     for (i = 0; i < len; i++) {
203         ns = vec[i];
204         {
205 #ifdef GC_MARK_DEBUG
206             char buf[100];
208             JS_snprintf(buf, sizeof buf, "%s=%s",
209                         ns->prefix ? JS_GetStringBytes(ns->prefix) : "",
210                         JS_GetStringBytes(ns->uri));
211 #else
212             const char *buf = NULL;
213 #endif
214             JS_MarkGCThing(cx, ns, buf, arg);
215         }
216     }
219 static uint32
220 namespace_mark(JSContext *cx, JSObject *obj, void *arg)
222     JSXMLNamespace *ns;
224     ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
225     JS_MarkGCThing(cx, ns, js_private_str, arg);
226     return 0;
229 static JSBool
230 namespace_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
232     JSXMLNamespace *ns, *ns2;
233     JSObject *obj2;
235     ns = (JSXMLNamespace *) JS_GetPrivate(cx, obj);
236     JS_ASSERT(JSVAL_IS_OBJECT(v));
237     obj2 = JSVAL_TO_OBJECT(v);
238     if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_NamespaceClass.base) {
239         *bp = JS_FALSE;
240     } else {
241         ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, obj2);
242         *bp = !js_CompareStrings(ns->uri, ns2->uri);
243     }
244     return JS_TRUE;
247 JS_FRIEND_DATA(JSExtendedClass) js_NamespaceClass = {
248   { "Namespace",
249     JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED,
250     JS_PropertyStub,   JS_PropertyStub,   namespace_getProperty, NULL,
251     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    namespace_finalize,
252     NULL,              NULL,              NULL,              NULL,
253     NULL,              NULL,              namespace_mark,    NULL },
254     namespace_equality,
255     NULL, NULL,
256     JSCLASS_NO_RESERVED_MEMBERS
257 };
259 #define NAMESPACE_ATTRS                                                       \
260     (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
262 static JSPropertySpec namespace_props[] = {
263     {js_prefix_str,    NAMESPACE_PREFIX,  NAMESPACE_ATTRS,   0, 0},
264     {js_uri_str,       NAMESPACE_URI,     NAMESPACE_ATTRS,   0, 0},
265     {0,0,0,0,0}
266 };
268 static JSBool
269 namespace_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
270                    jsval *rval)
272     JSXMLNamespace *ns;
274     ns = (JSXMLNamespace *)
275          JS_GetInstancePrivate(cx, obj, &js_NamespaceClass.base, argv);
276     if (!ns)
277         return JS_FALSE;
279     *rval = STRING_TO_JSVAL(ns->uri);
280     return JS_TRUE;
283 static JSFunctionSpec namespace_methods[] = {
284     {js_toString_str,  namespace_toString,        0,0,0},
285     {0,0,0,0,0}
286 };
288 JSXMLNamespace *
289 js_NewXMLNamespace(JSContext *cx, JSString *prefix, JSString *uri,
290                    JSBool declared)
292     JSXMLNamespace *ns;
294     ns = (JSXMLNamespace *)
295          js_NewGCThing(cx, GCX_NAMESPACE, sizeof(JSXMLNamespace));
296     if (!ns)
297         return NULL;
298     ns->object = NULL;
299     ns->prefix = prefix;
300     ns->uri = uri;
301     ns->declared = declared;
302     METER(xml_stats.namespace);
303     METER(xml_stats.livenamespace);
304     return ns;
307 void
308 js_MarkXMLNamespace(JSContext *cx, JSXMLNamespace *ns, void *arg)
310     JS_MarkGCThing(cx, ns->object, js_object_str, arg);
311     JS_MarkGCThing(cx, ns->prefix, js_prefix_str, arg);
312     JS_MarkGCThing(cx, ns->uri, js_uri_str, arg);
315 void
316 js_FinalizeXMLNamespace(JSContext *cx, JSXMLNamespace *ns)
318     UNMETER(xml_stats.livenamespace);
321 JSObject *
322 js_NewXMLNamespaceObject(JSContext *cx, JSString *prefix, JSString *uri,
323                          JSBool declared)
325     JSXMLNamespace *ns;
327     ns = js_NewXMLNamespace(cx, prefix, uri, declared);
328     if (!ns)
329         return NULL;
330     return js_GetXMLNamespaceObject(cx, ns);
333 JSObject *
334 js_GetXMLNamespaceObject(JSContext *cx, JSXMLNamespace *ns)
336     JSObject *obj;
338     obj = ns->object;
339     if (obj) {
340         JS_ASSERT(JS_GetPrivate(cx, obj) == ns);
341         return obj;
342     }
343     obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
344     if (!obj || !JS_SetPrivate(cx, obj, ns)) {
345         cx->newborn[GCX_OBJECT] = NULL;
346         return NULL;
347     }
348     ns->object = obj;
349     METER(xml_stats.namespaceobj);
350     METER(xml_stats.livenamespaceobj);
351     return obj;
354 /*
355  * QName class and library functions.
356  */
357 enum qname_tinyid {
358     QNAME_URI = -1,
359     QNAME_LOCALNAME = -2
360 };
362 static JSBool
363 qname_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
365     JSXMLQName *qn;
367     if (!JSVAL_IS_INT(id))
368         return JS_TRUE;
370     qn = (JSXMLQName *)
371          JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, NULL);
372     if (!qn)
373         return JS_TRUE;
375     switch (JSVAL_TO_INT(id)) {
376       case QNAME_URI:
377         *vp = qn->uri ? STRING_TO_JSVAL(qn->uri) : JSVAL_NULL;
378         break;
379       case QNAME_LOCALNAME:
380         *vp = STRING_TO_JSVAL(qn->localName);
381         break;
382     }
383     return JS_TRUE;
386 static void
387 qname_finalize(JSContext *cx, JSObject *obj)
389     JSXMLQName *qn;
391     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
392     if (!qn)
393         return;
394     JS_ASSERT(qn->object == obj);
395     qn->object = NULL;
396     UNMETER(xml_stats.liveqnameobj);
399 static void
400 anyname_finalize(JSContext* cx, JSObject* obj)
402     JSRuntime *rt;
404     /* Make sure the next call to js_GetAnyName doesn't try to use obj. */
405     rt = cx->runtime;
406     if (rt->anynameObject == obj)
407         rt->anynameObject = NULL;
409     qname_finalize(cx, obj);
412 static uint32
413 qname_mark(JSContext *cx, JSObject *obj, void *arg)
415     JSXMLQName *qn;
417     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
418     JS_MarkGCThing(cx, qn, js_private_str, arg);
419     return 0;
422 static JSBool
423 qname_identity(JSXMLQName *qna, JSXMLQName *qnb)
425     if (!qna->uri ^ !qnb->uri)
426         return JS_FALSE;
427     if (qna->uri && js_CompareStrings(qna->uri, qnb->uri))
428         return JS_FALSE;
429     return !js_CompareStrings(qna->localName, qnb->localName);
432 static JSBool
433 qname_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
435     JSXMLQName *qn, *qn2;
436     JSObject *obj2;
438     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
439     JS_ASSERT(JSVAL_IS_OBJECT(v));
440     obj2 = JSVAL_TO_OBJECT(v);
441     if (!obj2 || OBJ_GET_CLASS(cx, obj2) != &js_QNameClass.base) {
442         *bp = JS_FALSE;
443     } else {
444         qn2 = (JSXMLQName *) JS_GetPrivate(cx, obj2);
445         *bp = qname_identity(qn, qn2);
446     }
447     return JS_TRUE;
450 JS_FRIEND_DATA(JSExtendedClass) js_QNameClass = {
451   { "QName",
452     JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE | JSCLASS_IS_EXTENDED,
453     JS_PropertyStub,   JS_PropertyStub,   qname_getProperty, NULL,
454     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    qname_finalize,
455     NULL,              NULL,              NULL,              NULL,
456     NULL,              NULL,              qname_mark,        NULL },
457     qname_equality,
458     NULL, NULL,
459     JSCLASS_NO_RESERVED_MEMBERS
460 };
462 /*
463  * Classes for the ECMA-357-internal types AttributeName and AnyName, which
464  * are like QName, except that they have no property getters.  They share the
465  * qname_toString method, and therefore are exposed as constructable objects
466  * in this implementation.
467  */
468 JS_FRIEND_DATA(JSClass) js_AttributeNameClass = {
469     js_AttributeName_str, JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE,
470     JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
471     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    qname_finalize,
472     NULL,              NULL,              NULL,              NULL,
473     NULL,              NULL,              qname_mark,        NULL
474 };
476 JS_FRIEND_DATA(JSClass) js_AnyNameClass = {
477     js_AnyName_str,    JSCLASS_HAS_PRIVATE | JSCLASS_CONSTRUCT_PROTOTYPE,
478     JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
479     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    anyname_finalize,
480     NULL,              NULL,              NULL,              NULL,
481     NULL,              NULL,              qname_mark,        NULL
482 };
484 #define QNAME_ATTRS                                                           \
485     (JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED)
487 static JSPropertySpec qname_props[] = {
488     {js_uri_str,       QNAME_URI,         QNAME_ATTRS,       0, 0},
489     {js_localName_str, QNAME_LOCALNAME,   QNAME_ATTRS,       0, 0},
490     {0,0,0,0,0}
491 };
493 static JSBool
494 qname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
495                jsval *rval)
497     JSClass *clasp;
498     JSXMLQName *qn;
499     JSString *str, *qualstr;
500     size_t length;
501     jschar *chars;
503     clasp = OBJ_GET_CLASS(cx, obj);
504     if (clasp == &js_AttributeNameClass || clasp == &js_AnyNameClass) {
505         qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
506     } else {
507         qn = (JSXMLQName *)
508              JS_GetInstancePrivate(cx, obj, &js_QNameClass.base, argv);
509         if (!qn)
510             return JS_FALSE;
511     }
513     if (!qn->uri) {
514         /* No uri means wildcard qualifier. */
515         str = ATOM_TO_STRING(cx->runtime->atomState.starQualifierAtom);
516     } else if (IS_EMPTY(qn->uri)) {
517         /* Empty string for uri means localName is in no namespace. */
518         str = cx->runtime->emptyString;
519     } else {
520         qualstr = ATOM_TO_STRING(cx->runtime->atomState.qualifierAtom);
521         str = js_ConcatStrings(cx, qn->uri, qualstr);
522         if (!str)
523             return JS_FALSE;
524     }
525     str = js_ConcatStrings(cx, str, qn->localName);
526     if (!str)
527         return JS_FALSE;
529     if (str && clasp == &js_AttributeNameClass) {
530         length = JSSTRING_LENGTH(str);
531         chars = (jschar *) JS_malloc(cx, (length + 2) * sizeof(jschar));
532         if (!chars)
533             return JS_FALSE;
534         *chars = '@';
535         js_strncpy(chars + 1, JSSTRING_CHARS(str), length);
536         chars[++length] = 0;
537         str = js_NewString(cx, chars, length, 0);
538         if (!str) {
539             JS_free(cx, chars);
540             return JS_FALSE;
541         }
542     }
544     *rval = STRING_TO_JSVAL(str);
545     return JS_TRUE;
548 static JSFunctionSpec qname_methods[] = {
549     {js_toString_str,  qname_toString,    0,0,0},
550     {0,0,0,0,0}
551 };
553 JSXMLQName *
554 js_NewXMLQName(JSContext *cx, JSString *uri, JSString *prefix,
555                JSString *localName)
557     JSXMLQName *qn;
559     qn = (JSXMLQName *) js_NewGCThing(cx, GCX_QNAME, sizeof(JSXMLQName));
560     if (!qn)
561         return NULL;
562     qn->object = NULL;
563     qn->uri = uri;
564     qn->prefix = prefix;
565     qn->localName = localName;
566     METER(xml_stats.qname);
567     METER(xml_stats.liveqname);
568     return qn;
571 void
572 js_MarkXMLQName(JSContext *cx, JSXMLQName *qn, void *arg)
574     JS_MarkGCThing(cx, qn->object, js_object_str, arg);
575     JS_MarkGCThing(cx, qn->uri, js_uri_str, arg);
576     JS_MarkGCThing(cx, qn->prefix, js_prefix_str, arg);
577     JS_MarkGCThing(cx, qn->localName, js_localName_str, arg);
580 void
581 js_FinalizeXMLQName(JSContext *cx, JSXMLQName *qn)
583     UNMETER(xml_stats.liveqname);
586 JSObject *
587 js_NewXMLQNameObject(JSContext *cx, JSString *uri, JSString *prefix,
588                      JSString *localName)
590     JSXMLQName *qn;
592     qn = js_NewXMLQName(cx, uri, prefix, localName);
593     if (!qn)
594         return NULL;
595     return js_GetXMLQNameObject(cx, qn);
598 JSObject *
599 js_GetXMLQNameObject(JSContext *cx, JSXMLQName *qn)
601     JSObject *obj;
603     obj = qn->object;
604     if (obj) {
605         JS_ASSERT(JS_GetPrivate(cx, obj) == qn);
606         return obj;
607     }
608     obj = js_NewObject(cx, &js_QNameClass.base, NULL, NULL);
609     if (!obj || !JS_SetPrivate(cx, obj, qn)) {
610         cx->newborn[GCX_OBJECT] = NULL;
611         return NULL;
612     }
613     qn->object = obj;
614     METER(xml_stats.qnameobj);
615     METER(xml_stats.liveqnameobj);
616     return obj;
619 JSObject *
620 js_GetAttributeNameObject(JSContext *cx, JSXMLQName *qn)
622     JSObject *obj;
624     obj = qn->object;
625     if (obj) {
626         if (OBJ_GET_CLASS(cx, obj) == &js_AttributeNameClass)
627             return obj;
628         qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
629         if (!qn)
630             return NULL;
631     }
633     obj = js_NewObject(cx, &js_AttributeNameClass, NULL, NULL);
634     if (!obj || !JS_SetPrivate(cx, obj, qn)) {
635         cx->newborn[GCX_OBJECT] = NULL;
636         return NULL;
637     }
639     qn->object = obj;
640     METER(xml_stats.qnameobj);
641     METER(xml_stats.liveqnameobj);
642     return obj;
645 JSObject *
646 js_ConstructXMLQNameObject(JSContext *cx, jsval nsval, jsval lnval)
648     jsval argv[2];
650     /*
651      * ECMA-357 11.1.2,
652      * The _QualifiedIdentifier : PropertySelector :: PropertySelector_
653      * production, step 2.
654      */
655     if (!JSVAL_IS_PRIMITIVE(nsval) &&
656         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nsval)) == &js_AnyNameClass) {
657         nsval = JSVAL_NULL;
658     }
660     argv[0] = nsval;
661     argv[1] = lnval;
662     return js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, argv);
665 static JSBool
666 IsXMLName(const jschar *cp, size_t n)
668     JSBool rv;
669     jschar c;
671     rv = JS_FALSE;
672     if (n != 0 && JS_ISXMLNSSTART(*cp)) {
673         while (--n != 0) {
674             c = *++cp;
675             if (!JS_ISXMLNS(c))
676                 return rv;
677         }
678         rv = JS_TRUE;
679     }
680     return rv;
683 JSBool
684 js_IsXMLName(JSContext *cx, jsval v)
686     JSClass *clasp;
687     JSXMLQName *qn;
688     JSString *name;
689     JSErrorReporter older;
691     /*
692      * Inline specialization of the QName constructor called with v passed as
693      * the only argument, to compute the localName for the constructed qname,
694      * without actually allocating the object or computing its uri and prefix.
695      * See ECMA-357 13.1.2.1 step 1 and 13.3.2.
696      */
697     if (!JSVAL_IS_PRIMITIVE(v) &&
698         (clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(v)),
699          clasp == &js_QNameClass.base ||
700          clasp == &js_AttributeNameClass ||
701          clasp == &js_AnyNameClass)) {
702         qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
703         name = qn->localName;
704     } else {
705         older = JS_SetErrorReporter(cx, NULL);
706         name = js_ValueToString(cx, v);
707         JS_SetErrorReporter(cx, older);
708         if (!name) {
709             JS_ClearPendingException(cx);
710             return JS_FALSE;
711         }
712     }
714     return IsXMLName(JSSTRING_CHARS(name), JSSTRING_LENGTH(name));
717 static JSBool
718 Namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
720     jsval urival, prefixval;
721     JSObject *uriobj;
722     JSBool isNamespace, isQName;
723     JSClass *clasp;
724     JSString *empty, *prefix;
725     JSXMLNamespace *ns, *ns2;
726     JSXMLQName *qn;
728     urival = argv[argc > 1];
729     isNamespace = isQName = JS_FALSE;
730     if (!JSVAL_IS_PRIMITIVE(urival)) {
731         uriobj = JSVAL_TO_OBJECT(urival);
732         clasp = OBJ_GET_CLASS(cx, uriobj);
733         isNamespace = (clasp == &js_NamespaceClass.base);
734         isQName = (clasp == &js_QNameClass.base);
735     }
736 #ifdef __GNUC__         /* suppress bogus gcc warnings */
737     else uriobj = NULL;
738 #endif
740     if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
741         /* Namespace called as function. */
742         if (argc == 1 && isNamespace) {
743             /* Namespace called with one Namespace argument is identity. */
744             *rval = urival;
745             return JS_TRUE;
746         }
748         /* Create and return a new QName object exactly as if constructed. */
749         obj = js_NewObject(cx, &js_NamespaceClass.base, NULL, NULL);
750         if (!obj)
751             return JS_FALSE;
752         *rval = OBJECT_TO_JSVAL(obj);
753     }
754     METER(xml_stats.namespaceobj);
755     METER(xml_stats.livenamespaceobj);
757     /*
758      * Create and connect private data to rooted obj early, so we don't have
759      * to worry about rooting string newborns hanging off of the private data
760      * further below.
761      */
762     empty = cx->runtime->emptyString;
763     ns = js_NewXMLNamespace(cx, empty, empty, JS_FALSE);
764     if (!ns)
765         return JS_FALSE;
766     if (!JS_SetPrivate(cx, obj, ns))
767         return JS_FALSE;
768     ns->object = obj;
770     if (argc == 1) {
771         if (isNamespace) {
772             ns2 = (JSXMLNamespace *) JS_GetPrivate(cx, uriobj);
773             ns->uri = ns2->uri;
774             ns->prefix = ns2->prefix;
775         } else if (isQName &&
776                    (qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
777             ns->uri = qn->uri;
778             ns->prefix = qn->prefix;
779         } else {
780             ns->uri = js_ValueToString(cx, urival);
781             if (!ns->uri)
782                 return JS_FALSE;
784             /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
785             if (!IS_EMPTY(ns->uri))
786                 ns->prefix = NULL;
787         }
788     } else if (argc == 2) {
789         if (isQName &&
790             (qn = (JSXMLQName *) JS_GetPrivate(cx, uriobj))->uri) {
791             ns->uri = qn->uri;
792         } else {
793             ns->uri = js_ValueToString(cx, urival);
794             if (!ns->uri)
795                 return JS_FALSE;
796         }
798         prefixval = argv[0];
799         if (IS_EMPTY(ns->uri)) {
800             if (!JSVAL_IS_VOID(prefixval)) {
801                 prefix = js_ValueToString(cx, prefixval);
802                 if (!prefix)
803                     return JS_FALSE;
804                 if (!IS_EMPTY(prefix)) {
805                     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
806                                          JSMSG_BAD_XML_NAMESPACE,
807                                          js_ValueToPrintableString(cx,
808                                              STRING_TO_JSVAL(prefix)));
809                     return JS_FALSE;
810                 }
811             }
812         } else if (JSVAL_IS_VOID(prefixval) || !js_IsXMLName(cx, prefixval)) {
813             /* NULL here represents *undefined* in ECMA-357 13.2.2 4(d) etc. */
814             ns->prefix = NULL;
815         } else {
816             prefix = js_ValueToString(cx, prefixval);
817             if (!prefix)
818                 return JS_FALSE;
819             ns->prefix = prefix;
820         }
821     }
823     return JS_TRUE;
826 static JSBool
827 QName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
829     jsval nameval, nsval;
830     JSBool isQName, isNamespace;
831     JSXMLQName *qn;
832     JSString *uri, *prefix, *name;
833     JSObject *nsobj;
834     JSClass *clasp;
835     JSXMLNamespace *ns;
837     nameval = argv[argc > 1];
838     isQName =
839         !JSVAL_IS_PRIMITIVE(nameval) &&
840         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(nameval)) == &js_QNameClass.base;
842     if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
843         /* QName called as function. */
844         if (argc == 1 && isQName) {
845             /* QName called with one QName argument is identity. */
846             *rval = nameval;
847             return JS_TRUE;
848         }
850         /*
851          * Create and return a new QName object exactly as if constructed.
852          * Use the constructor's clasp so we can be shared by AttributeName
853          * (see below after this function).
854          */
855         obj = js_NewObject(cx,
856                            argv
857                            ? JS_ValueToFunction(cx, argv[-2])->clasp
858                            : &js_QNameClass.base,
859                            NULL, NULL);
860         if (!obj)
861             return JS_FALSE;
862         *rval = OBJECT_TO_JSVAL(obj);
863     }
864     METER(xml_stats.qnameobj);
865     METER(xml_stats.liveqnameobj);
867     if (isQName) {
868         /* If namespace is not specified and name is a QName, clone it. */
869         qn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nameval));
870         if (argc == 1) {
871             uri = qn->uri;
872             prefix = qn->prefix;
873             name = qn->localName;
874             goto out;
875         }
877         /* Namespace and qname were passed -- use the qname's localName. */
878         nameval = STRING_TO_JSVAL(qn->localName);
879     }
881     if (argc == 0) {
882         name = cx->runtime->emptyString;
883     } else {
884         name = js_ValueToString(cx, nameval);
885         if (!name)
886             return JS_FALSE;
888         /* Use argv[1] as a local root for name, even if it was not passed. */
889         argv[1] = STRING_TO_JSVAL(name);
890     }
892     nsval = argv[0];
893     if (argc == 1 || JSVAL_IS_VOID(nsval)) {
894         if (IS_STAR(name)) {
895             nsval = JSVAL_NULL;
896         } else {
897             if (!js_GetDefaultXMLNamespace(cx, &nsval))
898                 return JS_FALSE;
899         }
900     }
902     if (JSVAL_IS_NULL(nsval)) {
903         /* NULL prefix represents *undefined* in ECMA-357 13.3.2 5(a). */
904         uri = prefix = NULL;
905     } else {
906         /*
907          * Inline specialization of the Namespace constructor called with
908          * nsval passed as the only argument, to compute the uri and prefix
909          * for the constructed namespace, without actually allocating the
910          * object or computing other members.  See ECMA-357 13.3.2 6(a) and
911          * 13.2.2.
912          */
913         isNamespace = isQName = JS_FALSE;
914         if (!JSVAL_IS_PRIMITIVE(nsval)) {
915             nsobj = JSVAL_TO_OBJECT(nsval);
916             clasp = OBJ_GET_CLASS(cx, nsobj);
917             isNamespace = (clasp == &js_NamespaceClass.base);
918             isQName = (clasp == &js_QNameClass.base);
919         }
920 #ifdef __GNUC__         /* suppress bogus gcc warnings */
921         else nsobj = NULL;
922 #endif
924         if (isNamespace) {
925             ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
926             uri = ns->uri;
927             prefix = ns->prefix;
928         } else if (isQName &&
929                    (qn = (JSXMLQName *) JS_GetPrivate(cx, nsobj))->uri) {
930             uri = qn->uri;
931             prefix = qn->prefix;
932         } else {
933             uri = js_ValueToString(cx, nsval);
934             if (!uri)
935                 return JS_FALSE;
936             argv[0] = STRING_TO_JSVAL(uri);     /* local root */
938             /* NULL here represents *undefined* in ECMA-357 13.2.2 3(c)iii. */
939             prefix = IS_EMPTY(uri) ? cx->runtime->emptyString : NULL;
940         }
941     }
943 out:
944     qn = js_NewXMLQName(cx, uri, prefix, name);
945     if (!qn)
946         return JS_FALSE;
947     if (!JS_SetPrivate(cx, obj, qn))
948         return JS_FALSE;
949     qn->object = obj;
950     return JS_TRUE;
953 static JSBool
954 AttributeName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
955               jsval *rval)
957     /*
958      * Since js_AttributeNameClass was initialized, obj will have that as its
959      * class, not js_QNameClass.
960      */
961     return QName(cx, obj, argc, argv, rval);
964 /*
965  * XMLArray library functions.
966  */
967 static JSBool
968 namespace_identity(const void *a, const void *b)
970     const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
971     const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
973     if (nsa->prefix && nsb->prefix) {
974         if (js_CompareStrings(nsa->prefix, nsb->prefix))
975             return JS_FALSE;
976     } else {
977         if (nsa->prefix || nsb->prefix)
978             return JS_FALSE;
979     }
980     return !js_CompareStrings(nsa->uri, nsb->uri);
983 static JSBool
984 attr_identity(const void *a, const void *b)
986     const JSXML *xmla = (const JSXML *) a;
987     const JSXML *xmlb = (const JSXML *) b;
989     return qname_identity(xmla->name, xmlb->name);
992 static void
993 XMLArrayCursorInit(JSXMLArrayCursor *cursor, JSXMLArray *array)
995     JSXMLArrayCursor *next;
997     cursor->array = array;
998     cursor->index = 0;
999     next = cursor->next = array->cursors;
1000     if (next)
1001         next->prevp = &cursor->next;
1002     cursor->prevp = &array->cursors;
1003     array->cursors = cursor;
1004     cursor->root = NULL;
1007 static void
1008 XMLArrayCursorFinish(JSXMLArrayCursor *cursor)
1010     JSXMLArrayCursor *next;
1012     if (!cursor->array)
1013         return;
1014     next = cursor->next;
1015     if (next)
1016         next->prevp = cursor->prevp;
1017     *cursor->prevp = next;
1018     cursor->array = NULL;
1021 static void *
1022 XMLArrayCursorNext(JSXMLArrayCursor *cursor)
1024     JSXMLArray *array;
1026     array = cursor->array;
1027     if (!array || cursor->index >= array->length)
1028         return NULL;
1029     return cursor->root = array->vector[cursor->index++];
1032 static void *
1033 XMLArrayCursorItem(JSXMLArrayCursor *cursor)
1035     JSXMLArray *array;
1037     array = cursor->array;
1038     if (!array || cursor->index >= array->length)
1039         return NULL;
1040     return cursor->root = array->vector[cursor->index];
1043 static void
1044 XMLArrayCursorMark(JSContext *cx, JSXMLArrayCursor *cursor)
1046     while (cursor) {
1047         GC_MARK(cx, cursor->root, "cursor->root", NULL);
1048         cursor = cursor->next;
1049     }
1052 /* NB: called with null cx from the GC, via xml_mark => XMLArrayTrim. */
1053 static JSBool
1054 XMLArraySetCapacity(JSContext *cx, JSXMLArray *array, uint32 capacity)
1056     void **vector;
1058     if (capacity == 0) {
1059         /* We could let realloc(p, 0) free this, but purify gets confused. */
1060         if (array->vector)
1061             free(array->vector);
1062         vector = NULL;
1063     } else {
1064         if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1065             !(vector = (void **)
1066                        realloc(array->vector, capacity * sizeof(void *)))) {
1067             if (cx)
1068                 JS_ReportOutOfMemory(cx);
1069             return JS_FALSE;
1070         }
1071     }
1072     array->capacity = JSXML_PRESET_CAPACITY | capacity;
1073     array->vector = vector;
1074     return JS_TRUE;
1077 static void
1078 XMLArrayTrim(JSXMLArray *array)
1080     if (array->capacity & JSXML_PRESET_CAPACITY)
1081         return;
1082     if (array->length < array->capacity)
1083         XMLArraySetCapacity(NULL, array, array->length);
1086 static JSBool
1087 XMLArrayInit(JSContext *cx, JSXMLArray *array, uint32 capacity)
1089     array->length = array->capacity = 0;
1090     array->vector = NULL;
1091     array->cursors = NULL;
1092     return capacity == 0 || XMLArraySetCapacity(cx, array, capacity);
1095 static void
1096 XMLArrayFinish(JSContext *cx, JSXMLArray *array)
1098     JSXMLArrayCursor *cursor;
1100     JS_free(cx, array->vector);
1102     while ((cursor = array->cursors) != NULL)
1103         XMLArrayCursorFinish(cursor);
1105 #ifdef DEBUG
1106     memset(array, 0xd5, sizeof *array);
1107 #endif
1110 #define XML_NOT_FOUND   ((uint32) -1)
1112 static uint32
1113 XMLArrayFindMember(const JSXMLArray *array, void *elt, JSIdentityOp identity)
1115     void **vector;
1116     uint32 i, n;
1118     /* The identity op must not reallocate array->vector. */
1119     vector = array->vector;
1120     if (identity) {
1121         for (i = 0, n = array->length; i < n; i++) {
1122             if (identity(vector[i], elt))
1123                 return i;
1124         }
1125     } else {
1126         for (i = 0, n = array->length; i < n; i++) {
1127             if (vector[i] == elt)
1128                 return i;
1129         }
1130     }
1131     return XML_NOT_FOUND;
1134 /*
1135  * Grow array vector capacity by powers of two to LINEAR_THRESHOLD, and after
1136  * that, grow by LINEAR_INCREMENT.  Both must be powers of two, and threshold
1137  * should be greater than increment.
1138  */
1139 #define LINEAR_THRESHOLD        256
1140 #define LINEAR_INCREMENT        32
1142 static JSBool
1143 XMLArrayAddMember(JSContext *cx, JSXMLArray *array, uint32 index, void *elt)
1145     uint32 capacity, i;
1146     int log2;
1147     void **vector;
1149     if (index >= array->length) {
1150         if (index >= JSXML_CAPACITY(array)) {
1151             /* Arrange to clear JSXML_PRESET_CAPACITY from array->capacity. */
1152             capacity = index + 1;
1153             if (index >= LINEAR_THRESHOLD) {
1154                 capacity = JS_ROUNDUP(capacity, LINEAR_INCREMENT);
1155             } else {
1156                 JS_CEILING_LOG2(log2, capacity);
1157                 capacity = JS_BIT(log2);
1158             }
1159             if ((size_t)capacity > ~(size_t)0 / sizeof(void *) ||
1160                 !(vector = (void **)
1161                            realloc(array->vector, capacity * sizeof(void *)))) {
1162                 JS_ReportOutOfMemory(cx);
1163                 return JS_FALSE;
1164             }
1165             array->capacity = capacity;
1166             array->vector = vector;
1167             for (i = array->length; i < index; i++)
1168                 vector[i] = NULL;
1169         }
1170         array->length = index + 1;
1171     }
1173     array->vector[index] = elt;
1174     return JS_TRUE;
1177 static JSBool
1178 XMLArrayInsert(JSContext *cx, JSXMLArray *array, uint32 i, uint32 n)
1180     uint32 j;
1181     JSXMLArrayCursor *cursor;
1183     j = array->length;
1184     JS_ASSERT(i <= j);
1185     if (!XMLArraySetCapacity(cx, array, j + n))
1186         return JS_FALSE;
1188     array->length = j + n;
1189     JS_ASSERT(n != (uint32)-1);
1190     while (j != i) {
1191         --j;
1192         array->vector[j + n] = array->vector[j];
1193     }
1195     for (cursor = array->cursors; cursor; cursor = cursor->next) {
1196         if (cursor->index > i)
1197             cursor->index += n;
1198     }
1199     return JS_TRUE;
1202 static void *
1203 XMLArrayDelete(JSContext *cx, JSXMLArray *array, uint32 index, JSBool compress)
1205     uint32 length;
1206     void **vector, *elt;
1207     JSXMLArrayCursor *cursor;
1209     length = array->length;
1210     if (index >= length)
1211         return NULL;
1213     vector = array->vector;
1214     elt = vector[index];
1215     if (compress) {
1216         while (++index < length)
1217             vector[index-1] = vector[index];
1218         array->length = length - 1;
1219         array->capacity = JSXML_CAPACITY(array);
1220     } else {
1221         vector[index] = NULL;
1222     }
1224     for (cursor = array->cursors; cursor; cursor = cursor->next) {
1225         if (cursor->index > index)
1226             --cursor->index;
1227     }
1228     return elt;
1231 static void
1232 XMLArrayTruncate(JSContext *cx, JSXMLArray *array, uint32 length)
1234     void **vector;
1236     JS_ASSERT(!array->cursors);
1237     if (length >= array->length)
1238         return;
1240     if (length == 0) {
1241         if (array->vector)
1242             free(array->vector);
1243         vector = NULL;
1244     } else {
1245         vector = realloc(array->vector, length * sizeof(void *));
1246         if (!vector)
1247             return;
1248     }
1250     if (array->length > length)
1251         array->length = length;
1252     array->capacity = length;
1253     array->vector = vector;
1256 #define XMLARRAY_FIND_MEMBER(a,e,f) XMLArrayFindMember(a, (void *)(e), f)
1257 #define XMLARRAY_HAS_MEMBER(a,e,f)  (XMLArrayFindMember(a, (void *)(e), f) != \
1258                                      XML_NOT_FOUND)
1259 #define XMLARRAY_MEMBER(a,i,t)      (((i) < (a)->length)                      \
1260                                      ? (t *) (a)->vector[i]                   \
1261                                      : NULL)
1262 #define XMLARRAY_SET_MEMBER(a,i,e)  JS_BEGIN_MACRO                            \
1263                                         if ((a)->length <= (i))               \
1264                                             (a)->length = (i) + 1;            \
1265                                         ((a)->vector[i] = (void *)(e));       \
1266                                     JS_END_MACRO
1267 #define XMLARRAY_ADD_MEMBER(x,a,i,e)XMLArrayAddMember(x, a, i, (void *)(e))
1268 #define XMLARRAY_INSERT(x,a,i,n)    XMLArrayInsert(x, a, i, n)
1269 #define XMLARRAY_APPEND(x,a,e)      XMLARRAY_ADD_MEMBER(x, a, (a)->length, (e))
1270 #define XMLARRAY_DELETE(x,a,i,c,t)  ((t *) XMLArrayDelete(x, a, i, c))
1271 #define XMLARRAY_TRUNCATE(x,a,n)    XMLArrayTruncate(x, a, n)
1273 /*
1274  * Define XML setting property strings and constants early, so everyone can
1275  * use the same names and their magic numbers (tinyids, flags).
1276  */
1277 static const char js_ignoreComments_str[]   = "ignoreComments";
1278 static const char js_ignoreProcessingInstructions_str[]
1279                                             = "ignoreProcessingInstructions";
1280 static const char js_ignoreWhitespace_str[] = "ignoreWhitespace";
1281 static const char js_prettyPrinting_str[]   = "prettyPrinting";
1282 static const char js_prettyIndent_str[]     = "prettyIndent";
1284 /*
1285  * NB: These XML static property tinyids must
1286  * (a) not collide with the generic negative tinyids at the top of jsfun.c;
1287  * (b) index their corresponding xml_static_props array elements.
1288  * Don't change 'em!
1289  */
1290 enum xml_static_tinyid {
1291     XML_IGNORE_COMMENTS,
1292     XML_IGNORE_PROCESSING_INSTRUCTIONS,
1293     XML_IGNORE_WHITESPACE,
1294     XML_PRETTY_PRINTING,
1295     XML_PRETTY_INDENT
1296 };
1298 static JSBool
1299 xml_setting_getter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1301     return JS_TRUE;
1304 static JSBool
1305 xml_setting_setter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
1307     JSBool b;
1308     uint8 flag;
1310     JS_ASSERT(JSVAL_IS_INT(id));
1311     if (!js_ValueToBoolean(cx, *vp, &b))
1312         return JS_FALSE;
1314     flag = JS_BIT(JSVAL_TO_INT(id));
1315     if (b)
1316         cx->xmlSettingFlags |= flag;
1317     else
1318         cx->xmlSettingFlags &= ~flag;
1319     return JS_TRUE;
1322 static JSPropertySpec xml_static_props[] = {
1323     {js_ignoreComments_str,     XML_IGNORE_COMMENTS,   JSPROP_PERMANENT,
1324                                 xml_setting_getter, xml_setting_setter},
1325     {js_ignoreProcessingInstructions_str,
1326                    XML_IGNORE_PROCESSING_INSTRUCTIONS, JSPROP_PERMANENT,
1327                                 xml_setting_getter, xml_setting_setter},
1328     {js_ignoreWhitespace_str,   XML_IGNORE_WHITESPACE, JSPROP_PERMANENT,
1329                                 xml_setting_getter, xml_setting_setter},
1330     {js_prettyPrinting_str,     XML_PRETTY_PRINTING,   JSPROP_PERMANENT,
1331                                 xml_setting_getter, xml_setting_setter},
1332     {js_prettyIndent_str,       XML_PRETTY_INDENT,     JSPROP_PERMANENT,
1333                                 xml_setting_getter, NULL},
1334     {0,0,0,0,0}
1335 };
1337 /* Derive cx->xmlSettingFlags bits from xml_static_props tinyids. */
1338 #define XSF_IGNORE_COMMENTS     JS_BIT(XML_IGNORE_COMMENTS)
1339 #define XSF_IGNORE_PROCESSING_INSTRUCTIONS                                    \
1340                                 JS_BIT(XML_IGNORE_PROCESSING_INSTRUCTIONS)
1341 #define XSF_IGNORE_WHITESPACE   JS_BIT(XML_IGNORE_WHITESPACE)
1342 #define XSF_PRETTY_PRINTING     JS_BIT(XML_PRETTY_PRINTING)
1343 #define XSF_CACHE_VALID         JS_BIT(XML_PRETTY_INDENT)
1345 /*
1346  * Extra, unrelated but necessarily disjoint flag used by ParseNodeToXML.
1347  * This flag means a couple of things:
1348  *
1349  * - The top JSXML created for a parse tree must have an object owning it.
1350  *
1351  * - That the default namespace normally inherited from the temporary
1352  *   <parent xmlns='...'> tag that wraps a runtime-concatenated XML source
1353  *   string must, in the case of a precompiled XML object tree, inherit via
1354  *   ad-hoc code in ParseNodeToXML.
1355  *
1356  * Because of the second purpose, we name this flag XSF_PRECOMPILED_ROOT.
1357  */
1358 #define XSF_PRECOMPILED_ROOT    (XSF_CACHE_VALID << 1)
1360 /* Macros for special-casing xml:, xmlns= and xmlns:foo= in ParseNodeToQName. */
1361 #define IS_XML(str)                                                           \
1362     (JSSTRING_LENGTH(str) == 3 && IS_XML_CHARS(JSSTRING_CHARS(str)))
1364 #define IS_XMLNS(str)                                                         \
1365     (JSSTRING_LENGTH(str) == 5 && IS_XMLNS_CHARS(JSSTRING_CHARS(str)))
1367 #define IS_XML_CHARS(chars)                                                   \
1368     (JS_TOLOWER((chars)[0]) == 'x' &&                                         \
1369      JS_TOLOWER((chars)[1]) == 'm' &&                                         \
1370      JS_TOLOWER((chars)[2]) == 'l')
1372 #define HAS_NS_AFTER_XML(chars)                                               \
1373     (JS_TOLOWER((chars)[3]) == 'n' &&                                         \
1374      JS_TOLOWER((chars)[4]) == 's')
1376 #define IS_XMLNS_CHARS(chars)                                                 \
1377     (IS_XML_CHARS(chars) && HAS_NS_AFTER_XML(chars))
1379 #define STARTS_WITH_XML(chars,length)                                         \
1380     (length >= 3 && IS_XML_CHARS(chars))
1382 static const char xml_namespace_str[] = "http://www.w3.org/XML/1998/namespace";
1383 static const char xmlns_namespace_str[] = "http://www.w3.org/2000/xmlns/";
1385 static JSXMLQName *
1386 ParseNodeToQName(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
1387                  JSBool isAttributeName)
1389     JSString *str, *uri, *prefix, *localName;
1390     size_t length, offset;
1391     const jschar *start, *limit, *colon;
1392     uint32 n;
1393     JSXMLNamespace *ns;
1395     JS_ASSERT(pn->pn_arity == PN_NULLARY);
1396     str = ATOM_TO_STRING(pn->pn_atom);
1397     length = JSSTRING_LENGTH(str);
1398     start = JSSTRING_CHARS(str);
1399     JS_ASSERT(length != 0 && *start != '@');
1400     JS_ASSERT(length != 1 || *start != '*');
1402     uri = cx->runtime->emptyString;
1403     limit = start + length;
1404     colon = js_strchr_limit(start, ':', limit);
1405     if (colon) {
1406         offset = PTRDIFF(colon, start, jschar);
1407         prefix = js_NewDependentString(cx, str, 0, offset, 0);
1408         if (!prefix)
1409             return NULL;
1411         if (STARTS_WITH_XML(start, offset)) {
1412             if (offset == 3) {
1413                 uri = JS_InternString(cx, xml_namespace_str);
1414                 if (!uri)
1415                     return NULL;
1416             } else if (offset == 5 && HAS_NS_AFTER_XML(start)) {
1417                 uri = JS_InternString(cx, xmlns_namespace_str);
1418                 if (!uri)
1419                     return NULL;
1420             } else {
1421                 uri = NULL;
1422             }
1423         } else {
1424             uri = NULL;
1425             n = inScopeNSes->length;
1426             while (n != 0) {
1427                 --n;
1428                 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
1429                 if (ns->prefix && !js_CompareStrings(ns->prefix, prefix)) {
1430                     uri = ns->uri;
1431                     break;
1432                 }
1433             }
1434         }
1436         if (!uri) {
1437             js_ReportCompileErrorNumber(cx, pn,
1438                                         JSREPORT_PN | JSREPORT_ERROR,
1439                                         JSMSG_BAD_XML_NAMESPACE,
1440                                         js_ValueToPrintableString(cx,
1441                                             STRING_TO_JSVAL(prefix)));
1442             return NULL;
1443         }
1445         localName = js_NewStringCopyN(cx, colon + 1, length - (offset + 1), 0);
1446         if (!localName)
1447             return NULL;
1448     } else {
1449         if (isAttributeName) {
1450             /*
1451              * An unprefixed attribute is not in any namespace, so set prefix
1452              * as well as uri to the empty string.
1453              */
1454             prefix = uri;
1455         } else {
1456             /*
1457              * Loop from back to front looking for the closest declared default
1458              * namespace.
1459              */
1460             n = inScopeNSes->length;
1461             while (n != 0) {
1462                 --n;
1463                 ns = XMLARRAY_MEMBER(inScopeNSes, n, JSXMLNamespace);
1464                 if (!ns->prefix || IS_EMPTY(ns->prefix)) {
1465                     uri = ns->uri;
1466                     break;
1467                 }
1468             }
1469             prefix = NULL;
1470         }
1471         localName = str;
1472     }
1474     return js_NewXMLQName(cx, uri, prefix, localName);
1477 static JSString *
1478 ChompXMLWhitespace(JSContext *cx, JSString *str)
1480     size_t length, newlength, offset;
1481     const jschar *cp, *start, *end;
1482     jschar c;
1484     length = JSSTRING_LENGTH(str);
1485     for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
1486         c = *cp;
1487         if (!JS_ISXMLSPACE(c))
1488             break;
1489     }
1490     while (end > cp) {
1491         c = end[-1];
1492         if (!JS_ISXMLSPACE(c))
1493             break;
1494         --end;
1495     }
1496     newlength = PTRDIFF(end, cp, jschar);
1497     if (newlength == length)
1498         return str;
1499     offset = PTRDIFF(cp, start, jschar);
1500     return js_NewDependentString(cx, str, offset, newlength, 0);
1503 static JSXML *
1504 ParseNodeToXML(JSContext *cx, JSParseNode *pn, JSXMLArray *inScopeNSes,
1505                uintN flags)
1507     JSXML *xml, *kid, *attr, *attrj;
1508     JSString *str;
1509     uint32 length, n, i, j;
1510     JSParseNode *pn2, *pn3, *head, **pnp;
1511     JSXMLNamespace *ns;
1512     JSXMLQName *qn, *attrjqn;
1513     JSXMLClass xml_class;
1515 #define PN2X_SKIP_CHILD ((JSXML *) 1)
1517     /*
1518      * Cases return early to avoid common code that gets an outermost xml's
1519      * object, which protects GC-things owned by xml and its descendants from
1520      * garbage collection.
1521      */
1522     xml = NULL;
1523     if (!JS_EnterLocalRootScope(cx))
1524         return NULL;
1525     switch (pn->pn_type) {
1526       case TOK_XMLELEM:
1527         length = inScopeNSes->length;
1528         pn2 = pn->pn_head;
1529         xml = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1530         if (!xml)
1531             goto fail;
1532         if (js_PushLocalRoot(cx, cx->localRootStack, (jsval)xml) < 0)
1533             goto fail;
1535         flags &= ~XSF_PRECOMPILED_ROOT;
1536         n = pn->pn_count;
1537         JS_ASSERT(n >= 2);
1538         n -= 2;
1539         if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1540             goto fail;
1542         i = 0;
1543         while ((pn2 = pn2->pn_next) != NULL) {
1544             if (!pn2->pn_next) {
1545                 /* Don't append the end tag! */
1546                 JS_ASSERT(pn2->pn_type == TOK_XMLETAGO);
1547                 break;
1548             }
1550             if ((flags & XSF_IGNORE_WHITESPACE) &&
1551                 n > 1 && pn2->pn_type == TOK_XMLSPACE) {
1552                 --n;
1553                 continue;
1554             }
1556             kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1557             if (kid == PN2X_SKIP_CHILD) {
1558                 --n;
1559                 continue;
1560             }
1562             if (!kid)
1563                 goto fail;
1565             /* Store kid in xml right away, to protect it from GC. */
1566             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1567             kid->parent = xml;
1568             ++i;
1570             /* XXX where is this documented in an XML spec, or in E4X? */
1571             if ((flags & XSF_IGNORE_WHITESPACE) &&
1572                 n > 1 && kid->xml_class == JSXML_CLASS_TEXT) {
1573                 str = ChompXMLWhitespace(cx, kid->xml_value);
1574                 if (!str)
1575                     goto fail;
1576                 kid->xml_value = str;
1577             }
1578         }
1580         JS_ASSERT(i == n);
1581         if (n < pn->pn_count - 2)
1582             XMLArrayTrim(&xml->xml_kids);
1583         XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1584         break;
1586       case TOK_XMLLIST:
1587         xml = js_NewXML(cx, JSXML_CLASS_LIST);
1588         if (!xml)
1589             goto fail;
1591         n = pn->pn_count;
1592         if (!XMLArraySetCapacity(cx, &xml->xml_kids, n))
1593             goto fail;
1595         i = 0;
1596         for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
1597             /*
1598              * Always ignore insignificant whitespace in lists -- we shouldn't
1599              * condition this on an XML.ignoreWhitespace setting when the list
1600              * constructor is XMLList (note XML/XMLList unification hazard).
1601              */
1602             if (pn2->pn_type == TOK_XMLSPACE) {
1603                 --n;
1604                 continue;
1605             }
1607             kid = ParseNodeToXML(cx, pn2, inScopeNSes, flags);
1608             if (kid == PN2X_SKIP_CHILD) {
1609                 --n;
1610                 continue;
1611             }
1613             if (!kid)
1614                 goto fail;
1616             XMLARRAY_SET_MEMBER(&xml->xml_kids, i, kid);
1617             ++i;
1618         }
1620         if (n < pn->pn_count)
1621             XMLArrayTrim(&xml->xml_kids);
1622         break;
1624       case TOK_XMLSTAGO:
1625       case TOK_XMLPTAGC:
1626         length = inScopeNSes->length;
1627         pn2 = pn->pn_head;
1628         JS_ASSERT(pn2->pn_type == TOK_XMLNAME);
1629         if (pn2->pn_arity == PN_LIST)
1630             goto syntax;
1632         xml = js_NewXML(cx, JSXML_CLASS_ELEMENT);
1633         if (!xml)
1634             goto fail;
1636         /* First pass: check syntax and process namespace declarations. */
1637         JS_ASSERT(pn->pn_count >= 1);
1638         n = pn->pn_count - 1;
1639         pnp = &pn2->pn_next;
1640         head = *pnp;
1641         while ((pn2 = *pnp) != NULL) {
1642             size_t length;
1643             const jschar *chars;
1645             if (pn2->pn_type != TOK_XMLNAME || pn2->pn_arity != PN_NULLARY)
1646                 goto syntax;
1648             /* Enforce "Well-formedness constraint: Unique Att Spec". */
1649             for (pn3 = head; pn3 != pn2; pn3 = pn3->pn_next->pn_next) {
1650                 if (pn3->pn_atom == pn2->pn_atom) {
1651                     js_ReportCompileErrorNumber(cx, pn2,
1652                                                 JSREPORT_PN | JSREPORT_ERROR,
1653                                                 JSMSG_DUPLICATE_XML_ATTR,
1654                                                 js_ValueToPrintableString(cx,
1655                                                     ATOM_KEY(pn2->pn_atom)));
1656                     goto fail;
1657                 }
1658             }
1660             str = ATOM_TO_STRING(pn2->pn_atom);
1661             pn2 = pn2->pn_next;
1662             JS_ASSERT(pn2);
1663             if (pn2->pn_type != TOK_XMLATTR)
1664                 goto syntax;
1666             length = JSSTRING_LENGTH(str);
1667             chars = JSSTRING_CHARS(str);
1668             if (length >= 5 &&
1669                 IS_XMLNS_CHARS(chars) &&
1670                 (length == 5 || chars[5] == ':')) {
1671                 JSString *uri, *prefix;
1673                 uri = ATOM_TO_STRING(pn2->pn_atom);
1674                 if (length == 5) {
1675                     /* 10.3.2.1. Step 6(h)(i)(1)(a). */
1676                     prefix = cx->runtime->emptyString;
1677                 } else {
1678                     prefix = js_NewStringCopyN(cx, chars + 6, length - 6, 0);
1679                     if (!prefix)
1680                         goto fail;
1681                 }
1683                 /*
1684                  * Once the new ns is appended to xml->xml_namespaces, it is
1685                  * protected from GC by the object that owns xml -- which is
1686                  * either xml->object if outermost, or the object owning xml's
1687                  * oldest ancestor if !outermost.
1688                  */
1689                 ns = js_NewXMLNamespace(cx, prefix, uri, JS_TRUE);
1690                 if (!ns)
1691                     goto fail;
1693                 /*
1694                  * Don't add a namespace that's already in scope.  If someone
1695                  * extracts a child property from its parent via [[Get]], then
1696                  * we enforce the invariant, noted many times in ECMA-357, that
1697                  * the child's namespaces form a possibly-improper superset of
1698                  * its ancestors' namespaces.
1699                  */
1700                 if (!XMLARRAY_HAS_MEMBER(inScopeNSes, ns, namespace_identity)) {
1701                     if (!XMLARRAY_APPEND(cx, inScopeNSes, ns) ||
1702                         !XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns)) {
1703                         goto fail;
1704                     }
1705                 }
1707                 JS_ASSERT(n >= 2);
1708                 n -= 2;
1709                 *pnp = pn2->pn_next;
1710                 /* XXXbe recycle pn2 */
1711                 continue;
1712             }
1714             pnp = &pn2->pn_next;
1715         }
1717         /*
1718          * If called from js_ParseNodeToXMLObject, emulate the effect of the
1719          * <parent xmlns='%s'>...</parent> wrapping done by "ToXML Applied to
1720          * the String Type" (ECMA-357 10.3.1).
1721          */
1722         if (flags & XSF_PRECOMPILED_ROOT) {
1723             JS_ASSERT(length >= 1);
1724             ns = XMLARRAY_MEMBER(inScopeNSes, 0, JSXMLNamespace);
1725             JS_ASSERT(!XMLARRAY_HAS_MEMBER(&xml->xml_namespaces, ns,
1726                                            namespace_identity));
1727             ns = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_FALSE);
1728             if (!ns)
1729                 goto fail;
1730             if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
1731                 goto fail;
1732         }
1733         XMLArrayTrim(&xml->xml_namespaces);
1735         /* Second pass: process tag name and attributes, using namespaces. */
1736         pn2 = pn->pn_head;
1737         qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_FALSE);
1738         if (!qn)
1739             goto fail;
1740         xml->name = qn;
1742         JS_ASSERT((n & 1) == 0);
1743         n >>= 1;
1744         if (!XMLArraySetCapacity(cx, &xml->xml_attrs, n))
1745             goto fail;
1747         for (i = 0; (pn2 = pn2->pn_next) != NULL; i++) {
1748             qn = ParseNodeToQName(cx, pn2, inScopeNSes, JS_TRUE);
1749             if (!qn) {
1750                 xml->xml_attrs.length = i;
1751                 goto fail;
1752             }
1754             /*
1755              * Enforce "Well-formedness constraint: Unique Att Spec", part 2:
1756              * this time checking local name and namespace URI.
1757              */
1758             for (j = 0; j < i; j++) {
1759                 attrj = XMLARRAY_MEMBER(&xml->xml_attrs, j, JSXML);
1760                 attrjqn = attrj->name;
1761                 if (!js_CompareStrings(attrjqn->uri, qn->uri) &&
1762                     !js_CompareStrings(attrjqn->localName, qn->localName)) {
1763                     js_ReportCompileErrorNumber(cx, pn2,
1764                                                 JSREPORT_PN | JSREPORT_ERROR,
1765                                                 JSMSG_DUPLICATE_XML_ATTR,
1766                                                 js_ValueToPrintableString(cx,
1767                                                     ATOM_KEY(pn2->pn_atom)));
1768                     goto fail;
1769                 }
1770             }
1772             pn2 = pn2->pn_next;
1773             JS_ASSERT(pn2);
1774             JS_ASSERT(pn2->pn_type == TOK_XMLATTR);
1776             attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
1777             if (!attr)
1778                 goto fail;
1780             XMLARRAY_SET_MEMBER(&xml->xml_attrs, i, attr);
1781             attr->parent = xml;
1782             attr->name = qn;
1783             attr->xml_value = ATOM_TO_STRING(pn2->pn_atom);
1784         }
1786         /* Point tag closes its own namespace scope. */
1787         if (pn->pn_type == TOK_XMLPTAGC)
1788             XMLARRAY_TRUNCATE(cx, inScopeNSes, length);
1789         break;
1791       case TOK_XMLSPACE:
1792       case TOK_XMLTEXT:
1793       case TOK_XMLCDATA:
1794       case TOK_XMLCOMMENT:
1795       case TOK_XMLPI:
1796         str = ATOM_TO_STRING(pn->pn_atom);
1797         qn = NULL;
1798         if (pn->pn_type == TOK_XMLCOMMENT) {
1799             if (flags & XSF_IGNORE_COMMENTS)
1800                 goto skip_child;
1801             xml_class = JSXML_CLASS_COMMENT;
1802         } else if (pn->pn_type == TOK_XMLPI) {
1803             if (IS_XML(str)) {
1804                 js_ReportCompileErrorNumber(cx, pn,
1805                                             JSREPORT_PN | JSREPORT_ERROR,
1806                                             JSMSG_RESERVED_ID,
1807                                             js_ValueToPrintableString(cx,
1808                                                 STRING_TO_JSVAL(str)));
1809                 goto fail;
1810             }
1812             if (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS)
1813                 goto skip_child;
1815             qn = ParseNodeToQName(cx, pn, inScopeNSes, JS_FALSE);
1816             if (!qn)
1817                 goto fail;
1819             str = pn->pn_atom2
1820                   ? ATOM_TO_STRING(pn->pn_atom2)
1821                   : cx->runtime->emptyString;
1822             xml_class = JSXML_CLASS_PROCESSING_INSTRUCTION;
1823         } else {
1824             /* CDATA section content, or element text. */
1825             xml_class = JSXML_CLASS_TEXT;
1826         }
1828         xml = js_NewXML(cx, xml_class);
1829         if (!xml)
1830             goto fail;
1831         xml->name = qn;
1832         if (pn->pn_type == TOK_XMLSPACE)
1833             xml->xml_flags |= XMLF_WHITESPACE_TEXT;
1834         xml->xml_value = str;
1835         break;
1837       default:
1838         goto syntax;
1839     }
1841     JS_LeaveLocalRootScope(cx);
1842     if ((flags & XSF_PRECOMPILED_ROOT) && !js_GetXMLObject(cx, xml))
1843         return NULL;
1844     return xml;
1846 skip_child:
1847     js_LeaveLocalRootScope(cx);
1848     return PN2X_SKIP_CHILD;
1850 #undef PN2X_SKIP_CHILD
1852 syntax:
1853     js_ReportCompileErrorNumber(cx, pn, JSREPORT_PN | JSREPORT_ERROR,
1854                                 JSMSG_BAD_XML_MARKUP);
1855 fail:
1856     JS_LeaveLocalRootScope(cx);
1857     return NULL;
1860 /*
1861  * XML helper, object-ops, and library functions.  We start with the helpers,
1862  * in ECMA-357 order, but merging XML (9.1) and XMLList (9.2) helpers.
1863  */
1864 static JSBool
1865 GetXMLSetting(JSContext *cx, const char *name, jsval *vp)
1867     jsval v;
1869     if (!js_FindConstructor(cx, NULL, js_XML_str, &v))
1870         return JS_FALSE;
1871     if (!JSVAL_IS_FUNCTION(cx, v)) {
1872         *vp = JSVAL_VOID;
1873         return JS_TRUE;
1874     }
1875     return JS_GetProperty(cx, JSVAL_TO_OBJECT(v), name, vp);
1878 static JSBool
1879 FillSettingsCache(JSContext *cx)
1881     int i;
1882     const char *name;
1883     jsval v;
1884     JSBool isSet;
1886     /* Note: XML_PRETTY_INDENT is not a boolean setting. */
1887     for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
1888         name = xml_static_props[i].name;
1889         if (!GetXMLSetting(cx, name, &v) || !js_ValueToBoolean(cx, v, &isSet))
1890             return JS_FALSE;
1891         if (isSet)
1892             cx->xmlSettingFlags |= JS_BIT(i);
1893         else
1894             cx->xmlSettingFlags &= ~JS_BIT(i);
1895     }
1897     cx->xmlSettingFlags |= XSF_CACHE_VALID;
1898     return JS_TRUE;
1901 static JSBool
1902 GetBooleanXMLSetting(JSContext *cx, const char *name, JSBool *bp)
1904     int i;
1906     if (!(cx->xmlSettingFlags & XSF_CACHE_VALID) && !FillSettingsCache(cx))
1907         return JS_FALSE;
1909     for (i = 0; xml_static_props[i].name; i++) {
1910         if (!strcmp(xml_static_props[i].name, name)) {
1911             *bp = (cx->xmlSettingFlags & JS_BIT(i)) != 0;
1912             return JS_TRUE;
1913         }
1914     }
1915     *bp = JS_FALSE;
1916     return JS_TRUE;
1919 static JSBool
1920 GetUint32XMLSetting(JSContext *cx, const char *name, uint32 *uip)
1922     jsval v;
1924     return GetXMLSetting(cx, name, &v) && js_ValueToECMAUint32(cx, v, uip);
1927 static JSBool
1928 GetXMLSettingFlags(JSContext *cx, uintN *flagsp)
1930     JSBool flag;
1932     /* Just get the first flag to validate the setting flags cache. */
1933     if (!GetBooleanXMLSetting(cx, js_ignoreComments_str, &flag))
1934         return JS_FALSE;
1935     *flagsp = cx->xmlSettingFlags;
1936     return JS_TRUE;
1939 static JSXML *
1940 ParseXMLSource(JSContext *cx, JSString *src)
1942     jsval nsval;
1943     JSXMLNamespace *ns;
1944     size_t urilen, srclen, length, offset, dstlen;
1945     jschar *chars;
1946     const jschar *srcp, *endp;
1947     void *mark;
1948     JSTokenStream *ts;
1949     uintN lineno;
1950     JSStackFrame *fp;
1951     JSOp op;
1952     JSParseNode *pn;
1953     JSXML *xml;
1954     JSXMLArray nsarray;
1955     uintN flags;
1957     static const char prefix[] = "<parent xmlns='";
1958     static const char middle[] = "'>";
1959     static const char suffix[] = "</parent>";
1961 #define constrlen(constr)   (sizeof(constr) - 1)
1963     if (!js_GetDefaultXMLNamespace(cx, &nsval))
1964         return NULL;
1965     ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
1967     urilen = JSSTRING_LENGTH(ns->uri);
1968     srclen = JSSTRING_LENGTH(src);
1969     length = constrlen(prefix) + urilen + constrlen(middle) + srclen +
1970              constrlen(suffix);
1972     chars = (jschar *) JS_malloc(cx, (length + 1) * sizeof(jschar));
1973     if (!chars)
1974         return NULL;
1976     dstlen = length;
1977     js_InflateStringToBuffer(cx, prefix, constrlen(prefix), chars, &dstlen);
1978     offset = dstlen;
1979     js_strncpy(chars + offset, JSSTRING_CHARS(ns->uri), urilen);
1980     offset += urilen;
1981     dstlen = length - offset + 1;
1982     js_InflateStringToBuffer(cx, middle, constrlen(middle), chars + offset, &dstlen);
1983     offset += dstlen;
1984     srcp = JSSTRING_CHARS(src);
1985     js_strncpy(chars + offset, srcp, srclen);
1986     offset += srclen;
1987     dstlen = length - offset + 1;
1988     js_InflateStringToBuffer(cx, suffix, constrlen(suffix), chars + offset, &dstlen);
1989     chars [offset + dstlen] = 0;
1991     mark = JS_ARENA_MARK(&cx->tempPool);
1992     ts = js_NewBufferTokenStream(cx, chars, length);
1993     if (!ts)
1994         return NULL;
1995     for (fp = cx->fp; fp && !fp->pc; fp = fp->down)
1996         continue;
1997     if (fp) {
1998         op = (JSOp) *fp->pc;
1999         if (op == JSOP_TOXML || op == JSOP_TOXMLLIST) {
2000             ts->filename = fp->script->filename;
2001             lineno = js_PCToLineNumber(cx, fp->script, fp->pc);
2002             for (endp = srcp + srclen; srcp < endp; srcp++)
2003                 if (*srcp == '\n')
2004                     --lineno;
2005             ts->lineno = lineno;
2006         }
2007     }
2009     JS_KEEP_ATOMS(cx->runtime);
2010     pn = js_ParseXMLTokenStream(cx, cx->fp->scopeChain, ts, JS_FALSE);
2011     xml = NULL;
2012     if (pn && XMLArrayInit(cx, &nsarray, 1)) {
2013         if (GetXMLSettingFlags(cx, &flags))
2014             xml = ParseNodeToXML(cx, pn, &nsarray, flags);
2016         XMLArrayFinish(cx, &nsarray);
2017     }
2018     JS_UNKEEP_ATOMS(cx->runtime);
2020     JS_ARENA_RELEASE(&cx->tempPool, mark);
2021     JS_free(cx, chars);
2022     return xml;
2024 #undef constrlen
2027 /*
2028  * Errata in 10.3.1, 10.4.1, and 13.4.4.24 (at least).
2029  *
2030  * 10.3.1 Step 6(a) fails to NOTE that implementations that do not enforce
2031  * the constraint:
2032  *
2033  *     for all x belonging to XML:
2034  *         x.[[InScopeNamespaces]] >= x.[[Parent]].[[InScopeNamespaces]]
2035  *
2036  * must union x.[[InScopeNamespaces]] into x[0].[[InScopeNamespaces]] here
2037  * (in new sub-step 6(a), renumbering the others to (b) and (c)).
2038  *
2039  * Same goes for 10.4.1 Step 7(a).
2040  *
2041  * In order for XML.prototype.namespaceDeclarations() to work correctly, the
2042  * default namespace thereby unioned into x[0].[[InScopeNamespaces]] must be
2043  * flagged as not declared, so that 13.4.4.24 Step 8(a) can exclude all such
2044  * undeclared namespaces associated with x not belonging to ancestorNS.
2045  */
2046 static JSXML *
2047 OrphanXMLChild(JSContext *cx, JSXML *xml, uint32 i)
2049     JSXMLNamespace *ns;
2051     ns = XMLARRAY_MEMBER(&xml->xml_namespaces, 0, JSXMLNamespace);
2052     xml = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
2053     if (!ns || !xml)
2054         return xml;
2055     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
2056         if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
2057             return NULL;
2058         ns->declared = JS_FALSE;
2059     }
2060     xml->parent = NULL;
2061     return xml;
2064 static JSObject *
2065 ToXML(JSContext *cx, jsval v)
2067     JSObject *obj;
2068     JSXML *xml;
2069     JSClass *clasp;
2070     JSString *str;
2071     uint32 length;
2073     if (JSVAL_IS_PRIMITIVE(v)) {
2074         if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2075             goto bad;
2076     } else {
2077         obj = JSVAL_TO_OBJECT(v);
2078         if (OBJECT_IS_XML(cx, obj)) {
2079             xml = (JSXML *) JS_GetPrivate(cx, obj);
2080             if (xml->xml_class == JSXML_CLASS_LIST) {
2081                 if (xml->xml_kids.length != 1)
2082                     goto bad;
2083                 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
2084                 if (xml) {
2085                     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
2086                     return js_GetXMLObject(cx, xml);
2087                 }
2088             }
2089             return obj;
2090         }
2092         clasp = OBJ_GET_CLASS(cx, obj);
2093         if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2094             JS_ASSERT(0);
2095         }
2097         if (clasp != &js_StringClass &&
2098             clasp != &js_NumberClass &&
2099             clasp != &js_BooleanClass) {
2100             goto bad;
2101         }
2102     }
2104     str = js_ValueToString(cx, v);
2105     if (!str)
2106         return NULL;
2107     if (IS_EMPTY(str)) {
2108         length = 0;
2109 #ifdef __GNUC__         /* suppress bogus gcc warnings */
2110         xml = NULL;
2111 #endif
2112     } else {
2113         xml = ParseXMLSource(cx, str);
2114         if (!xml)
2115             return NULL;
2116         length = JSXML_LENGTH(xml);
2117     }
2119     if (length == 0) {
2120         obj = js_NewXMLObject(cx, JSXML_CLASS_TEXT);
2121         if (!obj)
2122             return NULL;
2123     } else if (length == 1) {
2124         xml = OrphanXMLChild(cx, xml, 0);
2125         if (!xml)
2126             return NULL;
2127         obj = js_GetXMLObject(cx, xml);
2128         if (!obj)
2129             return NULL;
2130     } else {
2131         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SYNTAX_ERROR);
2132         return NULL;
2133     }
2134     return obj;
2136 bad:
2137     str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
2138     if (str) {
2139         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2140                              JSMSG_BAD_XML_CONVERSION,
2141                              JS_GetStringBytes(str));
2142     }
2143     return NULL;
2146 static JSBool
2147 Append(JSContext *cx, JSXML *list, JSXML *kid);
2149 static JSObject *
2150 ToXMLList(JSContext *cx, jsval v)
2152     JSObject *obj, *listobj;
2153     JSXML *xml, *list, *kid;
2154     JSClass *clasp;
2155     JSString *str;
2156     uint32 i, length;
2158     if (JSVAL_IS_PRIMITIVE(v)) {
2159         if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
2160             goto bad;
2161     } else {
2162         obj = JSVAL_TO_OBJECT(v);
2163         if (OBJECT_IS_XML(cx, obj)) {
2164             xml = (JSXML *) JS_GetPrivate(cx, obj);
2165             if (xml->xml_class != JSXML_CLASS_LIST) {
2166                 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2167                 if (!listobj)
2168                     return NULL;
2169                 list = (JSXML *) JS_GetPrivate(cx, listobj);
2170                 if (!Append(cx, list, xml))
2171                     return NULL;
2172                 return listobj;
2173             }
2174             return obj;
2175         }
2177         clasp = OBJ_GET_CLASS(cx, obj);
2178         if (clasp->flags & JSCLASS_DOCUMENT_OBSERVER) {
2179             JS_ASSERT(0);
2180         }
2182         if (clasp != &js_StringClass &&
2183             clasp != &js_NumberClass &&
2184             clasp != &js_BooleanClass) {
2185             goto bad;
2186         }
2187     }
2189     str = js_ValueToString(cx, v);
2190     if (!str)
2191         return NULL;
2192     if (IS_EMPTY(str)) {
2193         xml = NULL;
2194         length = 0;
2195     } else {
2196         if (!JS_EnterLocalRootScope(cx))
2197             return NULL;
2198         xml = ParseXMLSource(cx, str);
2199         if (!xml) {
2200             JS_LeaveLocalRootScope(cx);
2201             return NULL;
2202         }
2203         length = JSXML_LENGTH(xml);
2204     }
2206     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
2207     if (listobj) {
2208         list = (JSXML *) JS_GetPrivate(cx, listobj);
2209         for (i = 0; i < length; i++) {
2210             kid = OrphanXMLChild(cx, xml, i);
2211             if (!kid)
2212                 return NULL;
2213             if (!Append(cx, list, kid)) {
2214                 listobj = NULL;
2215                 break;
2216             }
2217         }
2218     }
2220     if (xml)
2221         JS_LeaveLocalRootScope(cx);
2222     return listobj;
2224 bad:
2225     str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
2226     if (str) {
2227         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2228                              JSMSG_BAD_XMLLIST_CONVERSION,
2229                              JS_GetStringBytes(str));
2230     }
2231     return NULL;
2234 /*
2235  * ECMA-357 10.2.1 Steps 5-7 pulled out as common subroutines of XMLToXMLString
2236  * and their library-public js_* counterparts.  The guts of MakeXMLCDataString,
2237  * MakeXMLCommentString, and MakeXMLPIString are further factored into a common
2238  * MakeXMLSpecialString subroutine.
2239  *
2240  * These functions take ownership of sb->base, if sb is non-null, in all cases
2241  * of success or failure.
2242  */
2243 static JSString *
2244 MakeXMLSpecialString(JSContext *cx, JSStringBuffer *sb,
2245                      JSString *str, JSString *str2,
2246                      const jschar *prefix, size_t prefixlength,
2247                      const jschar *suffix, size_t suffixlength)
2249     JSStringBuffer localSB;
2250     size_t length, length2, newlength;
2251     jschar *bp, *base;
2253     if (!sb) {
2254         sb = &localSB;
2255         js_InitStringBuffer(sb);
2256     }
2258     length = JSSTRING_LENGTH(str);
2259     length2 = str2 ? JSSTRING_LENGTH(str2) : 0;
2260     newlength = STRING_BUFFER_OFFSET(sb) +
2261                 prefixlength + length + ((length2 != 0) ? 1 + length2 : 0) +
2262                 suffixlength;
2263     bp = base = (jschar *)
2264                 JS_realloc(cx, sb->base, (newlength + 1) * sizeof(jschar));
2265     if (!bp) {
2266         js_FinishStringBuffer(sb);
2267         return NULL;
2268     }
2270     bp += STRING_BUFFER_OFFSET(sb);
2271     js_strncpy(bp, prefix, prefixlength);
2272     bp += prefixlength;
2273     js_strncpy(bp, JSSTRING_CHARS(str), length);
2274     bp += length;
2275     if (length2 != 0) {
2276         *bp++ = (jschar) ' ';
2277         js_strncpy(bp, JSSTRING_CHARS(str2), length2);
2278         bp += length2;
2279     }
2280     js_strncpy(bp, suffix, suffixlength);
2281     bp[suffixlength] = 0;
2283     str = js_NewString(cx, base, newlength, 0);
2284     if (!str)
2285         free(base);
2286     return str;
2289 static JSString *
2290 MakeXMLCDATAString(JSContext *cx, JSStringBuffer *sb, JSString *str)
2292     static const jschar cdata_prefix_ucNstr[] = {'<', '!', '[',
2293                                                  'C', 'D', 'A', 'T', 'A',
2294                                                  '['};
2295     static const jschar cdata_suffix_ucNstr[] = {']', ']', '>'};
2297     return MakeXMLSpecialString(cx, sb, str, NULL,
2298                                 cdata_prefix_ucNstr, 9,
2299                                 cdata_suffix_ucNstr, 3);
2302 static JSString *
2303 MakeXMLCommentString(JSContext *cx, JSStringBuffer *sb, JSString *str)
2305     static const jschar comment_prefix_ucNstr[] = {'<', '!', '-', '-'};
2306     static const jschar comment_suffix_ucNstr[] = {'-', '-', '>'};
2308     return MakeXMLSpecialString(cx, sb, str, NULL,
2309                                 comment_prefix_ucNstr, 4,
2310                                 comment_suffix_ucNstr, 3);
2313 static JSString *
2314 MakeXMLPIString(JSContext *cx, JSStringBuffer *sb, JSString *name,
2315                 JSString *value)
2317     static const jschar pi_prefix_ucNstr[] = {'<', '?'};
2318     static const jschar pi_suffix_ucNstr[] = {'?', '>'};
2320     return MakeXMLSpecialString(cx, sb, name, value,
2321                                 pi_prefix_ucNstr, 2,
2322                                 pi_suffix_ucNstr, 2);
2325 /*
2326  * ECMA-357 10.2.1 17(d-g) pulled out into a common subroutine that appends
2327  * equals, a double quote, an attribute value, and a closing double quote.
2328  */
2329 static void
2330 AppendAttributeValue(JSContext *cx, JSStringBuffer *sb, JSString *valstr)
2332     js_AppendCString(sb, "=\"");
2333     valstr = js_EscapeAttributeValue(cx, valstr);
2334     if (!valstr) {
2335         free(sb->base);
2336         sb->base = STRING_BUFFER_ERROR_BASE;
2337         return;
2338     }
2339     js_AppendJSString(sb, valstr);
2340     js_AppendChar(sb, '"');
2343 /*
2344  * ECMA-357 10.2.1.1 EscapeElementValue helper method.
2345  *
2346  * This function takes ownership of sb->base, if sb is non-null, in all cases
2347  * of success or failure.
2348  */
2349 static JSString *
2350 EscapeElementValue(JSContext *cx, JSStringBuffer *sb, JSString *str)
2352     size_t length, newlength;
2353     const jschar *cp, *start, *end;
2354     jschar c;
2356     length = newlength = JSSTRING_LENGTH(str);
2357     for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
2358         c = *cp;
2359         if (c == '<' || c == '>')
2360             newlength += 3;
2361         else if (c == '&')
2362             newlength += 4;
2364         if (newlength < length) {
2365             JS_ReportOutOfMemory(cx);
2366             return NULL;
2367         }
2368     }
2369     if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
2370         JSStringBuffer localSB;
2371         if (!sb) {
2372             sb = &localSB;
2373             js_InitStringBuffer(sb);
2374         }
2375         if (!sb->grow(sb, newlength)) {
2376             JS_ReportOutOfMemory(cx);
2377             return NULL;
2378         }
2379         for (cp = start; cp < end; cp++) {
2380             c = *cp;
2381             if (c == '<')
2382                 js_AppendCString(sb, js_lt_entity_str);
2383             else if (c == '>')
2384                 js_AppendCString(sb, js_gt_entity_str);
2385             else if (c == '&')
2386                 js_AppendCString(sb, js_amp_entity_str);
2387             else
2388                 js_AppendChar(sb, c);
2389         }
2390         JS_ASSERT(STRING_BUFFER_OK(sb));
2391         str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
2392         if (!str)
2393             js_FinishStringBuffer(sb);
2394     }
2395     return str;
2398 /*
2399  * ECMA-357 10.2.1.2 EscapeAttributeValue helper method.
2400  * This function takes ownership of sb->base, if sb is non-null, in all cases.
2401  */
2402 static JSString *
2403 EscapeAttributeValue(JSContext *cx, JSStringBuffer *sb, JSString *str)
2405     size_t length, newlength;
2406     const jschar *cp, *start, *end;
2407     jschar c;
2409     length = newlength = JSSTRING_LENGTH(str);
2410     for (cp = start = JSSTRING_CHARS(str), end = cp + length; cp < end; cp++) {
2411         c = *cp;
2412         if (c == '"')
2413             newlength += 5;
2414         else if (c == '<')
2415             newlength += 3;
2416         else if (c == '&' || c == '\n' || c == '\r' || c == '\t')
2417             newlength += 4;
2419         if (newlength < length) {
2420             JS_ReportOutOfMemory(cx);
2421             return NULL;
2422         }
2423     }
2424     if ((sb && STRING_BUFFER_OFFSET(sb) != 0) || newlength > length) {
2425         JSStringBuffer localSB;
2426         if (!sb) {
2427             sb = &localSB;
2428             js_InitStringBuffer(sb);
2429         }
2430         if (!sb->grow(sb, newlength)) {
2431             JS_ReportOutOfMemory(cx);
2432             return NULL;
2433         }
2434         for (cp = start; cp < end; cp++) {
2435             c = *cp;
2436             if (c == '"')
2437                 js_AppendCString(sb, js_quot_entity_str);
2438             else if (c == '<')
2439                 js_AppendCString(sb, js_lt_entity_str);
2440             else if (c == '&')
2441                 js_AppendCString(sb, js_amp_entity_str);
2442             else if (c == '\n')
2443                 js_AppendCString(sb, "&#xA;");
2444             else if (c == '\r')
2445                 js_AppendCString(sb, "&#xD;");
2446             else if (c == '\t')
2447                 js_AppendCString(sb, "&#x9;");
2448             else
2449                 js_AppendChar(sb, c);
2450         }
2451         JS_ASSERT(STRING_BUFFER_OK(sb));
2452         str = js_NewString(cx, sb->base, STRING_BUFFER_OFFSET(sb), 0);
2453         if (!str)
2454             js_FinishStringBuffer(sb);
2455     }
2456     return str;
2459 /* 13.3.5.4 [[GetNamespace]]([InScopeNamespaces]) */
2460 static JSXMLNamespace *
2461 GetNamespace(JSContext *cx, JSXMLQName *qn, const JSXMLArray *inScopeNSes)
2463     JSXMLNamespace *match, *ns;
2464     uint32 i, n;
2465     jsval argv[2];
2466     JSObject *nsobj;
2468     JS_ASSERT(qn->uri);
2469     if (!qn->uri) {
2470         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2471                              JSMSG_BAD_XML_NAMESPACE,
2472                              qn->prefix
2473                              ? js_ValueToPrintableString(cx,
2474                                    STRING_TO_JSVAL(qn->prefix))
2475                              : js_type_str[JSTYPE_VOID]);
2476         return NULL;
2477     }
2479     /* Look for a matching namespace in inScopeNSes, if provided. */
2480     match = NULL;
2481     if (inScopeNSes) {
2482         for (i = 0, n = inScopeNSes->length; i < n; i++) {
2483             ns = XMLARRAY_MEMBER(inScopeNSes, i, JSXMLNamespace);
2484             if (!ns)
2485                 continue;
2487             /*
2488              * Erratum, very tricky, and not specified in ECMA-357 13.3.5.4:
2489              * If we preserve prefixes, we must match null qn->prefix against
2490              * an empty ns->prefix, in order to avoid generating redundant
2491              * prefixed and default namespaces for cases such as:
2492              *
2493              *   x = <t xmlns="http://foo.com"/>
2494              *   print(x.toXMLString());
2495              *
2496              * Per 10.3.2.1, the namespace attribute in t has an empty string
2497              * prefix (*not* a null prefix), per 10.3.2.1 Step 6(h)(i)(1):
2498              *
2499              *   1. If the [local name] property of a is "xmlns"
2500              *      a. Map ns.prefix to the empty string
2501              *
2502              * But t's name has a null prefix in this implementation, meaning
2503              * *undefined*, per 10.3.2.1 Step 6(c)'s NOTE (which refers to
2504              * the http://www.w3.org/TR/xml-infoset/ spec, item 2.2.3, without
2505              * saying how "no value" maps to an ECMA-357 value -- but it must
2506              * map to the *undefined* prefix value).
2507              *
2508              * Since "" != undefined (or null, in the current implementation)
2509              * the ECMA-357 spec will fail to match in [[GetNamespace]] called
2510              * on t with argument {} U {(prefix="", uri="http://foo.com")}.
2511              * This spec bug leads to ToXMLString results that duplicate the
2512              * declared namespace.
2513              */
2514             if (!js_CompareStrings(ns->uri, qn->uri) &&
2515                 (ns->prefix == qn->prefix ||
2516                  ((ns->prefix && qn->prefix)
2517                   ? !js_CompareStrings(ns->prefix, qn->prefix)
2518                   : IS_EMPTY(ns->prefix ? ns->prefix : qn->prefix)))) {
2519                 match = ns;
2520                 break;
2521             }
2522         }
2523     }
2525     /* If we didn't match, make a new namespace from qn. */
2526     if (!match) {
2527         argv[0] = qn->prefix ? STRING_TO_JSVAL(qn->prefix) : JSVAL_VOID;
2528         argv[1] = STRING_TO_JSVAL(qn->uri);
2529         nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
2530                                    2, argv);
2531         if (!nsobj)
2532             return NULL;
2533         match = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
2534     }
2535     return match;
2538 static JSString *
2539 GeneratePrefix(JSContext *cx, JSString *uri, JSXMLArray *decls)
2541     const jschar *cp, *start, *end;
2542     size_t length, newlength, offset;
2543     uint32 i, n, m, serial;
2544     jschar *bp, *dp;
2545     JSBool done;
2546     JSXMLNamespace *ns;
2547     JSString *prefix;
2549     JS_ASSERT(!IS_EMPTY(uri));
2551     /*
2552      * Try peeling off the last filename suffix or pathname component till
2553      * we have a valid XML name.  This heuristic will prefer "xul" given
2554      * ".../there.is.only.xul", "xbl" given ".../xbl", and "xbl2" given any
2555      * likely URI of the form ".../xbl2/2005".
2556      */
2557     start = JSSTRING_CHARS(uri);
2558     cp = end = start + JSSTRING_LENGTH(uri);
2559     while (--cp > start) {
2560         if (*cp == '.' || *cp == '/' || *cp == ':') {
2561             ++cp;
2562             if (IsXMLName(cp, PTRDIFF(end, cp, jschar)))
2563                 break;
2564             end = --cp;
2565         }
2566     }
2567     length = PTRDIFF(end, cp, jschar);
2569     /*
2570      * Now search through decls looking for a collision.  If we collide with
2571      * an existing prefix, start tacking on a hyphen and a serial number.
2572      */
2573     serial = 0;
2574     bp = NULL;
2575 #ifdef __GNUC__         /* suppress bogus gcc warnings */
2576     newlength = 0;
2577 #endif
2578     do {
2579         done = JS_TRUE;
2580         for (i = 0, n = decls->length; i < n; i++) {
2581             ns = XMLARRAY_MEMBER(decls, i, JSXMLNamespace);
2582             if (ns && ns->prefix &&
2583                 JSSTRING_LENGTH(ns->prefix) == length &&
2584                 !memcmp(JSSTRING_CHARS(ns->prefix), cp,
2585                         length * sizeof(jschar))) {
2586                 if (!bp) {
2587                     newlength = length + 2 + (size_t) log10(n);
2588                     bp = (jschar *)
2589                          JS_malloc(cx, (newlength + 1) * sizeof(jschar));
2590                     if (!bp)
2591                         return NULL;
2592                     js_strncpy(bp, cp, length);
2593                 }
2595                 ++serial;
2596                 JS_ASSERT(serial <= n);
2597                 dp = bp + length + 2 + (size_t) log10(serial);
2598                 *dp = 0;
2599                 for (m = serial; m != 0; m /= 10)
2600                     *--dp = (jschar)('0' + m % 10);
2601                 *--dp = '-';
2602                 JS_ASSERT(dp == bp + length);
2604                 done = JS_FALSE;
2605                 break;
2606             }
2607         }
2608     } while (!done);
2610     if (!bp) {
2611         offset = PTRDIFF(cp, start, jschar);
2612         prefix = js_NewDependentString(cx, uri, offset, length, 0);
2613     } else {
2614         prefix = js_NewString(cx, bp, newlength, 0);
2615         if (!prefix)
2616             JS_free(cx, bp);
2617     }
2618     return prefix;
2621 static JSBool
2622 namespace_match(const void *a, const void *b)
2624     const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
2625     const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
2627     if (nsb->prefix)
2628         return nsa->prefix && !js_CompareStrings(nsa->prefix, nsb->prefix);
2629     return !js_CompareStrings(nsa->uri, nsb->uri);
2632 /* ECMA-357 10.2.1 and 10.2.2 */
2633 static JSString *
2634 XMLToXMLString(JSContext *cx, JSXML *xml, const JSXMLArray *ancestorNSes,
2635                uintN indentLevel)
2637     JSBool pretty, indentKids;
2638     JSStringBuffer sb;
2639     JSString *str, *prefix, *kidstr;
2640     JSXMLArrayCursor cursor;
2641     uint32 i, n;
2642     JSXMLArray empty, decls, ancdecls;
2643     JSXMLNamespace *ns, *ns2;
2644     uintN nextIndentLevel;
2645     JSXML *attr, *kid;
2647     if (!GetBooleanXMLSetting(cx, js_prettyPrinting_str, &pretty))
2648         return NULL;
2650     js_InitStringBuffer(&sb);
2651     if (pretty)
2652         js_RepeatChar(&sb, ' ', indentLevel);
2653     str = NULL;
2655     switch (xml->xml_class) {
2656       case JSXML_CLASS_TEXT:
2657         /* Step 4. */
2658         if (pretty) {
2659             str = ChompXMLWhitespace(cx, xml->xml_value);
2660             if (!str)
2661                 return NULL;
2662         } else {
2663             str = xml->xml_value;
2664         }
2665         return EscapeElementValue(cx, &sb, str);
2667       case JSXML_CLASS_ATTRIBUTE:
2668         /* Step 5. */
2669         return EscapeAttributeValue(cx, &sb, xml->xml_value);
2671       case JSXML_CLASS_COMMENT:
2672         /* Step 6. */
2673         return MakeXMLCommentString(cx, &sb, xml->xml_value);
2675       case JSXML_CLASS_PROCESSING_INSTRUCTION:
2676         /* Step 7. */
2677         return MakeXMLPIString(cx, &sb, xml->name->localName, xml->xml_value);
2679       case JSXML_CLASS_LIST:
2680         /* ECMA-357 10.2.2. */
2681         XMLArrayCursorInit(&cursor, &xml->xml_kids);
2682         i = 0;
2683         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2684             if (pretty && i != 0)
2685                 js_AppendChar(&sb, '\n');
2687             kidstr = XMLToXMLString(cx, kid, ancestorNSes, indentLevel);
2688             if (!kidstr)
2689                 break;
2691             js_AppendJSString(&sb, kidstr);
2692             ++i;
2693         }
2694         XMLArrayCursorFinish(&cursor);
2695         if (kid)
2696             goto list_out;
2698         if (!sb.base) {
2699             if (!STRING_BUFFER_OK(&sb)) {
2700                 JS_ReportOutOfMemory(cx);
2701                 return NULL;
2702             }
2703             return cx->runtime->emptyString;
2704         }
2706         str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
2707       list_out:
2708         if (!str)
2709             js_FinishStringBuffer(&sb);
2710         return str;
2712       default:;
2713     }
2715     /* After this point, control must flow through label out: to exit. */
2716     if (!JS_EnterLocalRootScope(cx))
2717         return NULL;
2719     /* ECMA-357 10.2.1 step 8 onward: handle ToXMLString on an XML element. */
2720     if (!ancestorNSes) {
2721         XMLArrayInit(cx, &empty, 0);
2722         ancestorNSes = &empty;
2723     }
2724     XMLArrayInit(cx, &decls, 0);
2725     ancdecls.capacity = 0;
2727     /* Clone in-scope namespaces not in ancestorNSes into decls. */
2728     XMLArrayCursorInit(&cursor, &xml->xml_namespaces);
2729     while ((ns = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
2730         if (!ns->declared)
2731             continue;
2732         if (!XMLARRAY_HAS_MEMBER(ancestorNSes, ns, namespace_identity)) {
2733             /* NOTE: may want to exclude unused namespaces here. */
2734             ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, JS_TRUE);
2735             if (!ns2 || !XMLARRAY_APPEND(cx, &decls, ns2))
2736                 break;
2737         }
2738     }
2739     XMLArrayCursorFinish(&cursor);
2740     if (ns)
2741         goto out;
2743     /*
2744      * Union ancestorNSes and decls into ancdecls.  Note that ancdecls does
2745      * not own its member references.  In the spec, ancdecls has no name, but
2746      * is always written out as (AncestorNamespaces U namespaceDeclarations).
2747      */
2748     if (!XMLArrayInit(cx, &ancdecls, ancestorNSes->length + decls.length))
2749         goto out;
2750     for (i = 0, n = ancestorNSes->length; i < n; i++) {
2751         ns2 = XMLARRAY_MEMBER(ancestorNSes, i, JSXMLNamespace);
2752         if (!ns2)
2753             continue;
2754         JS_ASSERT(!XMLARRAY_HAS_MEMBER(&decls, ns2, namespace_identity));
2755         if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2756             goto out;
2757     }
2758     for (i = 0, n = decls.length; i < n; i++) {
2759         ns2 = XMLARRAY_MEMBER(&decls, i, JSXMLNamespace);
2760         if (!ns2)
2761             continue;
2762         JS_ASSERT(!XMLARRAY_HAS_MEMBER(&ancdecls, ns2, namespace_identity));
2763         if (!XMLARRAY_APPEND(cx, &ancdecls, ns2))
2764             goto out;
2765     }
2767     /* Step 11, except we don't clone ns unless its prefix is undefined. */
2768     ns = GetNamespace(cx, xml->name, &ancdecls);
2769     if (!ns)
2770         goto out;
2772     /* Step 12 (NULL means *undefined* here), plus the deferred ns cloning. */
2773     if (!ns->prefix) {
2774         /*
2775          * Create a namespace prefix that isn't used by any member of decls.
2776          * Assign the new prefix to a copy of ns.  Flag this namespace as if
2777          * it were declared, for assertion-testing's sake later below.
2778          *
2779          * Erratum: if ns->prefix and xml->name are both null (*undefined* in
2780          * ECMA-357), we know that xml was named using the default namespace
2781          * (proof: see GetNamespace and the Namespace constructor called with
2782          * two arguments).  So we ought not generate a new prefix here, when
2783          * we can declare ns as the default namespace for xml.
2784          *
2785          * This helps descendants inherit the namespace instead of redundantly
2786          * redeclaring it with generated prefixes in each descendant.
2787          */
2788         if (!xml->name->prefix) {
2789             prefix = cx->runtime->emptyString;
2790         } else {
2791             prefix = GeneratePrefix(cx, ns->uri, &ancdecls);
2792             if (!prefix)
2793                 goto out;
2794         }
2795         ns = js_NewXMLNamespace(cx, prefix, ns->uri, JS_TRUE);
2796         if (!ns)
2797             goto out;
2799         /*
2800          * If the xml->name was unprefixed, we must remove any declared default
2801          * namespace from decls before appending ns.  How can you get a default
2802          * namespace in decls that doesn't match the one from name?  Apparently
2803          * by calling x.setNamespace(ns) where ns has no prefix.  The other way
2804          * to fix this is to update x's in-scope namespaces when setNamespace
2805          * is called, but that's not specified by ECMA-357.
2806          *
2807          * Likely Erratum here, depending on whether the lack of update to x's
2808          * in-scope namespace in XML.prototype.setNamespace (13.4.4.36) is an
2809          * erratum or not.  Note that changing setNamespace to update the list
2810          * of in-scope namespaces will change x.namespaceDeclarations().
2811          */
2812         if (IS_EMPTY(prefix)) {
2813             i = XMLArrayFindMember(&decls, ns, namespace_match);
2814             if (i != XML_NOT_FOUND)
2815                 XMLArrayDelete(cx, &decls, i, JS_TRUE);
2816         }
2818         /*
2819          * In the spec, ancdecls has no name, but is always written out as
2820          * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2821          * that union in ancdecls, any time we append a namespace strong
2822          * ref to decls, we must also append a weak ref to ancdecls.  Order
2823          * matters here: code at label out: releases strong refs in decls.
2824          */
2825         if (!XMLARRAY_APPEND(cx, &ancdecls, ns) ||
2826             !XMLARRAY_APPEND(cx, &decls, ns)) {
2827             goto out;
2828         }
2829     }
2831     /* Format the element or point-tag into sb. */
2832     js_AppendChar(&sb, '<');
2834     if (ns->prefix && !IS_EMPTY(ns->prefix)) {
2835         js_AppendJSString(&sb, ns->prefix);
2836         js_AppendChar(&sb, ':');
2837     }
2838     js_AppendJSString(&sb, xml->name->localName);
2840     /*
2841      * Step 16 makes a union to avoid writing two loops in step 17, to share
2842      * common attribute value appending spec-code.  We prefer two loops for
2843      * faster code and less data overhead.
2844      */
2846     /* Step 17(c): append XML namespace declarations. */
2847     XMLArrayCursorInit(&cursor, &decls);
2848     while ((ns2 = (JSXMLNamespace *) XMLArrayCursorNext(&cursor)) != NULL) {
2849         JS_ASSERT(ns2->declared);
2851         js_AppendCString(&sb, " xmlns");
2853         /* 17(c)(ii): NULL means *undefined* here. */
2854         if (!ns2->prefix) {
2855             prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
2856             if (!prefix)
2857                 break;
2858             ns2->prefix = prefix;
2859         }
2861         /* 17(c)(iii). */
2862         if (!IS_EMPTY(ns2->prefix)) {
2863             js_AppendChar(&sb, ':');
2864             js_AppendJSString(&sb, ns2->prefix);
2865         }
2867         /* 17(d-g). */
2868         AppendAttributeValue(cx, &sb, ns2->uri);
2869     }
2870     XMLArrayCursorFinish(&cursor);
2871     if (ns2)
2872         goto out;
2874     /* Step 17(b): append attributes. */
2875     XMLArrayCursorInit(&cursor, &xml->xml_attrs);
2876     while ((attr = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2877         js_AppendChar(&sb, ' ');
2878         ns2 = GetNamespace(cx, attr->name, &ancdecls);
2879         if (!ns2)
2880             break;
2882         /* 17(b)(ii): NULL means *undefined* here. */
2883         if (!ns2->prefix) {
2884             prefix = GeneratePrefix(cx, ns2->uri, &ancdecls);
2885             if (!prefix)
2886                 break;
2888             /* Again, we avoid copying ns2 until we know it's prefix-less. */
2889             ns2 = js_NewXMLNamespace(cx, prefix, ns2->uri, JS_TRUE);
2890             if (!ns2)
2891                 break;
2893             /*
2894              * In the spec, ancdecls has no name, but is always written out as
2895              * (AncestorNamespaces U namespaceDeclarations).  Since we compute
2896              * that union in ancdecls, any time we append a namespace strong
2897              * ref to decls, we must also append a weak ref to ancdecls.  Order
2898              * matters here: code at label out: releases strong refs in decls.
2899              */
2900             if (!XMLARRAY_APPEND(cx, &ancdecls, ns2) ||
2901                 !XMLARRAY_APPEND(cx, &decls, ns2)) {
2902                 break;
2903             }
2904         }
2906         /* 17(b)(iii). */
2907         if (!IS_EMPTY(ns2->prefix)) {
2908             js_AppendJSString(&sb, ns2->prefix);
2909             js_AppendChar(&sb, ':');
2910         }
2912         /* 17(b)(iv). */
2913         js_AppendJSString(&sb, attr->name->localName);
2915         /* 17(d-g). */
2916         AppendAttributeValue(cx, &sb, attr->xml_value);
2917     }
2918     XMLArrayCursorFinish(&cursor);
2919     if (attr)
2920         goto out;
2922     /* Step 18: handle point tags. */
2923     n = xml->xml_kids.length;
2924     if (n == 0) {
2925         js_AppendCString(&sb, "/>");
2926     } else {
2927         /* Steps 19 through 25: handle element content, and open the end-tag. */
2928         js_AppendChar(&sb, '>');
2929         indentKids = n > 1 ||
2930                      (n == 1 &&
2931                       (kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML)) &&
2932                       kid->xml_class != JSXML_CLASS_TEXT);
2934         if (pretty && indentKids) {
2935             if (!GetUint32XMLSetting(cx, js_prettyIndent_str, &i))
2936                 goto out;
2937             nextIndentLevel = indentLevel + i;
2938         } else {
2939             nextIndentLevel = 0;
2940         }
2942         XMLArrayCursorInit(&cursor, &xml->xml_kids);
2943         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
2944             if (pretty && indentKids)
2945                 js_AppendChar(&sb, '\n');
2947             kidstr = XMLToXMLString(cx, kid, &ancdecls, nextIndentLevel);
2948             if (!kidstr)
2949                 break;
2951             js_AppendJSString(&sb, kidstr);
2952         }
2953         XMLArrayCursorFinish(&cursor);
2954         if (kid)
2955             goto out;
2957         if (pretty && indentKids) {
2958             js_AppendChar(&sb, '\n');
2959             js_RepeatChar(&sb, ' ', indentLevel);
2960         }
2961         js_AppendCString(&sb, "</");
2963         /* Step 26. */
2964         if (ns->prefix && !IS_EMPTY(ns->prefix)) {
2965             js_AppendJSString(&sb, ns->prefix);
2966             js_AppendChar(&sb, ':');
2967         }
2969         /* Step 27. */
2970         js_AppendJSString(&sb, xml->name->localName);
2971         js_AppendChar(&sb, '>');
2972     }
2974     if (!STRING_BUFFER_OK(&sb)) {
2975         JS_ReportOutOfMemory(cx);
2976         goto out;
2977     }
2979     str = js_NewString(cx, sb.base, STRING_BUFFER_OFFSET(&sb), 0);
2980 out:
2981     JS_LeaveLocalRootScope(cx);
2982     if (!str && STRING_BUFFER_OK(&sb))
2983         js_FinishStringBuffer(&sb);
2984     XMLArrayFinish(cx, &decls);
2985     if (ancdecls.capacity != 0)
2986         XMLArrayFinish(cx, &ancdecls);
2987     return str;
2990 /* ECMA-357 10.2 */
2991 static JSString *
2992 ToXMLString(JSContext *cx, jsval v)
2994     JSObject *obj;
2995     JSString *str;
2996     JSXML *xml;
2998     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
2999         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3000                              JSMSG_BAD_XML_CONVERSION,
3001                              js_type_str[JSVAL_IS_NULL(v)
3002                                          ? JSTYPE_NULL
3003                                          : JSTYPE_VOID]);
3004         return NULL;
3005     }
3007     if (JSVAL_IS_BOOLEAN(v) || JSVAL_IS_NUMBER(v))
3008         return js_ValueToString(cx, v);
3010     if (JSVAL_IS_STRING(v))
3011         return EscapeElementValue(cx, NULL, JSVAL_TO_STRING(v));
3013     obj = JSVAL_TO_OBJECT(v);
3014     if (!OBJECT_IS_XML(cx, obj)) {
3015         if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_STRING, &v))
3016             return NULL;
3017         str = js_ValueToString(cx, v);
3018         if (!str)
3019             return NULL;
3020         return EscapeElementValue(cx, NULL, str);
3021     }
3023     /* Handle non-element cases in this switch, returning from each case. */
3024     xml = (JSXML *) JS_GetPrivate(cx, obj);
3025     return XMLToXMLString(cx, xml, NULL, 0);
3028 static JSXMLQName *
3029 ToAttributeName(JSContext *cx, jsval v)
3031     JSString *name, *uri, *prefix;
3032     JSObject *obj;
3033     JSClass *clasp;
3034     JSXMLQName *qn;
3035     JSTempValueRooter tvr;
3037     if (JSVAL_IS_STRING(v)) {
3038         name = JSVAL_TO_STRING(v);
3039         uri = prefix = cx->runtime->emptyString;
3040     } else {
3041         if (JSVAL_IS_PRIMITIVE(v)) {
3042             name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
3043             if (name) {
3044                 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3045                                      JSMSG_BAD_XML_ATTR_NAME,
3046                                      JS_GetStringBytes(name));
3047             }
3048             return NULL;
3049         }
3051         obj = JSVAL_TO_OBJECT(v);
3052         clasp = OBJ_GET_CLASS(cx, obj);
3053         if (clasp == &js_AttributeNameClass)
3054             return (JSXMLQName *) JS_GetPrivate(cx, obj);
3056         if (clasp == &js_QNameClass.base) {
3057             qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
3058             uri = qn->uri;
3059             prefix = qn->prefix;
3060             name = qn->localName;
3061         } else {
3062             if (clasp == &js_AnyNameClass) {
3063                 name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
3064             } else {
3065                 name = js_ValueToString(cx, v);
3066                 if (!name)
3067                     return NULL;
3068             }
3069             uri = prefix = cx->runtime->emptyString;
3070         }
3071     }
3073     qn = js_NewXMLQName(cx, uri, prefix, name);
3074     if (!qn)
3075         return NULL;
3077     /*
3078      * Temp and local root scope APIs take GC-thing pointers tagged as jsvals
3079      * and blindly untag.  Since qn is a GC-thing pointer, we can treat it as
3080      * an object pointer.
3081      */
3082     JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(qn), &tvr);
3083     obj = js_GetAttributeNameObject(cx, qn);
3084     JS_POP_TEMP_ROOT(cx, &tvr);
3085     if (!obj)
3086         return NULL;
3087     return qn;
3090 static JSXMLQName *
3091 ToXMLName(JSContext *cx, jsval v, jsid *funidp)
3093     JSString *name;
3094     JSObject *obj;
3095     JSClass *clasp;
3096     uint32 index;
3097     JSXMLQName *qn;
3098     JSAtom *atom;
3100     if (JSVAL_IS_STRING(v)) {
3101         name = JSVAL_TO_STRING(v);
3102     } else {
3103         if (JSVAL_IS_PRIMITIVE(v)) {
3104             name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, v, NULL);
3105             if (name)
3106                 goto bad;
3107             return NULL;
3108         }
3110         obj = JSVAL_TO_OBJECT(v);
3111         clasp = OBJ_GET_CLASS(cx, obj);
3112         if (clasp == &js_AttributeNameClass || clasp == &js_QNameClass.base)
3113             goto out;
3114         if (clasp == &js_AnyNameClass) {
3115             name = ATOM_TO_STRING(cx->runtime->atomState.starAtom);
3116             goto construct;
3117         }
3118         name = js_ValueToString(cx, v);
3119         if (!name)
3120             return NULL;
3121     }
3123     /*
3124      * ECMA-357 10.6.1 step 1 seems to be incorrect.  The spec says:
3125      *
3126      * 1. If ToString(ToNumber(P)) == ToString(P), throw a TypeError exception
3127      *
3128      * First, _P_ should be _s_, to refer to the given string.
3129      *
3130      * Second, why does ToXMLName applied to the string type throw TypeError
3131      * only for numeric literals without any leading or trailing whitespace?
3132      *
3133      * If the idea is to reject uint32 property names, then the check needs to
3134      * be stricter, to exclude hexadecimal and floating point literals.
3135      */
3136     if (js_IdIsIndex(STRING_TO_JSVAL(name), &index))
3137         goto bad;
3139     if (*JSSTRING_CHARS(name) == '@') {
3140         name = js_NewDependentString(cx, name, 1, JSSTRING_LENGTH(name) - 1, 0);
3141         if (!name)
3142             return NULL;
3143         *funidp = 0;
3144         return ToAttributeName(cx, STRING_TO_JSVAL(name));
3145     }
3147 construct:
3148     v = STRING_TO_JSVAL(name);
3149     obj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &v);
3150     if (!obj)
3151         return NULL;
3153 out:
3154     qn = (JSXMLQName *) JS_GetPrivate(cx, obj);
3155     atom = cx->runtime->atomState.lazy.functionNamespaceURIAtom;
3156     if (qn->uri && atom &&
3157         (qn->uri == ATOM_TO_STRING(atom) ||
3158          !js_CompareStrings(qn->uri, ATOM_TO_STRING(atom)))) {
3159         if (!JS_ValueToId(cx, STRING_TO_JSVAL(qn->localName), funidp))
3160             return NULL;
3161     } else {
3162         *funidp = 0;
3163     }
3164     return qn;
3166 bad:
3167     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3168                          JSMSG_BAD_XML_NAME,
3169                          js_ValueToPrintableString(cx, STRING_TO_JSVAL(name)));
3170     return NULL;
3173 /* ECMA-357 9.1.1.13 XML [[AddInScopeNamespace]]. */
3174 static JSBool
3175 AddInScopeNamespace(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
3177     JSXMLNamespace *match, *ns2;
3178     uint32 i, n, m;
3180     if (xml->xml_class != JSXML_CLASS_ELEMENT)
3181         return JS_TRUE;
3183     /* NULL means *undefined* here -- see ECMA-357 9.1.1.13 step 2. */
3184     if (!ns->prefix) {
3185         match = NULL;
3186         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3187             ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3188             if (ns2 && !js_CompareStrings(ns2->uri, ns->uri)) {
3189                 match = ns2;
3190                 break;
3191             }
3192         }
3193         if (!match && !XMLARRAY_ADD_MEMBER(cx, &xml->xml_namespaces, n, ns))
3194             return JS_FALSE;
3195     } else {
3196         if (IS_EMPTY(ns->prefix) && IS_EMPTY(xml->name->uri))
3197             return JS_TRUE;
3198         match = NULL;
3199 #ifdef __GNUC__         /* suppress bogus gcc warnings */
3200         m = XML_NOT_FOUND;
3201 #endif
3202         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
3203             ns2 = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3204             if (ns2 && ns2->prefix &&
3205                 !js_CompareStrings(ns2->prefix, ns->prefix)) {
3206                 match = ns2;
3207                 m = i;
3208                 break;
3209             }
3210         }
3211         if (match && js_CompareStrings(match->uri, ns->uri)) {
3212             ns2 = XMLARRAY_DELETE(cx, &xml->xml_namespaces, m, JS_TRUE,
3213                                   JSXMLNamespace);
3214             JS_ASSERT(ns2 == match);
3215             match->prefix = NULL;
3216             if (!AddInScopeNamespace(cx, xml, match))
3217                 return JS_FALSE;
3218         }
3219         if (!XMLARRAY_APPEND(cx, &xml->xml_namespaces, ns))
3220             return JS_FALSE;
3221     }
3223     /* OPTION: enforce that descendants have superset namespaces. */
3224     return JS_TRUE;
3227 /* ECMA-357 9.2.1.6 XMLList [[Append]]. */
3228 static JSBool
3229 Append(JSContext *cx, JSXML *list, JSXML *xml)
3231     uint32 i, j, k, n;
3232     JSXML *kid;
3234     JS_ASSERT(list->xml_class == JSXML_CLASS_LIST);
3235     i = list->xml_kids.length;
3236     n = 1;
3237     if (xml->xml_class == JSXML_CLASS_LIST) {
3238         list->xml_target = xml->xml_target;
3239         list->xml_targetprop = xml->xml_targetprop;
3240         n = JSXML_LENGTH(xml);
3241         k = i + n;
3242         if (!XMLArraySetCapacity(cx, &list->xml_kids, k))
3243             return JS_FALSE;
3244         for (j = 0; j < n; j++) {
3245             kid = XMLARRAY_MEMBER(&xml->xml_kids, j, JSXML);
3246             if (kid)
3247                 XMLARRAY_SET_MEMBER(&list->xml_kids, i + j, kid);
3248         }
3249         return JS_TRUE;
3250     }
3252     list->xml_target = xml->parent;
3253     if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
3254         list->xml_targetprop = NULL;
3255     else
3256         list->xml_targetprop = xml->name;
3257     if (!XMLARRAY_ADD_MEMBER(cx, &list->xml_kids, i, xml))
3258         return JS_FALSE;
3259     return JS_TRUE;
3262 /* ECMA-357 9.1.1.7 XML [[DeepCopy]] and 9.2.1.7 XMLList [[DeepCopy]]. */
3263 static JSXML *
3264 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags);
3266 static JSXML *
3267 DeepCopy(JSContext *cx, JSXML *xml, JSObject *obj, uintN flags)
3269     JSXML *copy;
3270     JSBool ok;
3272     /* Our caller may not be protecting newborns with a local root scope. */
3273     if (!JS_EnterLocalRootScope(cx))
3274         return NULL;
3275     copy = DeepCopyInLRS(cx, xml, flags);
3276     if (copy) {
3277         if (obj) {
3278             /* Caller provided the object for this copy, hook 'em up. */
3279             ok = JS_SetPrivate(cx, obj, copy);
3280             if (ok)
3281                 copy->object = obj;
3282         } else {
3283             ok = js_GetXMLObject(cx, copy) != NULL;
3284         }
3285         if (!ok)
3286             copy = NULL;
3287     }
3288     JS_LeaveLocalRootScope(cx);
3289     return copy;
3292 /*
3293  * (i) We must be in a local root scope (InLRS).
3294  * (ii) parent must have a rooted object.
3295  * (iii) from's owning object must be locked if not thread-local.
3296  */
3297 static JSBool
3298 DeepCopySetInLRS(JSContext *cx, JSXMLArray *from, JSXMLArray *to, JSXML *parent,
3299                  uintN flags)
3301     uint32 j, n;
3302     JSXMLArrayCursor cursor;
3303     JSBool ok;
3304     JSXML *kid, *kid2;
3305     JSString *str;
3307     JS_ASSERT(cx->localRootStack);
3309     n = from->length;
3310     if (!XMLArraySetCapacity(cx, to, n))
3311         return JS_FALSE;
3313     XMLArrayCursorInit(&cursor, from);
3314     j = 0;
3315     ok = JS_TRUE;
3316     while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
3317         if ((flags & XSF_IGNORE_COMMENTS) &&
3318             kid->xml_class == JSXML_CLASS_COMMENT) {
3319             continue;
3320         }
3321         if ((flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS) &&
3322             kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION) {
3323             continue;
3324         }
3325         if ((flags & XSF_IGNORE_WHITESPACE) &&
3326             (kid->xml_flags & XMLF_WHITESPACE_TEXT)) {
3327             continue;
3328         }
3329         kid2 = DeepCopyInLRS(cx, kid, flags);
3330         if (!kid2) {
3331             to->length = j;
3332             ok = JS_FALSE;
3333             break;
3334         }
3336         if ((flags & XSF_IGNORE_WHITESPACE) &&
3337             n > 1 && kid2->xml_class == JSXML_CLASS_TEXT) {
3338             str = ChompXMLWhitespace(cx, kid2->xml_value);
3339             if (!str) {
3340                 to->length = j;
3341                 ok = JS_FALSE;
3342                 break;
3343             }
3344             kid2->xml_value = str;
3345         }
3347         XMLARRAY_SET_MEMBER(to, j, kid2);
3348         ++j;
3349         if (parent->xml_class != JSXML_CLASS_LIST)
3350             kid2->parent = parent;
3351     }
3352     XMLArrayCursorFinish(&cursor);
3353     if (!ok)
3354         return JS_FALSE;
3356     if (j < n)
3357         XMLArrayTrim(to);
3358     return JS_TRUE;
3361 static JSXML *
3362 DeepCopyInLRS(JSContext *cx, JSXML *xml, uintN flags)
3364     JSXML *copy;
3365     JSXMLQName *qn;
3366     JSBool ok;
3367     uint32 i, n;
3368     JSXMLNamespace *ns, *ns2;
3370     /* Our caller must be protecting newborn objects. */
3371     JS_ASSERT(cx->localRootStack);
3373     copy = js_NewXML(cx, xml->xml_class);
3374     if (!copy)
3375         return NULL;
3376     qn = xml->name;
3377     if (qn) {
3378         qn = js_NewXMLQName(cx, qn->uri, qn->prefix, qn->localName);
3379         if (!qn) {
3380             ok = JS_FALSE;
3381             goto out;
3382         }
3383     }
3384     copy->name = qn;
3385     copy->xml_flags = xml->xml_flags;
3387     if (JSXML_HAS_VALUE(xml)) {
3388         copy->xml_value = xml->xml_value;
3389         ok = JS_TRUE;
3390     } else {
3391         ok = DeepCopySetInLRS(cx, &xml->xml_kids, &copy->xml_kids, copy, flags);
3392         if (!ok)
3393             goto out;
3395         if (xml->xml_class == JSXML_CLASS_LIST) {
3396             copy->xml_target = xml->xml_target;
3397             copy->xml_targetprop = xml->xml_targetprop;
3398         } else {
3399             n = xml->xml_namespaces.length;
3400             ok = XMLArraySetCapacity(cx, &copy->xml_namespaces, n);
3401             if (!ok)
3402                 goto out;
3403             for (i = 0; i < n; i++) {
3404                 ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
3405                 if (!ns)
3406                     continue;
3407                 ns2 = js_NewXMLNamespace(cx, ns->prefix, ns->uri, ns->declared);
3408                 if (!ns2) {
3409                     copy->xml_namespaces.length = i;
3410                     ok = JS_FALSE;
3411                     goto out;
3412                 }
3413                 XMLARRAY_SET_MEMBER(&copy->xml_namespaces, i, ns2);
3414             }
3416             ok = DeepCopySetInLRS(cx, &xml->xml_attrs, &copy->xml_attrs, copy,
3417                                   0);
3418             if (!ok)
3419                 goto out;
3420         }
3421     }
3423 out:
3424     if (!ok)
3425         return NULL;
3426     return copy;
3429 static void
3430 ReportBadXMLName(JSContext *cx, jsval id)
3432     JSString *name;
3434     name = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, id, NULL);
3435     if (name) {
3436         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3437                              JSMSG_BAD_XML_NAME,
3438                              JS_GetStringBytes(name));
3439     }
3442 /* ECMA-357 9.1.1.4 XML [[DeleteByIndex]]. */
3443 static JSBool
3444 DeleteByIndex(JSContext *cx, JSXML *xml, jsval id, jsval *vp)
3446     uint32 index;
3447     JSXML *kid;
3449     if (!js_IdIsIndex(id, &index)) {
3450         ReportBadXMLName(cx, id);
3451         return JS_FALSE;
3452     }
3454     if (JSXML_HAS_KIDS(xml) && index < xml->xml_kids.length) {
3455         kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
3456         if (kid)
3457             kid->parent = NULL;
3458         XMLArrayDelete(cx, &xml->xml_kids, index, JS_TRUE);
3459     }
3461     *vp = JSVAL_TRUE;
3462     return JS_TRUE;
3465 typedef JSBool (*JSXMLNameMatcher)(JSXMLQName *nameqn, JSXML *xml);
3467 static JSBool
3468 MatchAttrName(JSXMLQName *nameqn, JSXML *attr)
3470     JSXMLQName *attrqn = attr->name;
3472     return (IS_STAR(nameqn->localName) ||
3473             !js_CompareStrings(attrqn->localName, nameqn->localName)) &&
3474            (!nameqn->uri ||
3475             !js_CompareStrings(attrqn->uri, nameqn->uri));
3478 static JSBool
3479 MatchElemName(JSXMLQName *nameqn, JSXML *elem)
3481     return (IS_STAR(nameqn->localName) ||
3482             (elem->xml_class == JSXML_CLASS_ELEMENT &&
3483              !js_CompareStrings(elem->name->localName, nameqn->localName))) &&
3484            (!nameqn->uri ||
3485             (elem->xml_class == JSXML_CLASS_ELEMENT &&
3486              !js_CompareStrings(elem->name->uri, nameqn->uri)));
3489 /* ECMA-357 9.1.1.8 XML [[Descendants]] and 9.2.1.8 XMLList [[Descendants]]. */
3490 static JSBool
3491 DescendantsHelper(JSContext *cx, JSXML *xml, JSXMLQName *nameqn, JSXML *list)
3493     uint32 i, n;
3494     JSXML *attr, *kid;
3496     if (xml->xml_class == JSXML_CLASS_ELEMENT &&
3497         OBJ_GET_CLASS(cx, nameqn->object) == &js_AttributeNameClass) {
3498         for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
3499             attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3500             if (attr && MatchAttrName(nameqn, attr)) {
3501                 if (!Append(cx, list, attr))
3502                     return JS_FALSE;
3503             }
3504         }
3505     }
3507     for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
3508         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3509         if (!kid)
3510             continue;
3511         if (OBJ_GET_CLASS(cx, nameqn->object) != &js_AttributeNameClass &&
3512             MatchElemName(nameqn, kid)) {
3513             if (!Append(cx, list, kid))
3514                 return JS_FALSE;
3515         }
3516         if (!DescendantsHelper(cx, kid, nameqn, list))
3517             return JS_FALSE;
3518     }
3519     return JS_TRUE;
3522 static JSXML *
3523 Descendants(JSContext *cx, JSXML *xml, jsval id)
3525     jsid funid;
3526     JSXMLQName *nameqn;
3527     JSObject *listobj;
3528     JSXML *list, *kid;
3529     uint32 i, n;
3530     JSBool ok;
3532     nameqn = ToXMLName(cx, id, &funid);
3533     if (!nameqn)
3534         return NULL;
3536     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
3537     if (!listobj)
3538         return NULL;
3539     list = (JSXML *) JS_GetPrivate(cx, listobj);
3540     if (funid)
3541         return list;
3543     /*
3544      * Protect nameqn's object and strings from GC by linking list to it
3545      * temporarily.  The cx->newborn[GCX_OBJECT] GC root protects listobj,
3546      * which protects list.  Any other object allocations occuring beneath
3547      * DescendantsHelper use local roots.
3548      */
3549     list->name = nameqn;
3550     if (!JS_EnterLocalRootScope(cx))
3551         return NULL;
3552     if (xml->xml_class == JSXML_CLASS_LIST) {
3553         ok = JS_TRUE;
3554         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
3555             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3556             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3557                 ok = DescendantsHelper(cx, kid, nameqn, list);
3558                 if (!ok)
3559                     break;
3560             }
3561         }
3562     } else {
3563         ok = DescendantsHelper(cx, xml, nameqn, list);
3564     }
3565     JS_LeaveLocalRootScope(cx);
3566     if (!ok)
3567         return NULL;
3568     list->name = NULL;
3569     return list;
3572 static JSBool
3573 xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp);
3575 /* Recursive (JSXML *) parameterized version of Equals. */
3576 static JSBool
3577 XMLEquals(JSContext *cx, JSXML *xml, JSXML *vxml, JSBool *bp)
3579     JSXMLQName *qn, *vqn;
3580     uint32 i, j, n;
3581     JSXMLArrayCursor cursor, vcursor;
3582     JSXML *kid, *vkid, *attr, *vattr;
3583     JSBool ok;
3584     JSObject *xobj, *vobj;
3586 retry:
3587     if (xml->xml_class != vxml->xml_class) {
3588         if (xml->xml_class == JSXML_CLASS_LIST && xml->xml_kids.length == 1) {
3589             xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3590             if (xml)
3591                 goto retry;
3592         }
3593         if (vxml->xml_class == JSXML_CLASS_LIST && vxml->xml_kids.length == 1) {
3594             vxml = XMLARRAY_MEMBER(&vxml->xml_kids, 0, JSXML);
3595             if (vxml)
3596                 goto retry;
3597         }
3598         *bp = JS_FALSE;
3599         return JS_TRUE;
3600     }
3602     qn = xml->name;
3603     vqn = vxml->name;
3604     if (qn) {
3605         *bp = vqn &&
3606               !js_CompareStrings(qn->localName, vqn->localName) &&
3607               !js_CompareStrings(qn->uri, vqn->uri);
3608     } else {
3609         *bp = vqn == NULL;
3610     }
3611     if (!*bp)
3612         return JS_TRUE;
3614     if (JSXML_HAS_VALUE(xml)) {
3615         *bp = !js_CompareStrings(xml->xml_value, vxml->xml_value);
3616     } else if (xml->xml_kids.length != vxml->xml_kids.length) {
3617         *bp = JS_FALSE;
3618     } else {
3619         XMLArrayCursorInit(&cursor, &xml->xml_kids);
3620         XMLArrayCursorInit(&vcursor, &vxml->xml_kids);
3621         for (;;) {
3622             kid = (JSXML *) XMLArrayCursorNext(&cursor);
3623             vkid = (JSXML *) XMLArrayCursorNext(&vcursor);
3624             if (!kid || !vkid) {
3625                 *bp = !kid && !vkid;
3626                 ok = JS_TRUE;
3627                 break;
3628             }
3629             xobj = js_GetXMLObject(cx, kid);
3630             vobj = js_GetXMLObject(cx, vkid);
3631             ok = xobj && vobj &&
3632                  xml_equality(cx, xobj, OBJECT_TO_JSVAL(vobj), bp);
3633             if (!ok || !*bp)
3634                 break;
3635         }
3636         XMLArrayCursorFinish(&vcursor);
3637         XMLArrayCursorFinish(&cursor);
3638         if (!ok)
3639             return JS_FALSE;
3641         if (*bp && xml->xml_class == JSXML_CLASS_ELEMENT) {
3642             n = xml->xml_attrs.length;
3643             if (n != vxml->xml_attrs.length)
3644                 *bp = JS_FALSE;
3645             for (i = 0; *bp && i < n; i++) {
3646                 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
3647                 if (!attr)
3648                     continue;
3649                 j = XMLARRAY_FIND_MEMBER(&vxml->xml_attrs, attr, attr_identity);
3650                 if (j == XML_NOT_FOUND) {
3651                     *bp = JS_FALSE;
3652                     break;
3653                 }
3654                 vattr = XMLARRAY_MEMBER(&vxml->xml_attrs, j, JSXML);
3655                 if (!vattr)
3656                     continue;
3657                 *bp = !js_CompareStrings(attr->xml_value, vattr->xml_value);
3658             }
3659         }
3660     }
3662     return JS_TRUE;
3665 /* ECMA-357 9.1.1.9 XML [[Equals]] and 9.2.1.9 XMLList [[Equals]]. */
3666 static JSBool
3667 Equals(JSContext *cx, JSXML *xml, jsval v, JSBool *bp)
3669     JSObject *vobj;
3670     JSXML *vxml;
3672     if (JSVAL_IS_PRIMITIVE(v)) {
3673         *bp = JS_FALSE;
3674         if (xml->xml_class == JSXML_CLASS_LIST) {
3675             if (xml->xml_kids.length == 1) {
3676                 vxml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
3677                 if (!vxml)
3678                     return JS_TRUE;
3679                 vobj = js_GetXMLObject(cx, vxml);
3680                 if (!vobj)
3681                     return JS_FALSE;
3682                 return js_XMLObjectOps.equality(cx, vobj, v, bp);
3683             }
3684             if (JSVAL_IS_VOID(v) && xml->xml_kids.length == 0)
3685                 *bp = JS_TRUE;
3686         }
3687     } else {
3688         vobj = JSVAL_TO_OBJECT(v);
3689         if (!OBJECT_IS_XML(cx, vobj)) {
3690             *bp = JS_FALSE;
3691         } else {
3692             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3693             if (!XMLEquals(cx, xml, vxml, bp))
3694                 return JS_FALSE;
3695         }
3696     }
3697     return JS_TRUE;
3700 static JSBool
3701 CheckCycle(JSContext *cx, JSXML *xml, JSXML *kid)
3703     JS_ASSERT(kid->xml_class != JSXML_CLASS_LIST);
3705     do {
3706         if (xml == kid) {
3707             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3708                                  JSMSG_CYCLIC_VALUE, js_XML_str);
3709             return JS_FALSE;
3710         }
3711     } while ((xml = xml->parent) != NULL);
3713     return JS_TRUE;
3716 /* ECMA-357 9.1.1.11 XML [[Insert]]. */
3717 static JSBool
3718 Insert(JSContext *cx, JSXML *xml, uint32 i, jsval v)
3720     uint32 j, n;
3721     JSXML *vxml, *kid;
3722     JSObject *vobj;
3723     JSString *str;
3725     if (!JSXML_HAS_KIDS(xml))
3726         return JS_TRUE;
3728     n = 1;
3729     vxml = NULL;
3730     if (!JSVAL_IS_PRIMITIVE(v)) {
3731         vobj = JSVAL_TO_OBJECT(v);
3732         if (OBJECT_IS_XML(cx, vobj)) {
3733             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3734             if (vxml->xml_class == JSXML_CLASS_LIST) {
3735                 n = vxml->xml_kids.length;
3736                 if (n == 0)
3737                     return JS_TRUE;
3738                 for (j = 0; j < n; j++) {
3739                     kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3740                     if (!kid)
3741                         continue;
3742                     if (!CheckCycle(cx, xml, kid))
3743                         return JS_FALSE;
3744                 }
3745             } else if (vxml->xml_class == JSXML_CLASS_ELEMENT) {
3746                 /* OPTION: enforce that descendants have superset namespaces. */
3747                 if (!CheckCycle(cx, xml, vxml))
3748                     return JS_FALSE;
3749             }
3750         }
3751     }
3752     if (!vxml) {
3753         str = js_ValueToString(cx, v);
3754         if (!str)
3755             return JS_FALSE;
3757         vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3758         if (!vxml)
3759             return JS_FALSE;
3760         vxml->xml_value = str;
3761     }
3763     if (i > xml->xml_kids.length)
3764         i = xml->xml_kids.length;
3766     if (!XMLArrayInsert(cx, &xml->xml_kids, i, n))
3767         return JS_FALSE;
3769     if (vxml->xml_class == JSXML_CLASS_LIST) {
3770         for (j = 0; j < n; j++) {
3771             kid = XMLARRAY_MEMBER(&vxml->xml_kids, j, JSXML);
3772             if (!kid)
3773                 continue;
3774             kid->parent = xml;
3775             XMLARRAY_SET_MEMBER(&xml->xml_kids, i + j, kid);
3777             /* OPTION: enforce that descendants have superset namespaces. */
3778         }
3779     } else {
3780         vxml->parent = xml;
3781         XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
3782     }
3783     return JS_TRUE;
3786 static JSBool
3787 IndexToIdVal(JSContext *cx, uint32 index, jsval *idvp)
3789     JSString *str;
3791     if (index <= JSVAL_INT_MAX) {
3792         *idvp = INT_TO_JSVAL(index);
3793     } else {
3794         str = js_NumberToString(cx, (jsdouble) index);
3795         if (!str)
3796             return JS_FALSE;
3797         *idvp = STRING_TO_JSVAL(str);
3798     }
3799     return JS_TRUE;
3802 /* ECMA-357 9.1.1.12 XML [[Replace]]. */
3803 static JSBool
3804 Replace(JSContext *cx, JSXML *xml, jsval id, jsval v)
3806     uint32 i, n;
3807     JSXML *vxml, *kid;
3808     JSObject *vobj;
3809     jsval junk;
3810     JSString *str;
3812     if (!JSXML_HAS_KIDS(xml))
3813         return JS_TRUE;
3815     if (!js_IdIsIndex(id, &i)) {
3816         ReportBadXMLName(cx, id);
3817         return JS_FALSE;
3818     }
3820     /*
3821      * 9.1.1.12
3822      * [[Replace]] handles _i >= x.[[Length]]_ by incrementing _x.[[Length]_.
3823      * It should therefore constrain callers to pass in _i <= x.[[Length]]_.
3824      */
3825     n = xml->xml_kids.length;
3826     if (i >= n) {
3827         if (!IndexToIdVal(cx, n, &id))
3828             return JS_FALSE;
3829         i = n;
3830     }
3832     vxml = NULL;
3833     if (!JSVAL_IS_PRIMITIVE(v)) {
3834         vobj = JSVAL_TO_OBJECT(v);
3835         if (OBJECT_IS_XML(cx, vobj))
3836             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
3837     }
3839     switch (vxml ? vxml->xml_class : JSXML_CLASS_LIMIT) {
3840       case JSXML_CLASS_ELEMENT:
3841         /* OPTION: enforce that descendants have superset namespaces. */
3842         if (!CheckCycle(cx, xml, vxml))
3843             return JS_FALSE;
3844       case JSXML_CLASS_COMMENT:
3845       case JSXML_CLASS_PROCESSING_INSTRUCTION:
3846       case JSXML_CLASS_TEXT:
3847         goto do_replace;
3849       case JSXML_CLASS_LIST:
3850         if (i < n && !DeleteByIndex(cx, xml, id, &junk))
3851             return JS_FALSE;
3852         if (!Insert(cx, xml, i, v))
3853             return JS_FALSE;
3854         break;
3856       default:
3857         str = js_ValueToString(cx, v);
3858         if (!str)
3859             return JS_FALSE;
3861         vxml = js_NewXML(cx, JSXML_CLASS_TEXT);
3862         if (!vxml)
3863             return JS_FALSE;
3864         vxml->xml_value = str;
3866       do_replace:
3867         vxml->parent = xml;
3868         if (i < n) {
3869             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
3870             if (kid)
3871                 kid->parent = NULL;
3872         }
3873         if (!XMLARRAY_ADD_MEMBER(cx, &xml->xml_kids, i, vxml))
3874             return JS_FALSE;
3875         break;
3876     }
3878     return JS_TRUE;
3881 /* Forward declared -- its implementation uses other statics that call it. */
3882 static JSBool
3883 ResolveValue(JSContext *cx, JSXML *list, JSXML **result);
3885 /* ECMA-357 9.1.1.3 XML [[Delete]], 9.2.1.3 XML [[Delete]]. */
3886 static JSBool
3887 DeleteProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
3889     JSXML *xml, *kid, *parent;
3890     JSBool isIndex;
3891     JSXMLArray *array;
3892     uint32 length, index, deleteCount;
3893     JSXMLQName *nameqn;
3894     jsid funid;
3895     JSObject *nameobj, *kidobj;
3896     JSXMLNameMatcher matcher;
3898     xml = (JSXML *) JS_GetPrivate(cx, obj);
3899     isIndex = js_IdIsIndex(id, &index);
3900     if (JSXML_HAS_KIDS(xml)) {
3901         array = &xml->xml_kids;
3902         length = array->length;
3903     } else {
3904         array = NULL;
3905         length = 0;
3906     }
3908     if (xml->xml_class == JSXML_CLASS_LIST) {
3909         /* ECMA-357 9.2.1.3. */
3910         if (isIndex && index < length) {
3911             kid = XMLARRAY_MEMBER(array, index, JSXML);
3912             if (!kid)
3913                 goto out;
3914             parent = kid->parent;
3915             if (parent) {
3916                 JS_ASSERT(parent != xml);
3917                 JS_ASSERT(JSXML_HAS_KIDS(parent));
3919                 if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
3920                     nameqn = kid->name;
3921                     nameobj = js_GetAttributeNameObject(cx, nameqn);
3922                     if (!nameobj || !js_GetXMLObject(cx, parent))
3923                         return JS_FALSE;
3925                     id = OBJECT_TO_JSVAL(nameobj);
3926                     if (!DeleteProperty(cx, parent->object, id, vp))
3927                         return JS_FALSE;
3928                 } else {
3929                     index = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
3930                     JS_ASSERT(index != XML_NOT_FOUND);
3931                     if (!IndexToIdVal(cx, index, &id))
3932                         return JS_FALSE;
3933                     if (!DeleteByIndex(cx, parent, id, vp))
3934                         return JS_FALSE;
3935                 }
3936             }
3938             XMLArrayDelete(cx, array, index, JS_TRUE);
3939         } else {
3940             for (index = 0; index < length; index++) {
3941                 kid = XMLARRAY_MEMBER(array, index, JSXML);
3942                 if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
3943                     kidobj = js_GetXMLObject(cx, kid);
3944                     if (!kidobj || !DeleteProperty(cx, kidobj, id, vp))
3945                         return JS_FALSE;
3946                 }
3947             }
3948         }
3949     } else {
3950         /* ECMA-357 9.1.1.3. */
3951         if (isIndex) {
3952             /* See NOTE in spec: this variation is reserved for future use. */
3953             ReportBadXMLName(cx, id);
3954             return JS_FALSE;
3955         }
3957         nameqn = ToXMLName(cx, id, &funid);
3958         if (!nameqn)
3959             return JS_FALSE;
3960         if (funid)
3961             goto out;
3962         nameobj = nameqn->object;
3964         if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
3965             if (xml->xml_class != JSXML_CLASS_ELEMENT)
3966                 goto out;
3967             array = &xml->xml_attrs;
3968             length = array->length;
3969             matcher = MatchAttrName;
3970         } else {
3971             matcher = MatchElemName;
3972         }
3973         if (length != 0) {
3974             deleteCount = 0;
3975             for (index = 0; index < length; index++) {
3976                 kid = XMLARRAY_MEMBER(array, index, JSXML);
3977                 if (kid && matcher(nameqn, kid)) {
3978                     kid->parent = NULL;
3979                     XMLArrayDelete(cx, array, index, JS_FALSE);
3980                     ++deleteCount;
3981                 } else if (deleteCount != 0) {
3982                     XMLARRAY_SET_MEMBER(array,
3983                                         index - deleteCount,
3984                                         array->vector[index]);
3985                 }
3986             }
3987             array->length -= deleteCount;
3988         }
3989     }
3991 out:
3992     *vp = JSVAL_TRUE;
3993     return JS_TRUE;
3996 /*
3997  * Class compatibility mask flag bits stored in xml_methods[i].extra.  If XML
3998  * and XMLList are unified (an incompatible change to ECMA-357), then we don't
3999  * need any of this.
4000  */
4001 #define XML_MASK                0x1
4002 #define XMLLIST_MASK            0x2
4003 #define GENERIC_MASK            (XML_MASK | XMLLIST_MASK)
4004 #define CLASS_TO_MASK(c)        (1 + ((c) == JSXML_CLASS_LIST))
4006 static JSBool
4007 GetFunction(JSContext *cx, JSObject *obj, JSXML *xml, jsid id, jsval *vp)
4009     JSFunction *fun;
4011     do {
4012         /* XXXbe really want a separate scope for function::*. */
4013         if (!js_GetProperty(cx, obj, id, vp))
4014             return JS_FALSE;
4015         if (JSVAL_IS_FUNCTION(cx, *vp)) {
4016             if (xml && OBJECT_IS_XML(cx, obj)) {
4017                 fun = (JSFunction *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
4018                 if (fun->spare &&
4019                     (fun->spare & CLASS_TO_MASK(xml->xml_class)) == 0) {
4020                     /* XML method called on XMLList or vice versa. */
4021                     *vp = JSVAL_VOID;
4022                 }
4023             }
4024             break;
4025         }
4026     } while ((obj = OBJ_GET_PROTO(cx, obj)) != NULL);
4027     return JS_TRUE;
4030 static JSBool
4031 SyncInScopeNamespaces(JSContext *cx, JSXML *xml)
4033     JSXMLArray *nsarray;
4034     uint32 i, n;
4035     JSXMLNamespace *ns;
4037     nsarray = &xml->xml_namespaces;
4038     while ((xml = xml->parent) != NULL) {
4039         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
4040             ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
4041             if (ns && !XMLARRAY_HAS_MEMBER(nsarray, ns, namespace_identity)) {
4042                 if (!XMLARRAY_APPEND(cx, nsarray, ns))
4043                     return JS_FALSE;
4044             }
4045         }
4046     }
4047     return JS_TRUE;
4050 /* ECMA-357 9.1.1.1 XML [[Get]] and 9.2.1.1 XMLList [[Get]]. */
4051 static JSBool
4052 GetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4054     JSXML *xml, *list, *kid;
4055     uint32 index;
4056     JSObject *kidobj, *listobj, *nameobj;
4057     JSXMLQName *nameqn;
4058     jsid funid;
4059     JSBool ok;
4060     JSXMLArrayCursor cursor;
4061     jsval kidval;
4062     JSXMLArray *array;
4063     JSXMLNameMatcher matcher;
4065     xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
4066     if (!xml)
4067         return JS_TRUE;
4069 #ifdef __GNUC__
4070     list = NULL;    /* quell GCC overwarning */
4071 #endif
4073 retry:
4074     if (xml->xml_class == JSXML_CLASS_LIST) {
4075         /* ECMA-357 9.2.1.1 starts here. */
4076         if (js_IdIsIndex(id, &index)) {
4077             /*
4078              * Erratum: 9.2 is not completely clear that indexed properties
4079              * correspond to kids, but that's what it seems to say, and it's
4080              * what any sane user would want.
4081              */
4082             if (index < xml->xml_kids.length) {
4083                 kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
4084                 if (!kid) {
4085                     *vp = JSVAL_VOID;
4086                     return JS_TRUE;
4087                 }
4088                 kidobj = js_GetXMLObject(cx, kid);
4089                 if (!kidobj)
4090                     return JS_FALSE;
4092                 *vp = OBJECT_TO_JSVAL(kidobj);
4093             } else {
4094                 *vp = JSVAL_VOID;
4095             }
4096             return JS_TRUE;
4097         }
4099         nameqn = ToXMLName(cx, id, &funid);
4100         if (!nameqn)
4101             return JS_FALSE;
4102         if (funid)
4103             return GetFunction(cx, obj, xml, funid, vp);
4105         /*
4106          * Recursion through GetProperty may allocate more list objects, so
4107          * we make use of local root scopes here.  Each new allocation will
4108          * push the newborn onto the local root stack.
4109          */
4110         ok = JS_EnterLocalRootScope(cx);
4111         if (!ok)
4112             return JS_FALSE;
4114         /*
4115          * NB: nameqn is already protected from GC by cx->newborn[GCX_OBJECT]
4116          * until listobj is created.  After that, a local root keeps listobj
4117          * alive, and listobj's private keeps nameqn alive via targetprop.
4118          */
4119         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4120         if (!listobj) {
4121             ok = JS_FALSE;
4122         } else {
4123             list = (JSXML *) JS_GetPrivate(cx, listobj);
4124             list->xml_target = xml;
4126             XMLArrayCursorInit(&cursor, &xml->xml_kids);
4127             while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
4128                 if (kid->xml_class == JSXML_CLASS_ELEMENT) {
4129                     kidobj = js_GetXMLObject(cx, kid);
4130                     if (!kidobj) {
4131                         ok = JS_FALSE;
4132                         break;
4133                     }
4134                     ok = GetProperty(cx, kidobj, id, &kidval);
4135                     if (!ok)
4136                         break;
4137                     kidobj = JSVAL_TO_OBJECT(kidval);
4138                     kid = (JSXML *) JS_GetPrivate(cx, kidobj);
4139                     if (JSXML_LENGTH(kid) > 0) {
4140                         ok = Append(cx, list, kid);
4141                         if (!ok)
4142                             break;
4143                     }
4144                 }
4145             }
4146             XMLArrayCursorFinish(&cursor);
4147         }
4148     } else {
4149         /* ECMA-357 9.1.1.1 starts here. */
4150         if (js_IdIsIndex(id, &index)) {
4151             obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
4152             if (!obj)
4153                 return JS_FALSE;
4154             xml = (JSXML *) JS_GetPrivate(cx, obj);
4155             goto retry;
4156         }
4158         nameqn = ToXMLName(cx, id, &funid);
4159         if (!nameqn)
4160             return JS_FALSE;
4161         if (funid)
4162             return GetFunction(cx, obj, xml, funid, vp);
4163         nameobj = nameqn->object;
4165         /*
4166          * Recursion through GetProperty may allocate more list objects, so
4167          * we make use of local root scopes here.  Each new allocation will
4168          * push the newborn onto the local root stack.
4169          */
4170         ok = JS_EnterLocalRootScope(cx);
4171         if (!ok)
4172             return JS_FALSE;
4174         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4175         if (!listobj) {
4176             ok = JS_FALSE;
4177         } else {
4178             list = (JSXML *) JS_GetPrivate(cx, listobj);
4179             list->xml_target = xml;
4181             if (JSXML_HAS_KIDS(xml)) {
4182                 if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4183                     array = &xml->xml_attrs;
4184                     matcher = MatchAttrName;
4185                 } else {
4186                     array = &xml->xml_kids;
4187                     matcher = MatchElemName;
4188                 }
4189                 XMLArrayCursorInit(&cursor, array);
4190                 while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
4191                     if (matcher(nameqn, kid)) {
4192                         if (array == &xml->xml_kids &&
4193                             kid->xml_class == JSXML_CLASS_ELEMENT) {
4194                             ok = SyncInScopeNamespaces(cx, kid);
4195                             if (!ok)
4196                                 break;
4197                         }
4198                         ok = Append(cx, list, kid);
4199                         if (!ok)
4200                             break;
4201                     }
4202                 }
4203                 XMLArrayCursorFinish(&cursor);
4204             }
4205         }
4206     }
4208     /* Common tail code for list and non-list cases. */
4209     JS_LeaveLocalRootScope(cx);
4210     if (!ok)
4211         return JS_FALSE;
4213     /*
4214      * Erratum: ECMA-357 9.1.1.1 misses that [[Append]] sets the given list's
4215      * [[TargetProperty]] to the property that is being appended. This means
4216      * that any use of the internal [[Get]] property returns a list which,
4217      * when used by e.g. [[Insert]] duplicates the last element matched by id.
4218      * See bug 336921.
4219      */
4220     list->xml_targetprop = nameqn;
4221     *vp = OBJECT_TO_JSVAL(listobj);
4222     return JS_TRUE;
4225 static JSXML *
4226 CopyOnWrite(JSContext *cx, JSXML *xml, JSObject *obj)
4228     JS_ASSERT(xml->object != obj);
4230     xml = DeepCopy(cx, xml, obj, 0);
4231     if (!xml)
4232         return NULL;
4234     JS_ASSERT(xml->object == obj);
4235     return xml;
4238 #define CHECK_COPY_ON_WRITE(cx,xml,obj)                                       \
4239     (xml->object == obj ? xml : CopyOnWrite(cx, xml, obj))
4241 static JSString *
4242 KidToString(JSContext *cx, JSXML *xml, uint32 index)
4244     JSXML *kid;
4245     JSObject *kidobj;
4247     kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
4248     if (!kid)
4249         return cx->runtime->emptyString;
4250     kidobj = js_GetXMLObject(cx, kid);
4251     if (!kidobj)
4252         return NULL;
4253     return js_ValueToString(cx, OBJECT_TO_JSVAL(kidobj));
4256 /* ECMA-357 9.1.1.2 XML [[Put]] and 9.2.1.2 XMLList [[Put]]. */
4257 static JSBool
4258 PutProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4260     JSBool ok, primitiveAssign;
4261     enum { OBJ_ROOT, ID_ROOT, VAL_ROOT };
4262     jsval roots[3];
4263     JSTempValueRooter tvr;
4264     JSXML *xml, *vxml, *rxml, *kid, *attr, *parent, *copy, *kid2, *match;
4265     JSObject *vobj, *nameobj, *attrobj, *parentobj, *kidobj, *copyobj;
4266     JSXMLQName *targetprop, *nameqn, *attrqn;
4267     uint32 index, i, j, k, n, q;
4268     jsval attrval, nsval, junk;
4269     jsid funid;
4270     JSString *left, *right, *space;
4271     JSXMLNamespace *ns;
4273     xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
4274     if (!xml)
4275         return JS_TRUE;
4277     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
4278     if (!xml)
4279         return JS_FALSE;
4281     /* Precompute vxml for 9.2.1.2 2(c)(vii)(2-3) and 2(d) and 9.1.1.2 1. */
4282     vxml = NULL;
4283     if (!JSVAL_IS_PRIMITIVE(*vp)) {
4284         vobj = JSVAL_TO_OBJECT(*vp);
4285         if (OBJECT_IS_XML(cx, vobj))
4286             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4287     }
4289     /* Control flow after here must exit via label out. */
4290     ok = JS_EnterLocalRootScope(cx);
4291     if (!ok)
4292         return JS_FALSE;
4293     roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
4294     roots[ID_ROOT] = id;
4295     roots[VAL_ROOT] = *vp;
4296     JS_PUSH_TEMP_ROOT(cx, 3, roots, &tvr);
4298     if (xml->xml_class == JSXML_CLASS_LIST) {
4299         /* ECMA-357 9.2.1.2. */
4300         if (js_IdIsIndex(id, &index)) {
4301             /* Step 1 sets i to the property index. */
4302             i = index;
4304             /* 2(a-b). */
4305             if (xml->xml_target) {
4306                 ok = ResolveValue(cx, xml->xml_target, &rxml);
4307                 if (!ok)
4308                     goto out;
4309                 if (!rxml)
4310                     goto out;
4311                 JS_ASSERT(rxml->object);
4312             } else {
4313                 rxml = NULL;
4314             }
4316             /* 2(c). */
4317             if (index >= xml->xml_kids.length) {
4318                 /* 2(c)(i). */
4319                 if (rxml) {
4320                     if (rxml->xml_class == JSXML_CLASS_LIST) {
4321                         if (rxml->xml_kids.length != 1)
4322                             goto out;
4323                         rxml = XMLARRAY_MEMBER(&rxml->xml_kids, 0, JSXML);
4324                         if (!rxml)
4325                             goto out;
4326                         ok = js_GetXMLObject(cx, rxml) != NULL;
4327                         if (!ok)
4328                             goto out;
4329                     }
4331                     /*
4332                      * Erratum: ECMA-357 9.2.1.2 step 2(c)(ii) sets
4333                      * _y.[[Parent]] = r_ where _r_ is the result of
4334                      * [[ResolveValue]] called on _x.[[TargetObject]] in
4335                      * 2(a)(i).  This can result in text parenting text:
4336                      *
4337                      *    var MYXML = new XML();
4338                      *    MYXML.appendChild(new XML("<TEAM>Giants</TEAM>"));
4339                      *
4340                      * (testcase from Werner Sharp <wsharp@macromedia.com>).
4341                      *
4342                      * To match insertChildAfter, insertChildBefore,
4343                      * prependChild, and setChildren, we should silently
4344                      * do nothing in this case.
4345                      */
4346                     if (!JSXML_HAS_KIDS(rxml))
4347                         goto out;
4348                 }
4350                 /* 2(c)(ii) is distributed below as several js_NewXML calls. */
4351                 targetprop = xml->xml_targetprop;
4352                 if (!targetprop || IS_STAR(targetprop->localName)) {
4353                     /* 2(c)(iv)(1-2), out of order w.r.t. 2(c)(iii). */
4354                     kid = js_NewXML(cx, JSXML_CLASS_TEXT);
4355                     if (!kid)
4356                         goto bad;
4357                 } else {
4358                     nameobj = js_GetXMLQNameObject(cx, targetprop);
4359                     if (!nameobj)
4360                         goto bad;
4361                     if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4362                         /*
4363                          * 2(c)(iii)(1-3).
4364                          * Note that rxml can't be null here, because target
4365                          * and targetprop are non-null.
4366                          */
4367                         ok = GetProperty(cx, rxml->object, id, &attrval);
4368                         if (!ok)
4369                             goto out;
4370                         attrobj = JSVAL_TO_OBJECT(attrval);
4371                         attr = (JSXML *) JS_GetPrivate(cx, attrobj);
4372                         if (JSXML_LENGTH(attr) != 0)
4373                             goto out;
4375                         kid = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4376                     } else {
4377                         /* 2(c)(v). */
4378                         kid = js_NewXML(cx, JSXML_CLASS_ELEMENT);
4379                     }
4380                     if (!kid)
4381                         goto bad;
4383                     /* An important bit of 2(c)(ii). */
4384                     kid->name = targetprop;
4385                 }
4387                 /* Final important bit of 2(c)(ii). */
4388                 kid->parent = rxml;
4390                 /* 2(c)(vi-vii). */
4391                 i = xml->xml_kids.length;
4392                 if (kid->xml_class != JSXML_CLASS_ATTRIBUTE) {
4393                     /*
4394                      * 2(c)(vii)(1) tests whether _y.[[Parent]]_ is not null.
4395                      * y.[[Parent]] is here called kid->parent, which we know
4396                      * from 2(c)(ii) is _r_, here called rxml.  So let's just
4397                      * test that!  Erratum, the spec should be simpler here.
4398                      */
4399                     if (rxml) {
4400                         JS_ASSERT(JSXML_HAS_KIDS(rxml));
4401                         n = rxml->xml_kids.length;
4402                         j = n - 1;
4403                         if (n != 0 && i != 0) {
4404                             for (n = j, j = 0; j < n; j++) {
4405                                 if (rxml->xml_kids.vector[j] ==
4406                                     xml->xml_kids.vector[i-1]) {
4407                                     break;
4408                                 }
4409                             }
4410                         }
4412                         kidobj = js_GetXMLObject(cx, kid);
4413                         if (!kidobj)
4414                             goto bad;
4415                         ok = Insert(cx, rxml, j + 1, OBJECT_TO_JSVAL(kidobj));
4416                         if (!ok)
4417                             goto out;
4418                     }
4420                     /*
4421                      * 2(c)(vii)(2-3).
4422                      * Erratum: [[PropertyName]] in 2(c)(vii)(3) must be a
4423                      * typo for [[TargetProperty]].
4424                      */
4425                     if (vxml) {
4426                         kid->name = (vxml->xml_class == JSXML_CLASS_LIST)
4427                                     ? vxml->xml_targetprop
4428                                     : vxml->name;
4429                     }
4430                 }
4432                 /* 2(c)(viii). */
4433                 ok = Append(cx, xml, kid);
4434                 if (!ok)
4435                     goto out;
4436             }
4438             /* 2(d). */
4439             if (!vxml ||
4440                 vxml->xml_class == JSXML_CLASS_TEXT ||
4441                 vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4442                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4443                 if (!ok)
4444                     goto out;
4445                 roots[VAL_ROOT] = *vp;
4446             }
4448             /* 2(e). */
4449             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
4450             if (!kid)
4451                 goto out;
4452             parent = kid->parent;
4453             if (kid->xml_class == JSXML_CLASS_ATTRIBUTE) {
4454                 nameobj = js_GetAttributeNameObject(cx, kid->name);
4455                 if (!nameobj)
4456                     goto bad;
4457                 id = OBJECT_TO_JSVAL(nameobj);
4459                 if (parent) {
4460                     /* 2(e)(i). */
4461                     parentobj = parent->object;
4462                     ok = PutProperty(cx, parentobj, id, vp);
4463                     if (!ok)
4464                         goto out;
4466                     /* 2(e)(ii). */
4467                     ok = GetProperty(cx, parentobj, id, vp);
4468                     if (!ok)
4469                         goto out;
4470                     attr = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(*vp));
4472                     /* 2(e)(iii). */
4473                     xml->xml_kids.vector[i] = attr->xml_kids.vector[0];
4474                 }
4475             }
4477             /* 2(f). */
4478             else if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4479                 /* 2(f)(i) Create a shallow copy _c_ of _V_. */
4480                 copyobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
4481                 if (!copyobj)
4482                     goto bad;
4483                 copy = (JSXML *) JS_GetPrivate(cx, copyobj);
4484                 n = vxml->xml_kids.length;
4485                 ok = XMLArraySetCapacity(cx, &copy->xml_kids, n);
4486                 if (!ok)
4487                     goto out;
4488                 for (k = 0; k < n; k++) {
4489                     kid2 = XMLARRAY_MEMBER(&vxml->xml_kids, k, JSXML);
4490                     XMLARRAY_SET_MEMBER(&copy->xml_kids, k, kid2);
4491                 }
4493                 JS_ASSERT(parent != xml);
4494                 if (parent) {
4495                     q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4496                     JS_ASSERT(q != XML_NOT_FOUND);
4498                     ok = IndexToIdVal(cx, q, &id);
4499                     if (!ok)
4500                         goto out;
4501                     ok = Replace(cx, parent, id, OBJECT_TO_JSVAL(copyobj));
4502                     if (!ok)
4503                         goto out;
4505 #ifdef DEBUG
4506                     /* Erratum: this loop in the spec is useless. */
4507                     for (j = 0, n = copy->xml_kids.length; j < n; j++) {
4508                         kid2 = XMLARRAY_MEMBER(&parent->xml_kids, q + j, JSXML);
4509                         JS_ASSERT(XMLARRAY_MEMBER(&copy->xml_kids, j, JSXML)
4510                                   == kid2);
4511                     }
4512 #endif
4513                 }
4515                 /*
4516                  * 2(f)(iv-vi).
4517                  * Erratum: notice the unhandled zero-length V basis case and
4518                  * the off-by-one errors for the n != 0 cases in the spec.
4519                  */
4520                 if (n == 0) {
4521                     XMLArrayDelete(cx, &xml->xml_kids, i, JS_TRUE);
4522                 } else {
4523                     ok = XMLArrayInsert(cx, &xml->xml_kids, i + 1, n - 1);
4524                     if (!ok)
4525                         goto out;
4527                     for (j = 0; j < n; j++)
4528                         xml->xml_kids.vector[i + j] = copy->xml_kids.vector[j];
4529                 }
4530             }
4532             /* 2(g). */
4533             else if (vxml || JSXML_HAS_VALUE(kid)) {
4534                 if (parent) {
4535                     q = XMLARRAY_FIND_MEMBER(&parent->xml_kids, kid, NULL);
4536                     JS_ASSERT(q != XML_NOT_FOUND);
4538                     ok = IndexToIdVal(cx, q, &id);
4539                     if (!ok)
4540                         goto out;
4541                     ok = Replace(cx, parent, id, *vp);
4542                     if (!ok)
4543                         goto out;
4545                     vxml = XMLARRAY_MEMBER(&parent->xml_kids, q, JSXML);
4546                     if (!vxml)
4547                         goto out;
4548                     roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vxml->object);
4549                 }
4551                 /*
4552                  * 2(g)(iii).
4553                  * Erratum: _V_ may not be of type XML, but all index-named
4554                  * properties _x[i]_ in an XMLList _x_ must be of type XML,
4555                  * according to 9.2.1.1 Overview and other places in the spec.
4556                  *
4557                  * Thanks to 2(d), we know _V_ (*vp here) is either a string
4558                  * or an XML/XMLList object.  If *vp is a string, call ToXML
4559                  * on it to satisfy the constraint.
4560                  */
4561                 if (!vxml) {
4562                     JS_ASSERT(JSVAL_IS_STRING(*vp));
4563                     vobj = ToXML(cx, *vp);
4564                     if (!vobj)
4565                         goto bad;
4566                     roots[VAL_ROOT] = *vp = OBJECT_TO_JSVAL(vobj);
4567                     vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4568                 }
4569                 XMLARRAY_SET_MEMBER(&xml->xml_kids, i, vxml);
4570             }
4572             /* 2(h). */
4573             else {
4574                 kidobj = js_GetXMLObject(cx, kid);
4575                 if (!kidobj)
4576                     goto bad;
4577                 id = ATOM_KEY(cx->runtime->atomState.starAtom);
4578                 ok = PutProperty(cx, kidobj, id, vp);
4579                 if (!ok)
4580                     goto out;
4581             }
4582         } else {
4583             /*
4584              * 3.
4585              * Erratum: if x.[[Length]] > 1 or [[ResolveValue]] returns null
4586              * or an r with r.[[Length]] != 1, throw TypeError.
4587              */
4588             n = JSXML_LENGTH(xml);
4589             if (n > 1)
4590                 goto type_error;
4591             if (n == 0) {
4592                 ok = ResolveValue(cx, xml, &rxml);
4593                 if (!ok)
4594                     goto out;
4595                 if (!rxml || JSXML_LENGTH(rxml) != 1)
4596                     goto type_error;
4597                 ok = Append(cx, xml, rxml);
4598                 if (!ok)
4599                     goto out;
4600             }
4601             JS_ASSERT(JSXML_LENGTH(xml) == 1);
4602             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
4603             if (!kid)
4604                 goto out;
4605             kidobj = js_GetXMLObject(cx, kid);
4606             if (!kidobj)
4607                 goto bad;
4608             ok = PutProperty(cx, kidobj, id, vp);
4609             if (!ok)
4610                 goto out;
4611         }
4612     } else {
4613         /*
4614          * ECMA-357 9.1.1.2.
4615          * Erratum: move steps 3 and 4 to before 1 and 2, to avoid wasted
4616          * effort in ToString or [[DeepCopy]].
4617          */
4618         if (js_IdIsIndex(id, &index)) {
4619             /* See NOTE in spec: this variation is reserved for future use. */
4620             ReportBadXMLName(cx, id);
4621             goto bad;
4622         }
4624         nameqn = ToXMLName(cx, id, &funid);
4625         if (!nameqn)
4626             goto bad;
4627         if (funid) {
4628             ok = js_SetProperty(cx, obj, funid, vp);
4629             goto out;
4630         }
4631         nameobj = nameqn->object;
4633         if (JSXML_HAS_VALUE(xml))
4634             goto out;
4636         if (!vxml ||
4637             vxml->xml_class == JSXML_CLASS_TEXT ||
4638             vxml->xml_class == JSXML_CLASS_ATTRIBUTE) {
4639             ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4640             if (!ok)
4641                 goto out;
4642         } else {
4643             rxml = DeepCopyInLRS(cx, vxml, 0);
4644             if (!rxml || !js_GetXMLObject(cx, rxml))
4645                 goto bad;
4646             vxml = rxml;
4647             *vp = OBJECT_TO_JSVAL(vxml->object);
4648         }
4649         roots[VAL_ROOT] = *vp;
4651         /*
4652          * 6.
4653          * Erratum: why is this done here, so early? use is way later....
4654          */
4655         ok = js_GetDefaultXMLNamespace(cx, &nsval);
4656         if (!ok)
4657             goto out;
4659         if (OBJ_GET_CLASS(cx, nameobj) == &js_AttributeNameClass) {
4660             /* 7(a). */
4661             if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)))
4662                 goto out;
4664             /* 7(b-c). */
4665             if (vxml && vxml->xml_class == JSXML_CLASS_LIST) {
4666                 n = vxml->xml_kids.length;
4667                 if (n == 0) {
4668                     *vp = STRING_TO_JSVAL(cx->runtime->emptyString);
4669                 } else {
4670                     left = KidToString(cx, vxml, 0);
4671                     if (!left)
4672                         goto bad;
4674                     space = ATOM_TO_STRING(cx->runtime->atomState.spaceAtom);
4675                     for (i = 1; i < n; i++) {
4676                         left = js_ConcatStrings(cx, left, space);
4677                         if (!left)
4678                             goto bad;
4679                         right = KidToString(cx, vxml, i);
4680                         if (!right)
4681                             goto bad;
4682                         left = js_ConcatStrings(cx, left, right);
4683                         if (!left)
4684                             goto bad;
4685                     }
4687                     roots[VAL_ROOT] = *vp = STRING_TO_JSVAL(left);
4688                 }
4689             } else {
4690                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4691                 if (!ok)
4692                     goto out;
4693                 roots[VAL_ROOT] = *vp;
4694             }
4696             /* 7(d-e). */
4697             match = NULL;
4698             for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
4699                 attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
4700                 if (!attr)
4701                     continue;
4702                 attrqn = attr->name;
4703                 if (!js_CompareStrings(attrqn->localName, nameqn->localName) &&
4704                     (!nameqn->uri ||
4705                      !js_CompareStrings(attrqn->uri, nameqn->uri))) {
4706                     if (!match) {
4707                         match = attr;
4708                     } else {
4709                         nameobj = js_GetAttributeNameObject(cx, attrqn);
4710                         if (!nameobj)
4711                             goto bad;
4713                         id = OBJECT_TO_JSVAL(nameobj);
4714                         ok = DeleteProperty(cx, obj, id, &junk);
4715                         if (!ok)
4716                             goto out;
4717                         --i;
4718                     }
4719                 }
4720             }
4722             /* 7(f). */
4723             attr = match;
4724             if (!attr) {
4725                 /* 7(f)(i-ii). */
4726                 if (!nameqn->uri) {
4727                     left = right = cx->runtime->emptyString;
4728                 } else {
4729                     left = nameqn->uri;
4730                     right = nameqn->prefix;
4731                 }
4732                 nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
4733                 if (!nameqn)
4734                     goto bad;
4736                 /* 7(f)(iii). */
4737                 attr = js_NewXML(cx, JSXML_CLASS_ATTRIBUTE);
4738                 if (!attr)
4739                     goto bad;
4740                 attr->parent = xml;
4741                 attr->name = nameqn;
4743                 /* 7(f)(iv). */
4744                 ok = XMLARRAY_ADD_MEMBER(cx, &xml->xml_attrs, n, attr);
4745                 if (!ok)
4746                     goto out;
4748                 /* 7(f)(v-vi). */
4749                 ns = GetNamespace(cx, nameqn, NULL);
4750                 if (!ns)
4751                     goto bad;
4752                 ok = AddInScopeNamespace(cx, xml, ns);
4753                 if (!ok)
4754                     goto out;
4755             }
4757             /* 7(g). */
4758             attr->xml_value = JSVAL_TO_STRING(*vp);
4759             goto out;
4760         }
4762         /* 8-9. */
4763         if (!js_IsXMLName(cx, OBJECT_TO_JSVAL(nameobj)) &&
4764             !IS_STAR(nameqn->localName)) {
4765             goto out;
4766         }
4768         /* 10-11. */
4769         id = JSVAL_VOID;
4770         primitiveAssign = !vxml && !IS_STAR(nameqn->localName);
4772         /* 12. */
4773         k = n = xml->xml_kids.length;
4774         kid2 = NULL;
4775         while (k != 0) {
4776             --k;
4777             kid = XMLARRAY_MEMBER(&xml->xml_kids, k, JSXML);
4778             if (kid && MatchElemName(nameqn, kid)) {
4779                 if (!JSVAL_IS_VOID(id)) {
4780                     ok = DeleteByIndex(cx, xml, id, &junk);
4781                     if (!ok)
4782                         goto out;
4783                 }
4784                 ok = IndexToIdVal(cx, k, &id);
4785                 if (!ok)
4786                     goto out;
4787                 kid2 = kid;
4788             }
4789         }
4791         /*
4792          * Erratum: ECMA-357 specified child insertion inconsistently:
4793          * insertChildBefore and insertChildAfter insert an arbitrary XML
4794          * instance, and therefore can create cycles, but appendChild as
4795          * specified by the "Overview" of 13.4.4.3 calls [[DeepCopy]] on
4796          * its argument.  But the "Semantics" in 13.4.4.3 do not include
4797          * any [[DeepCopy]] call.
4798          *
4799          * Fixing this (https://bugzilla.mozilla.org/show_bug.cgi?id=312692)
4800          * required adding cycle detection, and allowing duplicate kids to
4801          * be created (see comment 6 in the bug).  Allowing duplicate kid
4802          * references means the loop above will delete all but the lowest
4803          * indexed reference, and each [[DeleteByIndex]] nulls the kid's
4804          * parent.  Thus the need to restore parent here.  This is covered
4805          * by https://bugzilla.mozilla.org/show_bug.cgi?id=327564.
4806          */
4807         if (kid2) {
4808             JS_ASSERT(kid2->parent == xml || !kid2->parent);
4809             if (!kid2->parent)
4810                 kid2->parent = xml;
4811         }
4813         /* 13. */
4814         if (JSVAL_IS_VOID(id)) {
4815             /* 13(a). */
4816             ok = IndexToIdVal(cx, n, &id);
4817             if (!ok)
4818                 goto out;
4820             /* 13(b). */
4821             if (primitiveAssign) {
4822                 if (!nameqn->uri) {
4823                     ns = (JSXMLNamespace *)
4824                          JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
4825                     left = ns->uri;
4826                     right = ns->prefix;
4827                 } else {
4828                     left = nameqn->uri;
4829                     right = nameqn->prefix;
4830                 }
4831                 nameqn = js_NewXMLQName(cx, left, right, nameqn->localName);
4832                 if (!nameqn)
4833                     goto bad;
4835                 /* 13(b)(iii). */
4836                 vobj = js_NewXMLObject(cx, JSXML_CLASS_ELEMENT);
4837                 if (!vobj)
4838                     goto bad;
4839                 vxml = (JSXML *) JS_GetPrivate(cx, vobj);
4840                 vxml->parent = xml;
4841                 vxml->name = nameqn;
4843                 /* 13(b)(iv-vi). */
4844                 ns = GetNamespace(cx, nameqn, NULL);
4845                 if (!ns)
4846                     goto bad;
4847                 ok = Replace(cx, xml, id, OBJECT_TO_JSVAL(vobj));
4848                 if (!ok)
4849                     goto out;
4850                 ok = AddInScopeNamespace(cx, vxml, ns);
4851                 if (!ok)
4852                     goto out;
4853             }
4854         }
4856         /* 14. */
4857         if (primitiveAssign) {
4858             JSXMLArrayCursor cursor;
4860             js_IdIsIndex(id, &index);
4861             XMLArrayCursorInit(&cursor, &xml->xml_kids);
4862             cursor.index = index;
4863             kid = (JSXML *) XMLArrayCursorItem(&cursor);
4864             if (JSXML_HAS_KIDS(kid)) {
4865                 XMLArrayFinish(cx, &kid->xml_kids);
4866                 ok = XMLArrayInit(cx, &kid->xml_kids, 1);
4867             }
4869             /* 14(b-c). */
4870             /* XXXbe Erratum? redundant w.r.t. 7(b-c) else clause above */
4871             if (ok) {
4872                 ok = JS_ConvertValue(cx, *vp, JSTYPE_STRING, vp);
4873                 if (ok && !IS_EMPTY(JSVAL_TO_STRING(*vp))) {
4874                     roots[VAL_ROOT] = *vp;
4875                     if ((JSXML *) XMLArrayCursorItem(&cursor) == kid)
4876                         ok = Replace(cx, kid, JSVAL_ZERO, *vp);
4877                 }
4878             }
4879             XMLArrayCursorFinish(&cursor);
4880         } else {
4881             /* 15(a). */
4882             ok = Replace(cx, xml, id, *vp);
4883         }
4884     }
4886 out:
4887     JS_POP_TEMP_ROOT(cx, &tvr);
4888     JS_LeaveLocalRootScope(cx);
4889     return ok;
4891 type_error:
4892     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4893                          JSMSG_BAD_XMLLIST_PUT,
4894                          js_ValueToPrintableString(cx, id));
4895 bad:
4896     ok = JS_FALSE;
4897     goto out;
4900 /* ECMA-357 9.1.1.10 XML [[ResolveValue]], 9.2.1.10 XMLList [[ResolveValue]]. */
4901 static JSBool
4902 ResolveValue(JSContext *cx, JSXML *list, JSXML **result)
4904     JSXML *target, *base;
4905     JSXMLQName *targetprop;
4906     JSObject *targetpropobj;
4907     jsval id, tv;
4909     /* Our caller must be protecting newborn objects. */
4910     JS_ASSERT(cx->localRootStack);
4912     if (list->xml_class != JSXML_CLASS_LIST || list->xml_kids.length != 0) {
4913         if (!js_GetXMLObject(cx, list))
4914             return JS_FALSE;
4915         *result = list;
4916         return JS_TRUE;
4917     }
4919     target = list->xml_target;
4920     targetprop = list->xml_targetprop;
4921     if (!target || !targetprop || IS_STAR(targetprop->localName)) {
4922         *result = NULL;
4923         return JS_TRUE;
4924     }
4926     targetpropobj = js_GetXMLQNameObject(cx, targetprop);
4927     if (!targetpropobj)
4928         return JS_FALSE;
4929     if (OBJ_GET_CLASS(cx, targetpropobj) == &js_AttributeNameClass) {
4930         *result = NULL;
4931         return JS_TRUE;
4932     }
4934     if (!ResolveValue(cx, target, &base))
4935         return JS_FALSE;
4936     if (!base) {
4937         *result = NULL;
4938         return JS_TRUE;
4939     }
4940     if (!js_GetXMLObject(cx, base))
4941         return JS_FALSE;
4943     id = OBJECT_TO_JSVAL(targetpropobj);
4944     if (!GetProperty(cx, base->object, id, &tv))
4945         return JS_FALSE;
4946     target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
4948     if (JSXML_LENGTH(target) == 0) {
4949         if (base->xml_class == JSXML_CLASS_LIST && JSXML_LENGTH(base) > 1) {
4950             *result = NULL;
4951             return JS_TRUE;
4952         }
4953         tv = STRING_TO_JSVAL(cx->runtime->emptyString);
4954         if (!PutProperty(cx, base->object, id, &tv))
4955             return JS_FALSE;
4956         if (!GetProperty(cx, base->object, id, &tv))
4957             return JS_FALSE;
4958         target = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(tv));
4959     }
4961     *result = target;
4962     return JS_TRUE;
4965 /*
4966  * HasProperty must be able to return a found JSProperty and the object in
4967  * which it was found, if id is of the form function::name.  For other ids,
4968  * if they index or name an XML child, we return FOUND_XML_PROPERTY in *propp
4969  * and null in *objp.
4970  *
4971  * DROP_PROPERTY helps HasProperty callers drop function properties without
4972  * trying to drop the magic FOUND_XML_PROPERTY cookie.
4973  */
4974 #define FOUND_XML_PROPERTY              ((JSProperty *) 1)
4975 #define DROP_PROPERTY(cx,pobj,prop)     (((prop) != FOUND_XML_PROPERTY)       \
4976                                          ? OBJ_DROP_PROPERTY(cx, pobj, prop)  \
4977                                          : (void) 0)
4979 /* ECMA-357 9.1.1.6 XML [[HasProperty]] and 9.2.1.5 XMLList [[HasProperty]]. */
4980 static JSBool
4981 HasProperty(JSContext *cx, JSObject *obj, jsval id, JSObject **objp,
4982             JSProperty **propp)
4984     JSXML *xml, *kid;
4985     JSXMLArrayCursor cursor;
4986     JSObject *kidobj;
4987     JSXMLQName *qn;
4988     jsid funid;
4989     JSXMLArray *array;
4990     JSXMLNameMatcher matcher;
4991     uint32 i, n;
4993     *objp = NULL;
4994     *propp = NULL;
4996     xml = (JSXML *) JS_GetPrivate(cx, obj);
4997     if (xml->xml_class == JSXML_CLASS_LIST) {
4998         n = JSXML_LENGTH(xml);
4999         if (js_IdIsIndex(id, &i)) {
5000             if (i < n)
5001                 *propp = FOUND_XML_PROPERTY;
5002             return JS_TRUE;
5003         }
5005         XMLArrayCursorInit(&cursor, &xml->xml_kids);
5006         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5007             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
5008                 kidobj = js_GetXMLObject(cx, kid);
5009                 if (!kidobj || !HasProperty(cx, kidobj, id, objp, propp))
5010                     break;
5011                 if (*propp)
5012                     break;
5013             }
5014         }
5015         XMLArrayCursorFinish(&cursor);
5016         if (kid)
5017             return *propp != NULL;
5018     } else {
5019         if (xml->xml_class == JSXML_CLASS_ELEMENT && js_IdIsIndex(id, &i)) {
5020             if (i == 0)
5021                 *propp = FOUND_XML_PROPERTY;
5022             return JS_TRUE;
5023         }
5025         qn = ToXMLName(cx, id, &funid);
5026         if (!qn)
5027             return JS_FALSE;
5028         if (funid)
5029             return js_LookupProperty(cx, obj, funid, objp, propp);
5031         if (xml->xml_class != JSXML_CLASS_ELEMENT)
5032             return JS_TRUE;
5034         if (OBJ_GET_CLASS(cx, qn->object) == &js_AttributeNameClass) {
5035             array = &xml->xml_attrs;
5036             matcher = MatchAttrName;
5037         } else {
5038             array = &xml->xml_kids;
5039             matcher = MatchElemName;
5040         }
5041         for (i = 0, n = array->length; i < n; i++) {
5042             kid = XMLARRAY_MEMBER(array, i, JSXML);
5043             if (kid && matcher(qn, kid)) {
5044                 *propp = FOUND_XML_PROPERTY;
5045                 return JS_TRUE;
5046             }
5047         }
5048     }
5050     return JS_TRUE;
5053 static void
5054 xml_finalize(JSContext *cx, JSObject *obj)
5056     JSXML *xml;
5058     xml = (JSXML *) JS_GetPrivate(cx, obj);
5059     if (!xml)
5060         return;
5061     if (xml->object == obj)
5062         xml->object = NULL;
5063     UNMETER(xml_stats.livexmlobj);
5066 static void
5067 xml_mark_vector(JSContext *cx, JSXML **vec, uint32 len, void *arg)
5069     uint32 i;
5070     JSXML *elt;
5072     for (i = 0; i < len; i++) {
5073         elt = vec[i];
5074         {
5075 #ifdef GC_MARK_DEBUG
5076             char buf[120];
5078             if (elt->xml_class == JSXML_CLASS_LIST) {
5079                 strcpy(buf, js_XMLList_str);
5080             } else if (JSXML_HAS_NAME(elt)) {
5081                 JSXMLQName *qn = elt->name;
5083                 JS_snprintf(buf, sizeof buf, "%s::%s",
5084                             qn->uri ? JS_GetStringBytes(qn->uri) : "*",
5085                             JS_GetStringBytes(qn->localName));
5086             } else {
5087                 JSString *str = elt->xml_value;
5088                 size_t srclen = JSSTRING_LENGTH(str);
5089                 size_t dstlen = sizeof buf;
5091                 if (srclen >= sizeof buf / 6)
5092                     srclen = sizeof buf / 6 - 1;
5093                 js_DeflateStringToBuffer(cx, JSSTRING_CHARS(str), srclen,
5094                                          buf, &dstlen);
5095             }
5096 #else
5097             const char *buf = NULL;
5098 #endif
5099             JS_MarkGCThing(cx, elt, buf, arg);
5100         }
5101     }
5104 /*
5105  * js_XMLObjectOps.newObjectMap == js_NewObjectMap, so XML objects appear to
5106  * be native.  Therefore, xml_lookupProperty must return a valid JSProperty
5107  * pointer parameter via *propp to signify "property found".  Since the only
5108  * call to xml_lookupProperty is via OBJ_LOOKUP_PROPERTY, and then only from
5109  * js_FindXMLProperty (in this file) and js_FindProperty (in jsobj.c, called
5110  * from jsinterp.c), the only time we add a JSScopeProperty here is when an
5111  * unqualified name or XML name is being accessed.
5112  *
5113  * This scope property both speeds up subsequent js_Find*Property calls, and
5114  * keeps the JSOP_NAME code in js_Interpret happy by giving it an sprop with
5115  * (getter, setter) == (GetProperty, PutProperty).  We can't use that getter
5116  * and setter as js_XMLClass's getProperty and setProperty, because doing so
5117  * would break the XML methods, which are function-valued properties of the
5118  * XML.prototype object.
5119  *
5120  * NB: xml_deleteProperty must take care to remove any property added here.
5121  */
5122 static JSBool
5123 xml_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
5124                    JSProperty **propp)
5126     JSScopeProperty *sprop;
5128     if (!HasProperty(cx, obj, ID_TO_VALUE(id), objp, propp))
5129         return JS_FALSE;
5131     if (*propp == FOUND_XML_PROPERTY) {
5132         sprop = js_AddNativeProperty(cx, obj, id, GetProperty, PutProperty,
5133                                      SPROP_INVALID_SLOT, JSPROP_ENUMERATE,
5134                                      0, 0);
5135         if (!sprop)
5136             return JS_FALSE;
5138         JS_LOCK_OBJ(cx, obj);
5139         *objp = obj;
5140         *propp = (JSProperty *) sprop;
5141     }
5142     return JS_TRUE;
5145 static JSBool
5146 xml_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
5147                    JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
5148                    JSProperty **propp)
5150     if (JSVAL_IS_FUNCTION(cx, value) || getter || setter ||
5151         (attrs & JSPROP_ENUMERATE) == 0 ||
5152         (attrs & (JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_SHARED))) {
5153         return js_DefineProperty(cx, obj, id, value, getter, setter, attrs,
5154                                  propp);
5155     }
5157     if (!PutProperty(cx, obj, ID_TO_VALUE(id), &value))
5158         return JS_FALSE;
5159     if (propp)
5160         *propp = NULL;
5161     return JS_TRUE;
5164 static JSBool
5165 xml_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5167     if (id == JS_DEFAULT_XML_NAMESPACE_ID) {
5168         *vp = JSVAL_VOID;
5169         return JS_TRUE;
5170     }
5172     return GetProperty(cx, obj, ID_TO_VALUE(id), vp);
5175 static JSBool
5176 xml_setProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5178     return PutProperty(cx, obj, ID_TO_VALUE(id), vp);
5181 static JSBool
5182 FoundProperty(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5183               JSBool *foundp)
5185     JSObject *pobj;
5187     if (prop) {
5188         *foundp = JS_TRUE;
5189     } else {
5190         if (!HasProperty(cx, obj, ID_TO_VALUE(id), &pobj, &prop))
5191             return JS_FALSE;
5192         if (prop)
5193             DROP_PROPERTY(cx, pobj, prop);
5194         *foundp = (prop != NULL);
5195     }
5196     return JS_TRUE;
5199 static JSBool
5200 xml_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5201                   uintN *attrsp)
5203     JSBool found;
5205     if (!FoundProperty(cx, obj, id, prop, &found))
5206         return JS_FALSE;
5207     *attrsp = found ? JSPROP_ENUMERATE : 0;
5208     return JS_TRUE;
5211 static JSBool
5212 xml_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
5213                   uintN *attrsp)
5215     JSBool found;
5217     if (!FoundProperty(cx, obj, id, prop, &found))
5218         return JS_FALSE;
5219     if (found) {
5220         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
5221                              JSMSG_CANT_SET_XML_ATTRS);
5222     }
5223     return !found;
5226 static JSBool
5227 xml_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
5229     /*
5230      * If this object has its own (mutable) scope, and if id isn't an index,
5231      * then we may have added a property to the scope in xml_lookupProperty
5232      * for it to return to mean "found" and to provide a handle for access
5233      * operations to call the property's getter or setter.  The property also
5234      * helps speed up unqualified accesses via the property cache, avoiding
5235      * what amount to two HasProperty searches.
5236      *
5237      * But now it's time to remove any such property, to purge the property
5238      * cache and remove the scope entry.
5239      */
5240     if (OBJ_SCOPE(obj)->object == obj && !JSID_IS_INT(id)) {
5241         if (!js_DeleteProperty(cx, obj, id, rval))
5242             return JS_FALSE;
5243     }
5245     return DeleteProperty(cx, obj, ID_TO_VALUE(id), rval);
5248 static JSBool
5249 xml_defaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
5251     JSXML *xml;
5253     if (hint == JSTYPE_OBJECT) {
5254         /* Called from for..in code in js_Interpret: return an XMLList. */
5255         xml = (JSXML *) JS_GetPrivate(cx, obj);
5256         if (xml->xml_class != JSXML_CLASS_LIST) {
5257             obj = ToXMLList(cx, OBJECT_TO_JSVAL(obj));
5258             if (!obj)
5259                 return JS_FALSE;
5260         }
5261         *vp = OBJECT_TO_JSVAL(obj);
5262         return JS_TRUE;
5263     }
5265     return JS_CallFunctionName(cx, obj, js_toString_str, 0, NULL, vp);
5268 static JSBool
5269 xml_enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
5270               jsval *statep, jsid *idp)
5272     JSXML *xml;
5273     uint32 length, index;
5274     JSXMLArrayCursor *cursor;
5276     xml = (JSXML *) JS_GetPrivate(cx, obj);
5277     length = JSXML_LENGTH(xml);
5279     switch (enum_op) {
5280       case JSENUMERATE_INIT:
5281         if (length == 0) {
5282             cursor = NULL;
5283         } else {
5284             cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
5285             if (!cursor)
5286                 return JS_FALSE;
5287             XMLArrayCursorInit(cursor, &xml->xml_kids);
5288         }
5289         *statep = PRIVATE_TO_JSVAL(cursor);
5290         if (idp)
5291             *idp = INT_TO_JSID(length);
5292         break;
5294       case JSENUMERATE_NEXT:
5295         cursor = JSVAL_TO_PRIVATE(*statep);
5296         if (cursor && cursor->array && (index = cursor->index) < length) {
5297             *idp = INT_TO_JSID(index);
5298             cursor->index = index + 1;
5299             break;
5300         }
5301         /* FALL THROUGH */
5303       case JSENUMERATE_DESTROY:
5304         cursor = JSVAL_TO_PRIVATE(*statep);
5305         if (cursor) {
5306             XMLArrayCursorFinish(cursor);
5307             JS_free(cx, cursor);
5308         }
5309         *statep = JSVAL_NULL;
5310         break;
5311     }
5312     return JS_TRUE;
5315 static JSBool
5316 xml_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
5318     return JS_TRUE;
5321 static uint32
5322 xml_mark(JSContext *cx, JSObject *obj, void *arg)
5324     JSXML *xml;
5326     xml = (JSXML *) JS_GetPrivate(cx, obj);
5327     JS_MarkGCThing(cx, xml, js_private_str, arg);
5328     return js_Mark(cx, obj, arg);
5331 static void
5332 xml_clear(JSContext *cx, JSObject *obj)
5336 static JSBool
5337 HasSimpleContent(JSXML *xml)
5339     JSXML *kid;
5340     JSBool simple;
5341     uint32 i, n;
5343 again:
5344     switch (xml->xml_class) {
5345       case JSXML_CLASS_COMMENT:
5346       case JSXML_CLASS_PROCESSING_INSTRUCTION:
5347         return JS_FALSE;
5348       case JSXML_CLASS_LIST:
5349         if (xml->xml_kids.length == 0)
5350             return JS_TRUE;
5351         if (xml->xml_kids.length == 1) {
5352             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5353             if (kid) {
5354                 xml = kid;
5355                 goto again;
5356             }
5357         }
5358         /* FALL THROUGH */
5359       default:
5360         simple = JS_TRUE;
5361         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5362             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5363             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5364                 simple = JS_FALSE;
5365                 break;
5366             }
5367         }
5368         return simple;
5369     }
5372 /*
5373  * 11.2.2.1 Step 3(d) onward.
5374  */
5375 static JSObject *
5376 xml_getMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5378     JSXML *xml;
5379     JSTempValueRooter tvr;
5380     jsval roots[2];
5381     enum {
5382         FUN_ROOT = 0,
5383         OBJ_ROOT = 1
5384     };
5386     JS_ASSERT(JS_InstanceOf(cx, obj, &js_XMLClass, NULL));
5387     xml = (JSXML *) JS_GetPrivate(cx, obj);
5388     memset(roots, 0, sizeof(roots));
5389     JS_PUSH_TEMP_ROOT(cx, sizeof roots / sizeof *roots, roots, &tvr);
5391     /* From this point the control must flow through out: or bad: */
5392   retry:
5393     if (!GetFunction(cx, obj, xml, id, &roots[FUN_ROOT]))
5394         goto bad;
5395     if (JSVAL_IS_VOID(roots[FUN_ROOT]) && OBJECT_IS_XML(cx, obj)) {
5396         if (xml->xml_class == JSXML_CLASS_LIST) {
5397             if (xml->xml_kids.length == 1) {
5398                 xml = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
5399                 if (xml) {
5400                     obj = js_GetXMLObject(cx, xml);
5401                     if (!obj)
5402                         goto bad;
5403                     roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
5404                     goto retry;
5405                 }
5406             }
5407         } else if (HasSimpleContent(xml)) {
5408             JSString *str;
5410             str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5411             if (!str)
5412                 goto bad;
5413             if (!js_ValueToObject(cx, STRING_TO_JSVAL(str), &obj))
5414                 goto bad;
5415             roots[OBJ_ROOT] = OBJECT_TO_JSVAL(obj);
5416             if (!js_GetProperty(cx, obj, id, &roots[FUN_ROOT]))
5417                 goto bad;
5418         }
5419     }
5420   out:
5421     *vp = roots[FUN_ROOT];
5422     if (obj) {
5423         /*
5424          * If we just POP tvr, then it is possible that nothing roots obj, see
5425          * bug 353165. To allow our callers to assume at least weakly rooting
5426          * of the result, we root obj via newborn array. Similarly we root the
5427          * value of roots[FUNCTION] since getMethod callers have a bad habit
5428          * of passing a pointer to unrooted local value as vp.
5429          */
5430         cx->newborn[GCX_OBJECT] = (JSGCThing *)obj;
5431         cx->lastInternalResult = roots[FUN_ROOT];
5432     }
5433     JS_POP_TEMP_ROOT(cx, &tvr);
5434     return obj;
5435   bad:
5436     obj = NULL;
5437     goto out;
5440 static JSBool
5441 xml_setMethod(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
5443     return js_SetProperty(cx, obj, id, vp);
5446 static JSBool
5447 xml_enumerateValues(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
5448                     jsval *statep, jsid *idp, jsval *vp)
5450     JSXML *xml, *kid;
5451     uint32 length, index;
5452     JSXMLArrayCursor *cursor;
5453     JSObject *kidobj;
5455     xml = (JSXML *) JS_GetPrivate(cx, obj);
5456     length = JSXML_LENGTH(xml);
5457     JS_ASSERT(INT_FITS_IN_JSVAL(length));
5459     switch (enum_op) {
5460       case JSENUMERATE_INIT:
5461         if (length == 0) {
5462             cursor = NULL;
5463         } else {
5464             cursor = (JSXMLArrayCursor *) JS_malloc(cx, sizeof *cursor);
5465             if (!cursor)
5466                 return JS_FALSE;
5467             XMLArrayCursorInit(cursor, &xml->xml_kids);
5468         }
5469         *statep = PRIVATE_TO_JSVAL(cursor);
5470         if (idp)
5471             *idp = INT_TO_JSID(length);
5472         if (vp)
5473             *vp = JSVAL_VOID;
5474         break;
5476       case JSENUMERATE_NEXT:
5477         cursor = JSVAL_TO_PRIVATE(*statep);
5478         if (cursor && cursor->array && (index = cursor->index) < length) {
5479             while (!(kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML))) {
5480                 if (++index == length)
5481                     goto destroy;
5482             }
5483             kidobj = js_GetXMLObject(cx, kid);
5484             if (!kidobj)
5485                 return JS_FALSE;
5486             JS_ASSERT(INT_FITS_IN_JSVAL(index));
5487             *idp = INT_TO_JSID(index);
5488             *vp = OBJECT_TO_JSVAL(kidobj);
5489             cursor->index = index + 1;
5490             break;
5491         }
5492         /* FALL THROUGH */
5494       case JSENUMERATE_DESTROY:
5495         cursor = JSVAL_TO_PRIVATE(*statep);
5496         if (cursor) {
5497       destroy:
5498             XMLArrayCursorFinish(cursor);
5499             JS_free(cx, cursor);
5500         }
5501         *statep = JSVAL_NULL;
5502         break;
5503     }
5504     return JS_TRUE;
5507 static JSBool
5508 xml_equality(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
5510     JSXML *xml, *vxml;
5511     JSObject *vobj;
5512     JSBool ok;
5513     JSString *str, *vstr;
5514     jsdouble d, d2;
5516     xml = (JSXML *) JS_GetPrivate(cx, obj);
5517     vxml = NULL;
5518     if (!JSVAL_IS_PRIMITIVE(v)) {
5519         vobj = JSVAL_TO_OBJECT(v);
5520         if (OBJECT_IS_XML(cx, vobj))
5521             vxml = (JSXML *) JS_GetPrivate(cx, vobj);
5522     }
5524     if (xml->xml_class == JSXML_CLASS_LIST) {
5525         ok = Equals(cx, xml, v, bp);
5526     } else if (vxml) {
5527         if (vxml->xml_class == JSXML_CLASS_LIST) {
5528             ok = Equals(cx, vxml, OBJECT_TO_JSVAL(obj), bp);
5529         } else {
5530             if (((xml->xml_class == JSXML_CLASS_TEXT ||
5531                   xml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5532                  HasSimpleContent(vxml)) ||
5533                 ((vxml->xml_class == JSXML_CLASS_TEXT ||
5534                   vxml->xml_class == JSXML_CLASS_ATTRIBUTE) &&
5535                  HasSimpleContent(xml))) {
5536                 ok = JS_EnterLocalRootScope(cx);
5537                 if (ok) {
5538                     str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5539                     vstr = js_ValueToString(cx, v);
5540                     ok = str && vstr;
5541                     if (ok)
5542                         *bp = !js_CompareStrings(str, vstr);
5543                     JS_LeaveLocalRootScope(cx);
5544                 }
5545             } else {
5546                 ok = XMLEquals(cx, xml, vxml, bp);
5547             }
5548         }
5549     } else {
5550         ok = JS_EnterLocalRootScope(cx);
5551         if (ok) {
5552             if (HasSimpleContent(xml)) {
5553                 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5554                 vstr = js_ValueToString(cx, v);
5555                 ok = str && vstr;
5556                 if (ok)
5557                     *bp = !js_CompareStrings(str, vstr);
5558             } else if (JSVAL_IS_STRING(v) || JSVAL_IS_NUMBER(v)) {
5559                 str = js_ValueToString(cx, OBJECT_TO_JSVAL(obj));
5560                 if (!str) {
5561                     ok = JS_FALSE;
5562                 } else if (JSVAL_IS_STRING(v)) {
5563                     *bp = !js_CompareStrings(str, JSVAL_TO_STRING(v));
5564                 } else {
5565                     ok = js_ValueToNumber(cx, STRING_TO_JSVAL(str), &d);
5566                     if (ok) {
5567                         d2 = JSVAL_IS_INT(v) ? JSVAL_TO_INT(v)
5568                                              : *JSVAL_TO_DOUBLE(v);
5569                         *bp = JSDOUBLE_COMPARE(d, ==, d2, JS_FALSE);
5570                     }
5571                 }
5572             } else {
5573                 *bp = JS_FALSE;
5574             }
5575             JS_LeaveLocalRootScope(cx);
5576         }
5577     }
5578     return ok;
5581 static JSBool
5582 xml_concatenate(JSContext *cx, JSObject *obj, jsval v, jsval *vp)
5584     JSBool ok;
5585     JSObject *listobj, *robj;
5586     JSXML *list, *lxml, *rxml;
5588     ok = JS_EnterLocalRootScope(cx);
5589     if (!ok)
5590         return JS_FALSE;
5592     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5593     if (!listobj) {
5594         ok = JS_FALSE;
5595         goto out;
5596     }
5598     list = (JSXML *) JS_GetPrivate(cx, listobj);
5599     lxml = (JSXML *) JS_GetPrivate(cx, obj);
5600     ok = Append(cx, list, lxml);
5601     if (!ok)
5602         goto out;
5604     if (VALUE_IS_XML(cx, v)) {
5605         rxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5606     } else {
5607         robj = ToXML(cx, v);
5608         if (!robj) {
5609             ok = JS_FALSE;
5610             goto out;
5611         }
5612         rxml = (JSXML *) JS_GetPrivate(cx, robj);
5613     }
5614     ok = Append(cx, list, rxml);
5615     if (!ok)
5616         goto out;
5618     *vp = OBJECT_TO_JSVAL(listobj);
5619 out:
5620     JS_LeaveLocalRootScope(cx);
5621     return ok;
5624 /* Use js_NewObjectMap so XML objects satisfy OBJ_IS_NATIVE tests. */
5625 JS_FRIEND_DATA(JSXMLObjectOps) js_XMLObjectOps = {
5626   { js_NewObjectMap,            js_DestroyObjectMap,
5627     xml_lookupProperty,         xml_defineProperty,
5628     xml_getProperty,            xml_setProperty,
5629     xml_getAttributes,          xml_setAttributes,
5630     xml_deleteProperty,         xml_defaultValue,
5631     xml_enumerate,              js_CheckAccess,
5632     NULL,                       NULL,
5633     NULL,                       NULL,
5634     NULL,                       xml_hasInstance,
5635     js_SetProtoOrParent,        js_SetProtoOrParent,
5636     xml_mark,                   xml_clear,
5637     NULL,                       NULL },
5638     xml_getMethod,              xml_setMethod,
5639     xml_enumerateValues,        xml_equality,
5640     xml_concatenate
5641 };
5643 static JSObjectOps *
5644 xml_getObjectOps(JSContext *cx, JSClass *clasp)
5646     return &js_XMLObjectOps.base;
5649 JS_FRIEND_DATA(JSClass) js_XMLClass = {
5650     js_XML_str,        JSCLASS_HAS_PRIVATE,
5651     JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,   JS_PropertyStub,
5652     JS_EnumerateStub,  JS_ResolveStub,    JS_ConvertStub,    xml_finalize,
5653     xml_getObjectOps,  NULL,              NULL,              NULL,
5654     NULL,              NULL,              NULL,              NULL
5655 };
5657 static JSObject *
5658 CallConstructorFunction(JSContext *cx, JSObject *obj, JSClass *clasp,
5659                         uintN argc, jsval *argv)
5661     JSObject *tmp;
5662     jsval rval;
5664     while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL)
5665         obj = tmp;
5666     if (!JS_CallFunctionName(cx, obj, clasp->name, argc, argv, &rval))
5667         return NULL;
5668     JS_ASSERT(!JSVAL_IS_PRIMITIVE(rval));
5669     return JSVAL_TO_OBJECT(rval);
5672 #define XML_METHOD_PROLOG                                                     \
5673     JS_BEGIN_MACRO                                                            \
5674         xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, argv);   \
5675         if (!xml)                                                             \
5676             return JS_FALSE;                                                  \
5677     JS_END_MACRO
5679 static JSBool
5680 xml_addNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5681                  jsval *rval)
5683     JSXML *xml;
5684     JSObject *nsobj;
5685     JSXMLNamespace *ns;
5687     XML_METHOD_PROLOG;
5688     if (xml->xml_class != JSXML_CLASS_ELEMENT)
5689         return JS_TRUE;
5690     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5691     if (!xml)
5692         return JS_FALSE;
5694     nsobj = CallConstructorFunction(cx, obj, &js_NamespaceClass.base, 1, argv);
5695     if (!nsobj)
5696         return JS_FALSE;
5697     argv[0] = OBJECT_TO_JSVAL(nsobj);
5699     ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
5700     if (!AddInScopeNamespace(cx, xml, ns))
5701         return JS_FALSE;
5702     ns->declared = JS_TRUE;
5703     *rval = OBJECT_TO_JSVAL(obj);
5704     return JS_TRUE;
5707 static JSBool
5708 xml_appendChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5709                 jsval *rval)
5711     JSXML *xml, *vxml;
5712     jsval name, v;
5713     JSObject *vobj;
5715     XML_METHOD_PROLOG;
5716     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
5717     if (!xml)
5718         return JS_FALSE;
5720     if (!js_GetAnyName(cx, &name))
5721         return JS_FALSE;
5723     if (!GetProperty(cx, obj, name, &v))
5724         return JS_FALSE;
5726     JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5727     vobj = JSVAL_TO_OBJECT(v);
5728     JS_ASSERT(OBJECT_IS_XML(cx, vobj));
5729     vxml = (JSXML *) JS_GetPrivate(cx, vobj);
5730     JS_ASSERT(vxml->xml_class == JSXML_CLASS_LIST);
5732     if (!IndexToIdVal(cx, vxml->xml_kids.length, &name))
5733         return JS_FALSE;
5734     if (!PutProperty(cx, JSVAL_TO_OBJECT(v), name, &argv[0]))
5735         return JS_FALSE;
5737     *rval = OBJECT_TO_JSVAL(obj);
5738     return JS_TRUE;
5741 /* XML and XMLList */
5742 static JSBool
5743 xml_attribute(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5744               jsval *rval)
5746     JSXMLQName *qn;
5748     qn = ToAttributeName(cx, argv[0]);
5749     if (!qn)
5750         return JS_FALSE;
5751     argv[0] = OBJECT_TO_JSVAL(qn->object);      /* local root */
5752     return GetProperty(cx, obj, argv[0], rval);
5755 /* XML and XMLList */
5756 static JSBool
5757 xml_attributes(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5758                jsval *rval)
5760     jsval name;
5761     JSXMLQName *qn;
5762     JSTempValueRooter tvr;
5763     JSBool ok;
5765     name = ATOM_KEY(cx->runtime->atomState.starAtom);
5766     qn = ToAttributeName(cx, name);
5767     if (!qn)
5768         return JS_FALSE;
5769     name = OBJECT_TO_JSVAL(qn->object);
5770     JS_PUSH_SINGLE_TEMP_ROOT(cx, name, &tvr);
5771     ok = GetProperty(cx, obj, name, rval);
5772     JS_POP_TEMP_ROOT(cx, &tvr);
5773     return ok;
5776 /* XML and XMLList */
5777 static JSBool
5778 xml_child_helper(JSContext *cx, JSObject *obj, JSXML *xml, jsval name,
5779                  jsval *rval)
5781     uint32 index;
5782     JSXML *kid;
5783     JSObject *kidobj;
5785     /* ECMA-357 13.4.4.6 */
5786     JS_ASSERT(xml->xml_class != JSXML_CLASS_LIST);
5788     if (js_IdIsIndex(name, &index)) {
5789         if (index >= JSXML_LENGTH(xml)) {
5790             *rval = JSVAL_VOID;
5791         } else {
5792             kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
5793             if (!kid) {
5794                 *rval = JSVAL_VOID;
5795             } else {
5796                 kidobj = js_GetXMLObject(cx, kid);
5797                 if (!kidobj)
5798                     return JS_FALSE;
5799                 *rval = OBJECT_TO_JSVAL(kidobj);
5800             }
5801         }
5802         return JS_TRUE;
5803     }
5805     return GetProperty(cx, obj, name, rval);
5808 static JSBool
5809 xml_child(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
5811     JSXML *xml, *list, *kid, *vxml;
5812     JSXMLArrayCursor cursor;
5813     jsval name, v;
5814     JSObject *listobj, *kidobj;
5816     XML_METHOD_PROLOG;
5817     name = argv[0];
5818     if (xml->xml_class == JSXML_CLASS_LIST) {
5819         /* ECMA-357 13.5.4.4 */
5820         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5821         if (!listobj)
5822             return JS_FALSE;
5824         *rval = OBJECT_TO_JSVAL(listobj);
5825         list = (JSXML *) JS_GetPrivate(cx, listobj);
5826         list->xml_target = xml;
5828         XMLArrayCursorInit(&cursor, &xml->xml_kids);
5829         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5830             kidobj = js_GetXMLObject(cx, kid);
5831             if (!kidobj)
5832                 break;
5833             if (!xml_child_helper(cx, kidobj, kid, name, &v))
5834                 break;
5835             if (JSVAL_IS_VOID(v)) {
5836                 /* The property didn't exist in this kid. */
5837                 continue;
5838             }
5840             JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
5841             vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5842             if ((!JSXML_HAS_KIDS(vxml) || vxml->xml_kids.length != 0) &&
5843                 !Append(cx, list, vxml)) {
5844                 break;
5845             }
5846         }
5847         XMLArrayCursorFinish(&cursor);
5848         return !kid;
5849     }
5851     return xml_child_helper(cx, obj, xml, name, rval);
5854 static JSBool
5855 xml_childIndex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5856                jsval *rval)
5858     JSXML *xml, *parent;
5859     uint32 i, n;
5861     XML_METHOD_PROLOG;
5862     parent = xml->parent;
5863     if (!parent || xml->xml_class == JSXML_CLASS_ATTRIBUTE) {
5864         *rval = DOUBLE_TO_JSVAL(cx->runtime->jsNaN);
5865         return JS_TRUE;
5866     }
5867     for (i = 0, n = JSXML_LENGTH(parent); i < n; i++) {
5868         if (XMLARRAY_MEMBER(&parent->xml_kids, i, JSXML) == xml)
5869             break;
5870     }
5871     JS_ASSERT(i < n);
5872     return js_NewNumberValue(cx, i, rval);
5875 /* XML and XMLList */
5876 static JSBool
5877 xml_children(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5878              jsval *rval)
5880     jsval name;
5882     name = ATOM_KEY(cx->runtime->atomState.starAtom);
5883     return GetProperty(cx, obj, name, rval);
5886 /* XML and XMLList */
5887 static JSBool
5888 xml_comments(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5889              jsval *rval)
5891     JSXML *xml, *list, *kid, *vxml;
5892     JSObject *listobj, *kidobj;
5893     JSBool ok;
5894     uint32 i, n;
5895     jsval v;
5897     XML_METHOD_PROLOG;
5898     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
5899     if (!listobj)
5900         return JS_FALSE;
5902     *rval = OBJECT_TO_JSVAL(listobj);
5903     list = (JSXML *) JS_GetPrivate(cx, listobj);
5904     list->xml_target = xml;
5906     ok = JS_TRUE;
5908     if (xml->xml_class == JSXML_CLASS_LIST) {
5909         /* 13.5.4.6 Step 2. */
5910         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5911             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5912             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
5913                 ok = JS_EnterLocalRootScope(cx);
5914                 if (!ok)
5915                     break;
5916                 kidobj = js_GetXMLObject(cx, kid);
5917                 ok = kidobj
5918                      ? xml_comments(cx, kidobj, argc, argv, &v)
5919                      : JS_FALSE;
5920                 JS_LeaveLocalRootScope(cx);
5921                 if (!ok)
5922                     break;
5923                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
5924                 if (JSXML_LENGTH(vxml) != 0) {
5925                     ok = Append(cx, list, vxml);
5926                     if (!ok)
5927                         break;
5928                 }
5929             }
5930         }
5931     } else {
5932         /* 13.4.4.9 Step 2. */
5933         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
5934             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
5935             if (kid && kid->xml_class == JSXML_CLASS_COMMENT) {
5936                 ok = Append(cx, list, kid);
5937                 if (!ok)
5938                     break;
5939             }
5940         }
5941     }
5943     return ok;
5946 /* XML and XMLList */
5947 static JSBool
5948 xml_contains(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5949              jsval *rval)
5951     JSXML *xml, *kid;
5952     jsval value;
5953     JSBool eq;
5954     JSXMLArrayCursor cursor;
5955     JSObject *kidobj;
5957     XML_METHOD_PROLOG;
5958     value = argv[0];
5959     if (xml->xml_class == JSXML_CLASS_LIST) {
5960         eq = JS_FALSE;
5961         XMLArrayCursorInit(&cursor, &xml->xml_kids);
5962         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
5963             kidobj = js_GetXMLObject(cx, kid);
5964             if (!kidobj || !xml_equality(cx, kidobj, value, &eq))
5965                 break;
5966             if (eq)
5967                 break;
5968         }
5969         XMLArrayCursorFinish(&cursor);
5970         if (kid)
5971             return JS_FALSE;
5972     } else {
5973         if (!xml_equality(cx, obj, value, &eq))
5974             return JS_FALSE;
5975     }
5976     *rval = BOOLEAN_TO_JSVAL(eq);
5977     return JS_TRUE;
5980 /* XML and XMLList */
5981 static JSBool
5982 xml_copy(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
5984     JSXML *xml, *copy;
5986     XML_METHOD_PROLOG;
5987     copy = DeepCopy(cx, xml, NULL, 0);
5988     if (!copy)
5989         return JS_FALSE;
5990     *rval = OBJECT_TO_JSVAL(copy->object);
5991     return JS_TRUE;
5994 /* XML and XMLList */
5995 static JSBool
5996 xml_descendants(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
5997                 jsval *rval)
5999     JSXML *xml, *list;
6000     jsval name;
6002     XML_METHOD_PROLOG;
6003     name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6004     list = Descendants(cx, xml, name);
6005     if (!list)
6006         return JS_FALSE;
6007     *rval = OBJECT_TO_JSVAL(list->object);
6008     return JS_TRUE;
6011 /* XML and XMLList */
6012 static JSBool
6013 xml_elements(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6014              jsval *rval)
6016     JSXML *xml, *list, *kid, *vxml;
6017     jsval name, v;
6018     JSXMLQName *nameqn;
6019     jsid funid;
6020     JSObject *listobj, *kidobj;
6021     JSBool ok;
6022     JSXMLArrayCursor cursor;
6023     uint32 i, n;
6025     XML_METHOD_PROLOG;
6026     name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6027     nameqn = ToXMLName(cx, name, &funid);
6028     if (!nameqn)
6029         return JS_FALSE;
6030     argv[0] = OBJECT_TO_JSVAL(nameqn->object);
6032     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6033     if (!listobj)
6034         return JS_FALSE;
6035     *rval = OBJECT_TO_JSVAL(listobj);
6036     if (funid)
6037         return JS_TRUE;
6039     list = (JSXML *) JS_GetPrivate(cx, listobj);
6040     list->xml_target = xml;
6041     list->xml_targetprop = nameqn;
6042     ok = JS_TRUE;
6044     if (xml->xml_class == JSXML_CLASS_LIST) {
6045         /* 13.5.4.6 */
6046         XMLArrayCursorInit(&cursor, &xml->xml_kids);
6047         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
6048             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6049                 ok = JS_EnterLocalRootScope(cx);
6050                 if (!ok)
6051                     break;
6052                 kidobj = js_GetXMLObject(cx, kid);
6053                 ok = kidobj
6054                      ? xml_elements(cx, kidobj, argc, argv, &v)
6055                      : JS_FALSE;
6056                 JS_LeaveLocalRootScope(cx);
6057                 if (!ok)
6058                     break;
6059                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
6060                 if (JSXML_LENGTH(vxml) != 0) {
6061                     ok = Append(cx, list, vxml);
6062                     if (!ok)
6063                         break;
6064                 }
6065             }
6066         }
6067         XMLArrayCursorFinish(&cursor);
6068     } else {
6069         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6070             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6071             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT &&
6072                 MatchElemName(nameqn, kid)) {
6073                 ok = Append(cx, list, kid);
6074                 if (!ok)
6075                     break;
6076             }
6077         }
6078     }
6080     return ok;
6083 /* XML and XMLList */
6084 static JSBool
6085 xml_hasOwnProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6086                    jsval *rval)
6088     jsval name;
6089     JSObject *pobj;
6090     JSProperty *prop;
6092     if (!JS_InstanceOf(cx, obj, &js_XMLClass, argv))
6093         return JS_FALSE;
6095     name = argv[0];
6096     if (!HasProperty(cx, obj, name, &pobj, &prop))
6097         return JS_FALSE;
6098     if (!prop) {
6099         return js_HasOwnPropertyHelper(cx, obj, js_LookupProperty, argc, argv,
6100                                        rval);
6101     }
6102     DROP_PROPERTY(cx, pobj, prop);
6103     *rval = JSVAL_TRUE;
6104     return JS_TRUE;
6107 /* XML and XMLList */
6108 static JSBool
6109 xml_hasComplexContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6110                       jsval *rval)
6112     JSXML *xml, *kid;
6113     JSObject *kidobj;
6114     uint32 i, n;
6116     XML_METHOD_PROLOG;
6117 again:
6118     switch (xml->xml_class) {
6119       case JSXML_CLASS_ATTRIBUTE:
6120       case JSXML_CLASS_COMMENT:
6121       case JSXML_CLASS_PROCESSING_INSTRUCTION:
6122       case JSXML_CLASS_TEXT:
6123         *rval = JSVAL_FALSE;
6124         break;
6125       case JSXML_CLASS_LIST:
6126         if (xml->xml_kids.length == 0) {
6127             *rval = JSVAL_TRUE;
6128         } else if (xml->xml_kids.length == 1) {
6129             kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6130             if (kid) {
6131                 kidobj = js_GetXMLObject(cx, kid);
6132                 if (!kidobj)
6133                     return JS_FALSE;
6134                 obj = kidobj;
6135                 xml = (JSXML *) JS_GetPrivate(cx, obj);
6136                 goto again;
6137             }
6138         }
6139         /* FALL THROUGH */
6140       default:
6141         *rval = JSVAL_FALSE;
6142         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6143             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6144             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6145                 *rval = JSVAL_TRUE;
6146                 break;
6147             }
6148         }
6149         break;
6150     }
6151     return JS_TRUE;
6154 /* XML and XMLList */
6155 static JSBool
6156 xml_hasSimpleContent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6157                      jsval *rval)
6159     JSXML *xml;
6161     XML_METHOD_PROLOG;
6162     *rval = BOOLEAN_TO_JSVAL(HasSimpleContent(xml));
6163     return JS_TRUE;
6166 typedef struct JSTempRootedNSArray {
6167     JSTempValueRooter   tvr;
6168     JSXMLArray          array;
6169     jsval               value;  /* extra root for temporaries */
6170 } JSTempRootedNSArray;
6172 JS_STATIC_DLL_CALLBACK(void)
6173 mark_temp_ns_array(JSContext *cx, JSTempValueRooter *tvr)
6175     JSTempRootedNSArray *tmp = (JSTempRootedNSArray *)tvr;
6177     namespace_mark_vector(cx,
6178                           (JSXMLNamespace **)tmp->array.vector,
6179                           tmp->array.length, NULL);
6180     XMLArrayCursorMark(cx, tmp->array.cursors);
6181     if (JSVAL_IS_GCTHING(tmp->value))
6182         GC_MARK(cx, JSVAL_TO_GCTHING(tmp->value), "temp_ns_array_value", NULL);
6185 static void
6186 InitTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
6188     XMLArrayInit(cx, &tmp->array, 0);
6189     tmp->value = JSVAL_NULL;
6190     JS_PUSH_TEMP_ROOT_MARKER(cx, mark_temp_ns_array, &tmp->tvr);
6193 static void
6194 FinishTempNSArray(JSContext *cx, JSTempRootedNSArray *tmp)
6196     JS_ASSERT(tmp->tvr.u.marker == mark_temp_ns_array);
6197     JS_POP_TEMP_ROOT(cx, &tmp->tvr);
6198     XMLArrayFinish(cx, &tmp->array);
6201 /*
6202  * Populate a new JS array with elements of JSTempRootedNSArray.array and
6203  * place the result into rval.  rval must point to a rooted location.
6204  */
6205 static JSBool
6206 TempNSArrayToJSArray(JSContext *cx, JSTempRootedNSArray *tmp, jsval *rval)
6208     JSObject *arrayobj;
6209     uint32 i, n;
6210     JSXMLNamespace *ns;
6211     JSObject *nsobj;
6213     arrayobj = js_NewArrayObject(cx, 0, NULL);
6214     if (!arrayobj)
6215         return JS_FALSE;
6216     *rval = OBJECT_TO_JSVAL(arrayobj);
6217     for (i = 0, n = tmp->array.length; i < n; i++) {
6218         ns = XMLARRAY_MEMBER(&tmp->array, i, JSXMLNamespace);
6219         if (!ns)
6220             continue;
6221         nsobj = js_GetXMLNamespaceObject(cx, ns);
6222         if (!nsobj)
6223             return JS_FALSE;
6224         tmp->value = OBJECT_TO_JSVAL(nsobj);
6225         if (!OBJ_SET_PROPERTY(cx, arrayobj, INT_TO_JSID(i), &tmp->value))
6226             return JS_FALSE;
6227     }
6228     return JS_TRUE;
6231 static JSBool
6232 FindInScopeNamespaces(JSContext *cx, JSXML *xml, JSXMLArray *nsarray)
6234     uint32 length, i, j, n;
6235     JSXMLNamespace *ns, *ns2;
6237     length = nsarray->length;
6238     do {
6239         if (xml->xml_class != JSXML_CLASS_ELEMENT)
6240             continue;
6241         for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
6242             ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
6243             if (!ns)
6244                 continue;
6246             for (j = 0; j < length; j++) {
6247                 ns2 = XMLARRAY_MEMBER(nsarray, j, JSXMLNamespace);
6248                 if (ns2 &&
6249                     ((ns2->prefix && ns->prefix)
6250                      ? !js_CompareStrings(ns2->prefix, ns->prefix)
6251                      : !js_CompareStrings(ns2->uri, ns->uri))) {
6252                     break;
6253                 }
6254             }
6256             if (j == length) {
6257                 if (!XMLARRAY_APPEND(cx, nsarray, ns))
6258                     return JS_FALSE;
6259                 ++length;
6260             }
6261         }
6262     } while ((xml = xml->parent) != NULL);
6263     JS_ASSERT(length == nsarray->length);
6265     return JS_TRUE;
6268 static JSBool
6269 xml_inScopeNamespaces(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6270                       jsval *rval)
6272     JSXML *xml;
6273     JSTempRootedNSArray namespaces;
6274     JSBool ok;
6276     XML_METHOD_PROLOG;
6278     InitTempNSArray(cx, &namespaces);
6279     ok = FindInScopeNamespaces(cx, xml, &namespaces.array) &&
6280          TempNSArrayToJSArray(cx, &namespaces, rval);
6281     FinishTempNSArray(cx, &namespaces);
6282     return ok;
6285 static JSBool
6286 xml_insertChildAfter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6287                      jsval *rval)
6289     JSXML *xml, *kid;
6290     jsval arg;
6291     uint32 i;
6293     XML_METHOD_PROLOG;
6294     if (!JSXML_HAS_KIDS(xml))
6295         return JS_TRUE;
6297     arg = argv[0];
6298     if (JSVAL_IS_NULL(arg)) {
6299         kid = NULL;
6300         i = 0;
6301     } else {
6302         if (!VALUE_IS_XML(cx, arg))
6303             return JS_TRUE;
6304         kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
6305         i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
6306         if (i == XML_NOT_FOUND)
6307             return JS_TRUE;
6308         ++i;
6309     }
6311     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6312     if (!xml)
6313         return JS_FALSE;
6314     if (!Insert(cx, xml, i, argv[1]))
6315         return JS_FALSE;
6316     *rval = OBJECT_TO_JSVAL(obj);
6317     return JS_TRUE;
6320 static JSBool
6321 xml_insertChildBefore(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6322                       jsval *rval)
6324     JSXML *xml, *kid;
6325     jsval arg;
6326     uint32 i;
6328     XML_METHOD_PROLOG;
6329     if (!JSXML_HAS_KIDS(xml))
6330         return JS_TRUE;
6332     arg = argv[0];
6333     if (JSVAL_IS_NULL(arg)) {
6334         kid = NULL;
6335         i = xml->xml_kids.length;
6336     } else {
6337         if (!VALUE_IS_XML(cx, arg))
6338             return JS_TRUE;
6339         kid = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(arg));
6340         i = XMLARRAY_FIND_MEMBER(&xml->xml_kids, kid, NULL);
6341         if (i == XML_NOT_FOUND)
6342             return JS_TRUE;
6343     }
6345     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6346     if (!xml)
6347         return JS_FALSE;
6348     if (!Insert(cx, xml, i, argv[1]))
6349         return JS_FALSE;
6350     *rval = OBJECT_TO_JSVAL(obj);
6351     return JS_TRUE;
6354 /* XML and XMLList */
6355 static JSBool
6356 xml_length(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6358     JSXML *xml;
6360     XML_METHOD_PROLOG;
6361     if (xml->xml_class != JSXML_CLASS_LIST) {
6362         *rval = JSVAL_ONE;
6363     } else {
6364         if (!js_NewNumberValue(cx, xml->xml_kids.length, rval))
6365             return JS_FALSE;
6366     }
6367     return JS_TRUE;
6370 static JSBool
6371 xml_localName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6372               jsval *rval)
6374     JSXML *xml;
6376     XML_METHOD_PROLOG;
6377     *rval = xml->name ? STRING_TO_JSVAL(xml->name->localName) : JSVAL_NULL;
6378     return JS_TRUE;
6381 static JSBool
6382 xml_name(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6384     JSXML *xml;
6385     JSObject *nameobj;
6387     XML_METHOD_PROLOG;
6388     if (!xml->name) {
6389         *rval = JSVAL_NULL;
6390     } else {
6391         nameobj = js_GetXMLQNameObject(cx, xml->name);
6392         if (!nameobj)
6393             return JS_FALSE;
6394         *rval = OBJECT_TO_JSVAL(nameobj);
6395     }
6396     return JS_TRUE;
6399 static JSBool
6400 xml_namespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6401               jsval *rval)
6403     JSXML *xml;
6404     JSString *prefix;
6405     JSTempRootedNSArray inScopeNSes;
6406     JSBool ok;
6407     jsuint i, length;
6408     JSXMLNamespace *ns;
6409     JSObject *nsobj;
6411     XML_METHOD_PROLOG;
6412     if (argc == 0 &&
6413         (xml->xml_class == JSXML_CLASS_TEXT ||
6414          xml->xml_class == JSXML_CLASS_COMMENT ||
6415          xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)) {
6416         *rval = JSVAL_NULL;
6417         return JS_TRUE;
6418     }
6420     if (argc == 0) {
6421         prefix = NULL;
6422     } else {
6423         prefix = js_ValueToString(cx, argv[0]);
6424         if (!prefix)
6425             return JS_FALSE;
6426         argv[0] = STRING_TO_JSVAL(prefix);      /* local root */
6427     }
6429     /* After this point the control must flow through label out. */
6430     InitTempNSArray(cx, &inScopeNSes);
6431     ok = FindInScopeNamespaces(cx, xml, &inScopeNSes.array);
6432     if (!ok)
6433         goto out;
6435     if (!prefix) {
6436         ns = GetNamespace(cx, xml->name, &inScopeNSes.array);
6437         if (!ns) {
6438             ok = JS_FALSE;
6439             goto out;
6440         }
6441     } else {
6442         ns = NULL;
6443         for (i = 0, length = inScopeNSes.array.length; i < length; i++) {
6444             ns = XMLARRAY_MEMBER(&inScopeNSes.array, i, JSXMLNamespace);
6445             if (ns && ns->prefix && !js_CompareStrings(ns->prefix, prefix))
6446                 break;
6447             ns = NULL;
6448         }
6449     }
6451     if (!ns) {
6452         *rval = JSVAL_VOID;
6453     } else {
6454         nsobj = js_GetXMLNamespaceObject(cx, ns);
6455         if (!nsobj) {
6456             ok = JS_FALSE;
6457             goto out;
6458         }
6459         *rval = OBJECT_TO_JSVAL(nsobj);
6460     }
6462   out:
6463     FinishTempNSArray(cx, &inScopeNSes);
6464     return JS_TRUE;
6467 static JSBool
6468 xml_namespaceDeclarations(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6469                           jsval *rval)
6471     JSXML *xml, *yml;
6472     JSBool ok;
6473     JSTempRootedNSArray ancestors, declared;
6474     uint32 i, n;
6475     JSXMLNamespace *ns;
6477     XML_METHOD_PROLOG;
6478     if (JSXML_HAS_VALUE(xml) || xml->xml_class == JSXML_CLASS_LIST)
6479         return JS_TRUE;
6481     /* From here, control flow must goto out to finish these arrays. */
6482     ok = JS_TRUE;
6483     InitTempNSArray(cx, &ancestors);
6484     InitTempNSArray(cx, &declared);
6485     yml = xml;
6487     while ((yml = yml->parent) != NULL) {
6488         JS_ASSERT(yml->xml_class == JSXML_CLASS_ELEMENT);
6489         for (i = 0, n = yml->xml_namespaces.length; i < n; i++) {
6490             ns = XMLARRAY_MEMBER(&yml->xml_namespaces, i, JSXMLNamespace);
6491             if (ns &&
6492                 !XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
6493                 ok = XMLARRAY_APPEND(cx, &ancestors.array, ns);
6494                 if (!ok)
6495                     goto out;
6496             }
6497         }
6498     }
6500     for (i = 0, n = xml->xml_namespaces.length; i < n; i++) {
6501         ns = XMLARRAY_MEMBER(&xml->xml_namespaces, i, JSXMLNamespace);
6502         if (!ns)
6503             continue;
6504         if (!ns->declared)
6505             continue;
6506         if (!XMLARRAY_HAS_MEMBER(&ancestors.array, ns, namespace_match)) {
6507             ok = XMLARRAY_APPEND(cx, &declared.array, ns);
6508             if (!ok)
6509                 goto out;
6510         }
6511     }
6513     ok = TempNSArrayToJSArray(cx, &declared, rval);
6515 out:
6516     /* Finishing must be in reverse order of initialization to follow LIFO. */
6517     FinishTempNSArray(cx, &declared);
6518     FinishTempNSArray(cx, &ancestors);
6519     return ok;
6522 static const char js_attribute_str[] = "attribute";
6523 static const char js_text_str[]      = "text";
6525 /* Exported to jsgc.c #ifdef GC_MARK_DEBUG. */
6526 const char *js_xml_class_str[] = {
6527     "list",
6528     "element",
6529     js_attribute_str,
6530     "processing-instruction",
6531     js_text_str,
6532     "comment"
6533 };
6535 static JSBool
6536 xml_nodeKind(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6537              jsval *rval)
6539     JSXML *xml;
6540     JSString *str;
6542     XML_METHOD_PROLOG;
6543     str = JS_InternString(cx, js_xml_class_str[xml->xml_class]);
6544     if (!str)
6545         return JS_FALSE;
6546     *rval = STRING_TO_JSVAL(str);
6547     return JS_TRUE;
6550 static JSBool
6551 NormalizingDelete(JSContext *cx, JSObject *obj, JSXML *xml, jsval id)
6553     jsval junk;
6555     if (xml->xml_class == JSXML_CLASS_LIST)
6556         return DeleteProperty(cx, obj, id, &junk);
6557     return DeleteByIndex(cx, xml, id, &junk);
6560 /*
6561  * Erratum? the testcase js/tests/e4x/XML/13.4.4.26.js wants all-whitespace
6562  * text between tags to be removed by normalize.
6563  */
6564 static JSBool
6565 IsXMLSpace(JSString *str)
6567     const jschar *cp, *end;
6569     cp = JSSTRING_CHARS(str);
6570     end = cp + JSSTRING_LENGTH(str);
6571     while (cp < end) {
6572         if (!JS_ISXMLSPACE(*cp))
6573             return JS_FALSE;
6574         ++cp;
6575     }
6576     return JS_TRUE;
6579 /* XML and XMLList */
6580 static JSBool
6581 xml_normalize(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6582               jsval *rval)
6584     JSXML *xml, *kid, *kid2;
6585     uint32 i, n;
6586     JSObject *kidobj;
6587     JSString *str;
6588     jsval junk;
6590     XML_METHOD_PROLOG;
6591     *rval = OBJECT_TO_JSVAL(obj);
6592     if (!JSXML_HAS_KIDS(xml))
6593         return JS_TRUE;
6595     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6596     if (!xml)
6597         return JS_FALSE;
6599     for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6600         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6601         if (!kid)
6602             continue;
6603         if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6604             kidobj = js_GetXMLObject(cx, kid);
6605             if (!kidobj || !xml_normalize(cx, kidobj, argc, argv, &junk))
6606                 return JS_FALSE;
6607         } else if (kid->xml_class == JSXML_CLASS_TEXT) {
6608             while (i + 1 < n &&
6609                    (kid2 = XMLARRAY_MEMBER(&xml->xml_kids, i + 1, JSXML)) &&
6610                    kid2->xml_class == JSXML_CLASS_TEXT) {
6611                 str = js_ConcatStrings(cx, kid->xml_value, kid2->xml_value);
6612                 if (!str)
6613                     return JS_FALSE;
6614                 if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i + 1)))
6615                     return JS_FALSE;
6616                 n = xml->xml_kids.length;
6617                 kid->xml_value = str;
6618             }
6619             if (IS_EMPTY(kid->xml_value) || IsXMLSpace(kid->xml_value)) {
6620                 if (!NormalizingDelete(cx, obj, xml, INT_TO_JSVAL(i)))
6621                     return JS_FALSE;
6622                 n = xml->xml_kids.length;
6623                 --i;
6624             }
6625         }
6626     }
6628     return JS_TRUE;
6631 /* XML and XMLList */
6632 static JSBool
6633 xml_parent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6635     JSXML *xml, *parent, *kid;
6636     uint32 i, n;
6637     JSObject *parentobj;
6639     XML_METHOD_PROLOG;
6640     parent = xml->parent;
6641     if (xml->xml_class == JSXML_CLASS_LIST) {
6642         *rval = JSVAL_VOID;
6643         n = xml->xml_kids.length;
6644         if (n == 0)
6645             return JS_TRUE;
6647         kid = XMLARRAY_MEMBER(&xml->xml_kids, 0, JSXML);
6648         if (!kid)
6649             return JS_TRUE;
6650         parent = kid->parent;
6651         for (i = 1; i < n; i++) {
6652             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6653             if (kid && kid->parent != parent)
6654                 return JS_TRUE;
6655         }
6656     }
6658     if (!parent) {
6659         *rval = JSVAL_NULL;
6660         return JS_TRUE;
6661     }
6663     parentobj = js_GetXMLObject(cx, parent);
6664     if (!parentobj)
6665         return JS_FALSE;
6666     *rval = OBJECT_TO_JSVAL(parentobj);
6667     return JS_TRUE;
6670 /* XML and XMLList */
6671 static JSBool
6672 xml_processingInstructions(JSContext *cx, JSObject *obj, uintN argc,
6673                            jsval *argv, jsval *rval)
6675     JSXML *xml, *list, *kid, *vxml;
6676     jsval name, v;
6677     JSXMLQName *nameqn;
6678     jsid funid;
6679     JSObject *listobj, *kidobj;
6680     JSBool ok;
6681     JSXMLArrayCursor cursor;
6682     uint32 i, n;
6684     XML_METHOD_PROLOG;
6685     name = (argc == 0) ? ATOM_KEY(cx->runtime->atomState.starAtom) : argv[0];
6686     nameqn = ToXMLName(cx, name, &funid);
6687     if (!nameqn)
6688         return JS_FALSE;
6689     argv[0] = OBJECT_TO_JSVAL(nameqn->object);
6691     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
6692     if (!listobj)
6693         return JS_FALSE;
6694     *rval = OBJECT_TO_JSVAL(listobj);
6695     if (funid)
6696         return JS_TRUE;
6698     list = (JSXML *) JS_GetPrivate(cx, listobj);
6699     list->xml_target = xml;
6700     list->xml_targetprop = nameqn;
6701     ok = JS_TRUE;
6703     if (xml->xml_class == JSXML_CLASS_LIST) {
6704         /* 13.5.4.17 Step 4 (misnumbered 9 -- Erratum?). */
6705         XMLArrayCursorInit(&cursor, &xml->xml_kids);
6706         while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
6707             if (kid->xml_class == JSXML_CLASS_ELEMENT) {
6708                 ok = JS_EnterLocalRootScope(cx);
6709                 if (!ok)
6710                     break;
6711                 kidobj = js_GetXMLObject(cx, kid);
6712                 ok = kidobj
6713                      ? xml_processingInstructions(cx, kidobj, argc, argv, &v)
6714                      : JS_FALSE;
6715                 JS_LeaveLocalRootScope(cx);
6716                 if (!ok)
6717                     break;
6718                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
6719                 if (JSXML_LENGTH(vxml) != 0) {
6720                     ok = Append(cx, list, vxml);
6721                     if (!ok)
6722                         break;
6723                 }
6724             }
6725         }
6726         XMLArrayCursorFinish(&cursor);
6727     } else {
6728         /* 13.4.4.28 Step 4. */
6729         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
6730             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6731             if (kid && kid->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
6732                 (IS_STAR(nameqn->localName) ||
6733                  !js_CompareStrings(nameqn->localName, kid->name->localName))) {
6734                 ok = Append(cx, list, kid);
6735                 if (!ok)
6736                     break;
6737             }
6738         }
6739     }
6741     return ok;
6744 static JSBool
6745 xml_prependChild(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6746                  jsval *rval)
6748     JSXML *xml;
6750     XML_METHOD_PROLOG;
6751     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6752     if (!xml)
6753         return JS_FALSE;
6754     *rval = OBJECT_TO_JSVAL(obj);
6755     return Insert(cx, xml, 0, argv[0]);
6758 /* XML and XMLList */
6759 static JSBool
6760 xml_propertyIsEnumerable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6761                          jsval *rval)
6763     JSXML *xml;
6764     jsval name;
6765     uint32 index;
6767     XML_METHOD_PROLOG;
6768     name = argv[0];
6769     *rval = JSVAL_FALSE;
6770     if (js_IdIsIndex(name, &index)) {
6771         if (xml->xml_class == JSXML_CLASS_LIST) {
6772             /* 13.5.4.18. */
6773             *rval = BOOLEAN_TO_JSVAL(index < xml->xml_kids.length);
6774         } else {
6775             /* 13.4.4.30. */
6776             *rval = BOOLEAN_TO_JSVAL(index == 0);
6777         }
6778     }
6779     return JS_TRUE;
6782 static JSBool
6783 namespace_full_match(const void *a, const void *b)
6785     const JSXMLNamespace *nsa = (const JSXMLNamespace *) a;
6786     const JSXMLNamespace *nsb = (const JSXMLNamespace *) b;
6788     if (nsa->prefix && nsb->prefix &&
6789         js_CompareStrings(nsa->prefix, nsb->prefix)) {
6790         return JS_FALSE;
6791     }
6792     return !js_CompareStrings(nsa->uri, nsb->uri);
6795 static JSBool
6796 xml_removeNamespace_helper(JSContext *cx, JSXML *xml, JSXMLNamespace *ns)
6798     JSXMLNamespace *thisns, *attrns;
6799     uint32 i, n;
6800     JSXML *attr, *kid;
6802     thisns = GetNamespace(cx, xml->name, &xml->xml_namespaces);
6803     JS_ASSERT(thisns);
6804     if (thisns == ns)
6805         return JS_TRUE;
6807     for (i = 0, n = xml->xml_attrs.length; i < n; i++) {
6808         attr = XMLARRAY_MEMBER(&xml->xml_attrs, i, JSXML);
6809         if (!attr)
6810             continue;
6811         attrns = GetNamespace(cx, attr->name, &xml->xml_namespaces);
6812         JS_ASSERT(attrns);
6813         if (attrns == ns)
6814             return JS_TRUE;
6815     }
6817     i = XMLARRAY_FIND_MEMBER(&xml->xml_namespaces, ns, namespace_full_match);
6818     if (i != XML_NOT_FOUND)
6819         XMLArrayDelete(cx, &xml->xml_namespaces, i, JS_TRUE);
6821     for (i = 0, n = xml->xml_kids.length; i < n; i++) {
6822         kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
6823         if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
6824             if (!xml_removeNamespace_helper(cx, kid, ns))
6825                 return JS_FALSE;
6826         }
6827     }
6828     return JS_TRUE;
6831 static JSBool
6832 xml_removeNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6833                     jsval *rval)
6835     JSXML *xml;
6836     JSObject *nsobj;
6837     JSXMLNamespace *ns;
6839     XML_METHOD_PROLOG;
6840     *rval = OBJECT_TO_JSVAL(obj);
6841     if (xml->xml_class != JSXML_CLASS_ELEMENT)
6842         return JS_TRUE;
6843     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6844     if (!xml)
6845         return JS_FALSE;
6847     nsobj = CallConstructorFunction(cx, obj, &js_NamespaceClass.base, 1, argv);
6848     if (!nsobj)
6849         return JS_FALSE;
6850     argv[0] = OBJECT_TO_JSVAL(nsobj);
6851     ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
6853     /* NOTE: remove ns from each ancestor if not used by that ancestor. */
6854     return xml_removeNamespace_helper(cx, xml, ns);
6857 static JSBool
6858 xml_replace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6860     JSXML *xml, *vxml, *kid;
6861     jsval name, value, id, junk;
6862     uint32 index;
6863     JSObject *nameobj;
6864     JSXMLQName *nameqn;
6866     XML_METHOD_PROLOG;
6867     *rval = OBJECT_TO_JSVAL(obj);
6868     if (xml->xml_class != JSXML_CLASS_ELEMENT)
6869         return JS_TRUE;
6871     value = argv[1];
6872     vxml = VALUE_IS_XML(cx, value)
6873            ? (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(value))
6874            : NULL;
6875     if (!vxml) {
6876         if (!JS_ConvertValue(cx, value, JSTYPE_STRING, &argv[1]))
6877             return JS_FALSE;
6878         value = argv[1];
6879     } else {
6880         vxml = DeepCopy(cx, vxml, NULL, 0);
6881         if (!vxml)
6882             return JS_FALSE;
6883         value = argv[1] = OBJECT_TO_JSVAL(vxml->object);
6884     }
6886     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6887     if (!xml)
6888         return JS_FALSE;
6890     name = argv[0];
6891     if (js_IdIsIndex(name, &index))
6892         return Replace(cx, xml, name, value);
6894     /* Call function QName per spec, not ToXMLName, to avoid attribute names. */
6895     nameobj = CallConstructorFunction(cx, obj, &js_QNameClass.base, 1, &name);
6896     if (!nameobj)
6897         return JS_FALSE;
6898     argv[0] = OBJECT_TO_JSVAL(nameobj);
6899     nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
6901     id = JSVAL_VOID;
6902     index = xml->xml_kids.length;
6903     while (index != 0) {
6904         --index;
6905         kid = XMLARRAY_MEMBER(&xml->xml_kids, index, JSXML);
6906         if (kid && MatchElemName(nameqn, kid)) {
6907             if (!JSVAL_IS_VOID(id) && !DeleteByIndex(cx, xml, id, &junk))
6908                 return JS_FALSE;
6909             if (!IndexToIdVal(cx, index, &id))
6910                 return JS_FALSE;
6911         }
6912     }
6913     if (JSVAL_IS_VOID(id))
6914         return JS_TRUE;
6915     return Replace(cx, xml, id, value);
6918 static JSBool
6919 xml_setChildren(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6920                 jsval *rval)
6922     if (!PutProperty(cx, obj, ATOM_KEY(cx->runtime->atomState.starAtom),
6923                      &argv[0])) {
6924         return JS_FALSE;
6925     }
6927     *rval = OBJECT_TO_JSVAL(obj);
6928     return JS_TRUE;
6931 static JSBool
6932 xml_setLocalName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
6933                  jsval *rval)
6935     JSXML *xml;
6936     jsval name;
6937     JSXMLQName *nameqn;
6938     JSString *namestr;
6940     XML_METHOD_PROLOG;
6941     if (!JSXML_HAS_NAME(xml))
6942         return JS_TRUE;
6944     name = argv[0];
6945     if (!JSVAL_IS_PRIMITIVE(name) &&
6946         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base) {
6947         nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name));
6948         namestr = nameqn->localName;
6949     } else {
6950         if (!JS_ConvertValue(cx, name, JSTYPE_STRING, &argv[0]))
6951             return JS_FALSE;
6952         name = argv[0];
6953         namestr = JSVAL_TO_STRING(name);
6954     }
6956     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6957     if (!xml)
6958         return JS_FALSE;
6959     xml->name->localName = namestr;
6960     return JS_TRUE;
6963 static JSBool
6964 xml_setName(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
6966     JSXML *xml, *nsowner;
6967     jsval name;
6968     JSXMLQName *nameqn;
6969     JSObject *nameobj;
6970     JSXMLArray *nsarray;
6971     uint32 i, n;
6972     JSXMLNamespace *ns;
6974     XML_METHOD_PROLOG;
6975     if (!JSXML_HAS_NAME(xml))
6976         return JS_TRUE;
6978     name = argv[0];
6979     if (!JSVAL_IS_PRIMITIVE(name) &&
6980         OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(name)) == &js_QNameClass.base &&
6981         !(nameqn = (JSXMLQName *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(name)))
6982          ->uri) {
6983         name = argv[0] = STRING_TO_JSVAL(nameqn->localName);
6984     }
6986     nameobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 1, &name);
6987     if (!nameobj)
6988         return JS_FALSE;
6989     nameqn = (JSXMLQName *) JS_GetPrivate(cx, nameobj);
6991     /* ECMA-357 13.4.4.35 Step 4. */
6992     if (xml->xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION)
6993         nameqn->uri = cx->runtime->emptyString;
6995     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
6996     if (!xml)
6997         return JS_FALSE;
6998     xml->name = nameqn;
7000     /*
7001      * Erratum: nothing in 13.4.4.35 talks about making the name match the
7002      * in-scope namespaces, either by finding an in-scope namespace with a
7003      * matching uri and setting the new name's prefix to that namespace's
7004      * prefix, or by extending the in-scope namespaces for xml (which are in
7005      * xml->parent if xml is an attribute or a PI).
7006      */
7007     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7008         nsowner = xml;
7009     } else {
7010         if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
7011             return JS_TRUE;
7012         nsowner = xml->parent;
7013     }
7015     if (nameqn->prefix) {
7016         /*
7017          * The name being set has a prefix, which originally came from some
7018          * namespace object (which may be the null namespace, where both the
7019          * prefix and uri are the empty string).  We must go through a full
7020          * GetNamespace in case that namespace is in-scope in nsowner.
7021          *
7022          * If we find such an in-scope namespace, we return true right away,
7023          * in this block.  Otherwise, we fall through to the final return of
7024          * AddInScopeNamespace(cx, nsowner, ns).
7025          */
7026         ns = GetNamespace(cx, nameqn, &nsowner->xml_namespaces);
7027         if (!ns)
7028             return JS_FALSE;
7030         /* XXXbe have to test membership to see whether GetNamespace added */
7031         if (XMLARRAY_HAS_MEMBER(&nsowner->xml_namespaces, ns, NULL))
7032             return JS_TRUE;
7033     } else {
7034         /*
7035          * At this point, we know nameqn->prefix is null, so nameqn->uri can't
7036          * be the empty string (the null namespace always uses the empty string
7037          * for both prefix and uri).
7038          *
7039          * This means we must inline GetNamespace and specialize it to match
7040          * uri only, never prefix.  If we find a namespace with nameqn's uri
7041          * already in nsowner->xml_namespaces, then all that we need do is set
7042          * nameqn->prefix to that namespace's prefix.
7043          *
7044          * If no such namespace exists, we can create one without going through
7045          * the constructor, because we know nameqn->uri is non-empty (so prefix
7046          * does not need to be converted from null to empty by QName).
7047          */
7048         JS_ASSERT(!IS_EMPTY(nameqn->uri));
7050         nsarray = &nsowner->xml_namespaces;
7051         for (i = 0, n = nsarray->length; i < n; i++) {
7052             ns = XMLARRAY_MEMBER(nsarray, i, JSXMLNamespace);
7053             if (ns && !js_CompareStrings(ns->uri, nameqn->uri)) {
7054                 nameqn->prefix = ns->prefix;
7055                 return JS_TRUE;
7056             }
7057         }
7059         ns = js_NewXMLNamespace(cx, NULL, nameqn->uri, JS_TRUE);
7060         if (!ns)
7061             return JS_FALSE;
7062     }
7064     return AddInScopeNamespace(cx, nsowner, ns);
7067 static JSBool
7068 xml_setNamespace(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7069                  jsval *rval)
7071     JSXML *xml, *nsowner;
7072     JSObject *nsobj, *qnobj;
7073     JSXMLNamespace *ns;
7074     jsval qnargv[2];
7076     XML_METHOD_PROLOG;
7077     if (xml->xml_class != JSXML_CLASS_ELEMENT &&
7078         xml->xml_class != JSXML_CLASS_ATTRIBUTE) {
7079         return JS_TRUE;
7080     }
7082     xml = CHECK_COPY_ON_WRITE(cx, xml, obj);
7083     if (!xml || !js_GetXMLQNameObject(cx, xml->name))
7084         return JS_FALSE;
7086     nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 1, argv);
7087     if (!nsobj)
7088         return JS_FALSE;
7089     ns = (JSXMLNamespace *) JS_GetPrivate(cx, nsobj);
7090     ns->declared = JS_TRUE;
7092     qnargv[0] = argv[0] = OBJECT_TO_JSVAL(nsobj);
7093     qnargv[1] = OBJECT_TO_JSVAL(xml->name->object);
7094     qnobj = js_ConstructObject(cx, &js_QNameClass.base, NULL, NULL, 2, qnargv);
7095     if (!qnobj)
7096         return JS_FALSE;
7098     xml->name = (JSXMLQName *) JS_GetPrivate(cx, qnobj);
7100     /*
7101      * Erratum: the spec fails to update the governing in-scope namespaces.
7102      * See the erratum noted in xml_setName, above.
7103      */
7104     if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7105         nsowner = xml;
7106     } else {
7107         if (!xml->parent || xml->parent->xml_class != JSXML_CLASS_ELEMENT)
7108             return JS_TRUE;
7109         nsowner = xml->parent;
7110     }
7111     return AddInScopeNamespace(cx, nsowner, ns);
7114 /* XML and XMLList */
7115 static JSBool
7116 xml_text(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7118     JSXML *xml, *list, *kid, *vxml;
7119     JSObject *listobj, *kidobj;
7120     uint32 i, n;
7121     JSBool ok;
7122     jsval v;
7124     XML_METHOD_PROLOG;
7125     listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7126     if (!listobj)
7127         return JS_FALSE;
7129     *rval = OBJECT_TO_JSVAL(listobj);
7130     list = (JSXML *) JS_GetPrivate(cx, listobj);
7131     list->xml_target = xml;
7133     if (xml->xml_class == JSXML_CLASS_LIST) {
7134         ok = JS_TRUE;
7135         for (i = 0, n = xml->xml_kids.length; i < n; i++) {
7136             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
7137             if (kid && kid->xml_class == JSXML_CLASS_ELEMENT) {
7138                 ok = JS_EnterLocalRootScope(cx);
7139                 if (!ok)
7140                     break;
7141                 kidobj = js_GetXMLObject(cx, kid);
7142                 ok = kidobj
7143                      ? xml_text(cx, kidobj, argc, argv, &v)
7144                      : JS_FALSE;
7145                 JS_LeaveLocalRootScope(cx);
7146                 if (!ok)
7147                     return JS_FALSE;
7148                 vxml = (JSXML *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(v));
7149                 if (JSXML_LENGTH(vxml) != 0 && !Append(cx, list, vxml))
7150                     return JS_FALSE;
7151             }
7152         }
7153     } else {
7154         for (i = 0, n = JSXML_LENGTH(xml); i < n; i++) {
7155             kid = XMLARRAY_MEMBER(&xml->xml_kids, i, JSXML);
7156             if (kid && kid->xml_class == JSXML_CLASS_TEXT) {
7157                 if (!Append(cx, list, kid))
7158                     return JS_FALSE;
7159             }
7160         }
7161     }
7162     return JS_TRUE;
7165 /* XML and XMLList */
7166 static JSBool
7167 xml_toXMLString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7168                 jsval *rval)
7170     JSString *str;
7172     str = ToXMLString(cx, OBJECT_TO_JSVAL(obj));
7173     if (!str)
7174         return JS_FALSE;
7175     *rval = STRING_TO_JSVAL(str);
7176     return JS_TRUE;
7179 /* XML and XMLList */
7180 static JSString *
7181 xml_toString_helper(JSContext *cx, JSXML *xml)
7183     JSString *str, *kidstr;
7184     JSXML *kid;
7185     JSXMLArrayCursor cursor;
7187     if (xml->xml_class == JSXML_CLASS_ATTRIBUTE ||
7188         xml->xml_class == JSXML_CLASS_TEXT) {
7189         return xml->xml_value;
7190     }
7192     if (!HasSimpleContent(xml))
7193         return ToXMLString(cx, OBJECT_TO_JSVAL(xml->object));
7195     str = cx->runtime->emptyString;
7196     JS_EnterLocalRootScope(cx);
7197     XMLArrayCursorInit(&cursor, &xml->xml_kids);
7198     while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
7199         if (kid->xml_class != JSXML_CLASS_COMMENT &&
7200             kid->xml_class != JSXML_CLASS_PROCESSING_INSTRUCTION) {
7201             kidstr = xml_toString_helper(cx, kid);
7202             if (!kidstr) {
7203                 str = NULL;
7204                 break;
7205             }
7206             str = js_ConcatStrings(cx, str, kidstr);
7207             if (!str)
7208                 break;
7209         }
7210     }
7211     XMLArrayCursorFinish(&cursor);
7212     JS_LeaveLocalRootScope(cx);
7213     return str;
7216 static JSBool
7217 xml_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7218              jsval *rval)
7220     JSXML *xml;
7221     JSString *str;
7223     XML_METHOD_PROLOG;
7224     str = xml_toString_helper(cx, xml);
7225     if (!str)
7226         return JS_FALSE;
7227     *rval = STRING_TO_JSVAL(str);
7228     return JS_TRUE;
7231 /* XML and XMLList */
7232 static JSBool
7233 xml_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7235     *rval = OBJECT_TO_JSVAL(obj);
7236     return JS_TRUE;
7239 static JSFunctionSpec xml_methods[] = {
7240     {"addNamespace",          xml_addNamespace,          1,0,XML_MASK},
7241     {"appendChild",           xml_appendChild,           1,0,XML_MASK},
7242     {js_attribute_str,        xml_attribute,             1,0,GENERIC_MASK},
7243     {"attributes",            xml_attributes,            0,0,GENERIC_MASK},
7244     {"child",                 xml_child,                 1,0,GENERIC_MASK},
7245     {"childIndex",            xml_childIndex,            0,0,XML_MASK},
7246     {"children",              xml_children,              0,0,GENERIC_MASK},
7247     {"comments",              xml_comments,              0,0,GENERIC_MASK},
7248     {"contains",              xml_contains,              1,0,GENERIC_MASK},
7249     {"copy",                  xml_copy,                  0,0,GENERIC_MASK},
7250     {"descendants",           xml_descendants,           1,0,GENERIC_MASK},
7251     {"elements",              xml_elements,              1,0,GENERIC_MASK},
7252     {"hasOwnProperty",        xml_hasOwnProperty,        1,0,GENERIC_MASK},
7253     {"hasComplexContent",     xml_hasComplexContent,     1,0,GENERIC_MASK},
7254     {"hasSimpleContent",      xml_hasSimpleContent,      1,0,GENERIC_MASK},
7255     {"inScopeNamespaces",     xml_inScopeNamespaces,     0,0,XML_MASK},
7256     {"insertChildAfter",      xml_insertChildAfter,      2,0,XML_MASK},
7257     {"insertChildBefore",     xml_insertChildBefore,     2,0,XML_MASK},
7258     {js_length_str,           xml_length,                0,0,GENERIC_MASK},
7259     {js_localName_str,        xml_localName,             0,0,XML_MASK},
7260     {js_name_str,             xml_name,                  0,0,XML_MASK},
7261     {js_namespace_str,        xml_namespace,             1,0,XML_MASK},
7262     {"namespaceDeclarations", xml_namespaceDeclarations, 0,0,XML_MASK},
7263     {"nodeKind",              xml_nodeKind,              0,0,XML_MASK},
7264     {"normalize",             xml_normalize,             0,0,GENERIC_MASK},
7265     {js_xml_parent_str,       xml_parent,                0,0,GENERIC_MASK},
7266     {"processingInstructions",xml_processingInstructions,1,0,GENERIC_MASK},
7267     {"prependChild",          xml_prependChild,          1,0,XML_MASK},
7268     {"propertyIsEnumerable",  xml_propertyIsEnumerable,  1,0,GENERIC_MASK},
7269     {"removeNamespace",       xml_removeNamespace,       1,0,XML_MASK},
7270     {"replace",               xml_replace,               2,0,XML_MASK},
7271     {"setChildren",           xml_setChildren,           1,0,XML_MASK},
7272     {"setLocalName",          xml_setLocalName,          1,0,XML_MASK},
7273     {"setName",               xml_setName,               1,0,XML_MASK},
7274     {"setNamespace",          xml_setNamespace,          1,0,XML_MASK},
7275     {js_text_str,             xml_text,                  0,0,GENERIC_MASK},
7276     {js_toString_str,         xml_toString,              0,0,GENERIC_MASK},
7277     {js_toXMLString_str,      xml_toXMLString,           0,0,GENERIC_MASK},
7278     {js_toSource_str,         xml_toXMLString,           0,0,GENERIC_MASK},
7279     {js_valueOf_str,          xml_valueOf,               0,0,GENERIC_MASK},
7280     {0,0,0,0,0}
7281 };
7283 static JSBool
7284 CopyXMLSettings(JSContext *cx, JSObject *from, JSObject *to)
7286     int i;
7287     const char *name;
7288     jsval v;
7290     for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
7291         name = xml_static_props[i].name;
7292         if (!JS_GetProperty(cx, from, name, &v))
7293             return JS_FALSE;
7294         if (JSVAL_IS_BOOLEAN(v) && !JS_SetProperty(cx, to, name, &v))
7295             return JS_FALSE;
7296     }
7298     name = xml_static_props[i].name;
7299     if (!JS_GetProperty(cx, from, name, &v))
7300         return JS_FALSE;
7301     if (JSVAL_IS_NUMBER(v) && !JS_SetProperty(cx, to, name, &v))
7302         return JS_FALSE;
7303     return JS_TRUE;
7306 static JSBool
7307 SetDefaultXMLSettings(JSContext *cx, JSObject *obj)
7309     int i;
7310     jsval v;
7312     for (i = XML_IGNORE_COMMENTS; i < XML_PRETTY_INDENT; i++) {
7313         v = JSVAL_TRUE;
7314         if (!JS_SetProperty(cx, obj, xml_static_props[i].name, &v))
7315             return JS_FALSE;
7316     }
7317     v = INT_TO_JSVAL(2);
7318     return JS_SetProperty(cx, obj, xml_static_props[i].name, &v);
7321 static JSBool
7322 xml_settings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7324     JSObject *settings;
7326     settings = JS_NewObject(cx, NULL, NULL, NULL);
7327     if (!settings)
7328         return JS_FALSE;
7329     *rval = OBJECT_TO_JSVAL(settings);
7330     return CopyXMLSettings(cx, obj, settings);
7333 static JSBool
7334 xml_setSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7335                 jsval *rval)
7337     jsval v;
7338     JSBool ok;
7339     JSObject *settings;
7341     v = argv[0];
7342     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
7343         cx->xmlSettingFlags = 0;
7344         ok = SetDefaultXMLSettings(cx, obj);
7345     } else {
7346         if (JSVAL_IS_PRIMITIVE(v))
7347             return JS_TRUE;
7348         settings = JSVAL_TO_OBJECT(v);
7349         cx->xmlSettingFlags = 0;
7350         ok = CopyXMLSettings(cx, settings, obj);
7351     }
7352     if (ok)
7353         cx->xmlSettingFlags |= XSF_CACHE_VALID;
7354     return ok;
7357 static JSBool
7358 xml_defaultSettings(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7359                     jsval *rval)
7361     JSObject *settings;
7363     settings = JS_NewObject(cx, NULL, NULL, NULL);
7364     if (!settings)
7365         return JS_FALSE;
7366     *rval = OBJECT_TO_JSVAL(settings);
7367     return SetDefaultXMLSettings(cx, settings);
7370 static JSFunctionSpec xml_static_methods[] = {
7371     {"settings",         xml_settings,          0,0,0},
7372     {"setSettings",      xml_setSettings,       1,0,0},
7373     {"defaultSettings",  xml_defaultSettings,   0,0,0},
7374     {0,0,0,0,0}
7375 };
7377 static JSBool
7378 XML(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7380     jsval v;
7381     JSXML *xml, *copy;
7382     JSObject *xobj, *vobj;
7383     JSClass *clasp;
7385     v = argv[0];
7386     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
7387         v = STRING_TO_JSVAL(cx->runtime->emptyString);
7389     xobj = ToXML(cx, v);
7390     if (!xobj)
7391         return JS_FALSE;
7392     *rval = OBJECT_TO_JSVAL(xobj);
7393     xml = (JSXML *) JS_GetPrivate(cx, xobj);
7395     if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
7396         vobj = JSVAL_TO_OBJECT(v);
7397         clasp = OBJ_GET_CLASS(cx, vobj);
7398         if (clasp == &js_XMLClass ||
7399             (clasp->flags & JSCLASS_DOCUMENT_OBSERVER)) {
7400             /* No need to lock obj, it's newly constructed and thread local. */
7401             copy = DeepCopy(cx, xml, obj, 0);
7402             if (!copy)
7403                 return JS_FALSE;
7404             JS_ASSERT(copy->object == obj);
7405             *rval = OBJECT_TO_JSVAL(obj);
7406             return JS_TRUE;
7407         }
7408     }
7409     return JS_TRUE;
7412 static JSBool
7413 XMLList(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
7415     jsval v;
7416     JSObject *vobj, *listobj;
7417     JSXML *xml, *list;
7419     v = argv[0];
7420     if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v))
7421         v = STRING_TO_JSVAL(cx->runtime->emptyString);
7423     if ((cx->fp->flags & JSFRAME_CONSTRUCTING) && !JSVAL_IS_PRIMITIVE(v)) {
7424         vobj = JSVAL_TO_OBJECT(v);
7425         if (OBJECT_IS_XML(cx, vobj)) {
7426             xml = (JSXML *) JS_GetPrivate(cx, vobj);
7427             if (xml->xml_class == JSXML_CLASS_LIST) {
7428                 listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
7429                 if (!listobj)
7430                     return JS_FALSE;
7431                 *rval = OBJECT_TO_JSVAL(listobj);
7433                 list = (JSXML *) JS_GetPrivate(cx, listobj);
7434                 if (!Append(cx, list, xml))
7435                     return JS_FALSE;
7436                 return JS_TRUE;
7437             }
7438         }
7439     }
7441     /* Toggle on XML support since the script has explicitly requested it. */
7442     listobj = ToXMLList(cx, v);
7443     if (!listobj)
7444         return JS_FALSE;
7446     *rval = OBJECT_TO_JSVAL(listobj);
7447     return JS_TRUE;
7450 #define JSXML_LIST_SIZE     (offsetof(JSXML, u) + sizeof(struct JSXMLListVar))
7451 #define JSXML_ELEMENT_SIZE  (offsetof(JSXML, u) + sizeof(struct JSXMLVar))
7452 #define JSXML_LEAF_SIZE     (offsetof(JSXML, u) + sizeof(JSString *))
7454 static size_t sizeof_JSXML[JSXML_CLASS_LIMIT] = {
7455     JSXML_LIST_SIZE,        /* JSXML_CLASS_LIST */
7456     JSXML_ELEMENT_SIZE,     /* JSXML_CLASS_ELEMENT */
7457     JSXML_LEAF_SIZE,        /* JSXML_CLASS_ATTRIBUTE */
7458     JSXML_LEAF_SIZE,        /* JSXML_CLASS_PROCESSING_INSTRUCTION */
7459     JSXML_LEAF_SIZE,        /* JSXML_CLASS_TEXT */
7460     JSXML_LEAF_SIZE         /* JSXML_CLASS_COMMENT */
7461 };
7463 #ifdef DEBUG_notme
7464 JSCList xml_leaks = JS_INIT_STATIC_CLIST(&xml_leaks);
7465 uint32  xml_serial;
7466 #endif
7468 JSXML *
7469 js_NewXML(JSContext *cx, JSXMLClass xml_class)
7471     JSXML *xml;
7473     xml = (JSXML *) js_NewGCThing(cx, GCX_XML, sizeof_JSXML[xml_class]);
7474     if (!xml)
7475         return NULL;
7477     xml->object = NULL;
7478     xml->domnode = NULL;
7479     xml->parent = NULL;
7480     xml->name = NULL;
7481     xml->xml_class = xml_class;
7482     xml->xml_flags = 0;
7483     if (JSXML_CLASS_HAS_VALUE(xml_class)) {
7484         xml->xml_value = cx->runtime->emptyString;
7485     } else {
7486         XMLArrayInit(cx, &xml->xml_kids, 0);
7487         if (xml_class == JSXML_CLASS_LIST) {
7488             xml->xml_target = NULL;
7489             xml->xml_targetprop = NULL;
7490         } else {
7491             XMLArrayInit(cx, &xml->xml_namespaces, 0);
7492             XMLArrayInit(cx, &xml->xml_attrs, 0);
7493         }
7494     }
7496 #ifdef DEBUG_notme
7497     JS_APPEND_LINK(&xml->links, &xml_leaks);
7498     xml->serial = xml_serial++;
7499 #endif
7500     METER(xml_stats.xml);
7501     METER(xml_stats.livexml);
7502     return xml;
7505 static void
7506 xml_mark_tail(JSContext *cx, JSXML *xml, void *arg)
7508     XMLArrayTrim(&xml->xml_kids);
7510     if (xml->xml_class == JSXML_CLASS_LIST) {
7511         if (xml->xml_target)
7512             JS_MarkGCThing(cx, xml->xml_target, "target", arg);
7513         if (xml->xml_targetprop)
7514             JS_MarkGCThing(cx, xml->xml_targetprop, "targetprop", arg);
7515     } else {
7516         namespace_mark_vector(cx,
7517                               (JSXMLNamespace **) xml->xml_namespaces.vector,
7518                               xml->xml_namespaces.length,
7519                               arg);
7520         XMLArrayCursorMark(cx, xml->xml_namespaces.cursors);
7521         XMLArrayTrim(&xml->xml_namespaces);
7523         xml_mark_vector(cx,
7524                         (JSXML **) xml->xml_attrs.vector,
7525                         xml->xml_attrs.length,
7526                         arg);
7527         XMLArrayCursorMark(cx, xml->xml_attrs.cursors);
7528         XMLArrayTrim(&xml->xml_attrs);
7529     }
7532 void
7533 js_MarkXML(JSContext *cx, JSXML *xml, void *arg)
7535     JS_MarkGCThing(cx, xml->object, js_object_str, arg);
7536     JS_MarkGCThing(cx, xml->name, js_name_str, arg);
7537     JS_MarkGCThing(cx, xml->parent, js_xml_parent_str, arg);
7539     if (JSXML_HAS_VALUE(xml)) {
7540         JS_MarkGCThing(cx, xml->xml_value, "value", arg);
7541     } else {
7542         xml_mark_vector(cx,
7543                         (JSXML **) xml->xml_kids.vector,
7544                         xml->xml_kids.length,
7545                         arg);
7546         XMLArrayCursorMark(cx, xml->xml_kids.cursors);
7548         xml_mark_tail(cx, xml, arg);
7549     }
7552 void
7553 js_FinalizeXML(JSContext *cx, JSXML *xml)
7555     if (JSXML_HAS_KIDS(xml)) {
7556         XMLArrayFinish(cx, &xml->xml_kids);
7557         if (xml->xml_class == JSXML_CLASS_ELEMENT) {
7558             XMLArrayFinish(cx, &xml->xml_namespaces);
7559             XMLArrayFinish(cx, &xml->xml_attrs);
7560         }
7561     }
7563 #ifdef DEBUG_notme
7564     JS_REMOVE_LINK(&xml->links);
7565 #endif
7567     UNMETER(xml_stats.livexml);
7570 JSObject *
7571 js_ParseNodeToXMLObject(JSContext *cx, JSParseNode *pn)
7573     jsval nsval;
7574     JSXMLNamespace *ns;
7575     JSXMLArray nsarray;
7576     JSXML *xml;
7578     if (!js_GetDefaultXMLNamespace(cx, &nsval))
7579         return NULL;
7580     JS_ASSERT(!JSVAL_IS_PRIMITIVE(nsval));
7581     ns = (JSXMLNamespace *) JS_GetPrivate(cx, JSVAL_TO_OBJECT(nsval));
7583     if (!XMLArrayInit(cx, &nsarray, 1))
7584         return NULL;
7586     XMLARRAY_APPEND(cx, &nsarray, ns);
7587     xml = ParseNodeToXML(cx, pn, &nsarray, XSF_PRECOMPILED_ROOT);
7588     XMLArrayFinish(cx, &nsarray);
7589     if (!xml)
7590         return NULL;
7592     return xml->object;
7595 JSObject *
7596 js_NewXMLObject(JSContext *cx, JSXMLClass xml_class)
7598     JSXML *xml;
7599     JSObject *obj;
7600     JSTempValueRooter tvr;
7602     xml = js_NewXML(cx, xml_class);
7603     if (!xml)
7604         return NULL;
7605     JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(xml), &tvr);
7606     obj = js_GetXMLObject(cx, xml);
7607     JS_POP_TEMP_ROOT(cx, &tvr);
7608     return obj;
7611 static JSObject *
7612 NewXMLObject(JSContext *cx, JSXML *xml)
7614     JSObject *obj;
7616     obj = js_NewObject(cx, &js_XMLClass, NULL, NULL);
7617     if (!obj || !JS_SetPrivate(cx, obj, xml)) {
7618         cx->newborn[GCX_OBJECT] = NULL;
7619         return NULL;
7620     }
7621     METER(xml_stats.xmlobj);
7622     METER(xml_stats.livexmlobj);
7623     return obj;
7626 JSObject *
7627 js_GetXMLObject(JSContext *cx, JSXML *xml)
7629     JSObject *obj;
7631     obj = xml->object;
7632     if (obj) {
7633         JS_ASSERT(JS_GetPrivate(cx, obj) == xml);
7634         return obj;
7635     }
7637     /*
7638      * A JSXML cannot be shared among threads unless it has an object.
7639      * A JSXML cannot be given an object unless:
7640      * (a) it has no parent; or
7641      * (b) its parent has no object (therefore is thread-private); or
7642      * (c) its parent's object is locked.
7643      *
7644      * Once given an object, a JSXML is immutable.
7645      */
7646     JS_ASSERT(!xml->parent ||
7647               !xml->parent->object ||
7648               JS_IS_OBJ_LOCKED(cx, xml->parent->object));
7650     obj = NewXMLObject(cx, xml);
7651     if (!obj)
7652         return NULL;
7653     xml->object = obj;
7654     return obj;
7657 JSObject *
7658 js_InitNamespaceClass(JSContext *cx, JSObject *obj)
7660     return JS_InitClass(cx, obj, NULL, &js_NamespaceClass.base, Namespace, 2,
7661                         namespace_props, namespace_methods, NULL, NULL);
7664 JSObject *
7665 js_InitQNameClass(JSContext *cx, JSObject *obj)
7667     return JS_InitClass(cx, obj, NULL, &js_QNameClass.base, QName, 2,
7668                         qname_props, qname_methods, NULL, NULL);
7671 JSObject *
7672 js_InitAttributeNameClass(JSContext *cx, JSObject *obj)
7674     return JS_InitClass(cx, obj, NULL, &js_AttributeNameClass, AttributeName, 2,
7675                         qname_props, qname_methods, NULL, NULL);
7678 JSObject *
7679 js_InitAnyNameClass(JSContext *cx, JSObject *obj)
7681     jsval v;
7683     if (!js_GetAnyName(cx, &v))
7684         return NULL;
7685     return JSVAL_TO_OBJECT(v);
7688 JSObject *
7689 js_InitXMLClass(JSContext *cx, JSObject *obj)
7691     JSObject *proto, *pobj, *ctor;
7692     JSFunctionSpec *fs;
7693     JSFunction *fun;
7694     JSXML *xml;
7695     JSProperty *prop;
7696     JSScopeProperty *sprop;
7697     jsval cval, argv[1], junk;
7699     /* Define the isXMLName function. */
7700     if (!JS_DefineFunction(cx, obj, js_isXMLName_str, xml_isXMLName, 1, 0))
7701         return NULL;
7703     /* Define the XML class constructor and prototype. */
7704     proto = JS_InitClass(cx, obj, NULL, &js_XMLClass, XML, 1,
7705                          NULL, NULL,
7706                          xml_static_props, xml_static_methods);
7707     if (!proto)
7708         return NULL;
7710     /*
7711      * XXX Hack alert: expand JS_DefineFunctions here to copy fs->extra into
7712      * fun->spare, clearing fun->extra.  No xml_methods require extra local GC
7713      * roots allocated after actual arguments on the VM stack, but we need a
7714      * way to tell which methods work only on XML objects, which work only on
7715      * XMLList objects, and which work on either.
7716      */
7717     for (fs = xml_methods; fs->name; fs++) {
7718         fun = JS_DefineFunction(cx, proto, fs->name, fs->call, fs->nargs,
7719                                 fs->flags);
7720         if (!fun)
7721             return NULL;
7722         fun->extra = 0;
7723         fun->spare = fs->extra;
7724     }
7726     xml = js_NewXML(cx, JSXML_CLASS_TEXT);
7727     if (!xml || !JS_SetPrivate(cx, proto, xml))
7728         return NULL;
7729     xml->object = proto;
7730     METER(xml_stats.xmlobj);
7731     METER(xml_stats.livexmlobj);
7733     /*
7734      * Prepare to set default settings on the XML constructor we just made.
7735      * NB: We can't use JS_GetConstructor, because it calls OBJ_GET_PROPERTY,
7736      * which is xml_getProperty, which creates a new XMLList every time!  We
7737      * must instead call js_LookupProperty directly.
7738      */
7739     if (!js_LookupProperty(cx, proto,
7740                            ATOM_TO_JSID(cx->runtime->atomState.constructorAtom),
7741                            &pobj, &prop)) {
7742         return NULL;
7743     }
7744     JS_ASSERT(prop);
7745     sprop = (JSScopeProperty *) prop;
7746     JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)));
7747     cval = OBJ_GET_SLOT(cx, pobj, sprop->slot);
7748     OBJ_DROP_PROPERTY(cx, pobj, prop);
7749     JS_ASSERT(JSVAL_IS_FUNCTION(cx, cval));
7751     /* Set default settings. */
7752     ctor = JSVAL_TO_OBJECT(cval);
7753     argv[0] = JSVAL_VOID;
7754     if (!xml_setSettings(cx, ctor, 1, argv, &junk))
7755         return NULL;
7757     /* Define the XMLList function and give it the same prototype as XML. */
7758     fun = JS_DefineFunction(cx, obj, js_XMLList_str, XMLList, 1, 0);
7759     if (!fun)
7760         return NULL;
7761     if (!js_SetClassPrototype(cx, fun->object, proto,
7762                               JSPROP_READONLY | JSPROP_PERMANENT)) {
7763         return NULL;
7764     }
7765     return proto;
7768 JSObject *
7769 js_InitXMLClasses(JSContext *cx, JSObject *obj)
7771     if (!js_InitNamespaceClass(cx, obj))
7772         return NULL;
7773     if (!js_InitQNameClass(cx, obj))
7774         return NULL;
7775     if (!js_InitAttributeNameClass(cx, obj))
7776         return NULL;
7777     if (!js_InitAnyNameClass(cx, obj))
7778         return NULL;
7779     return js_InitXMLClass(cx, obj);
7782 JSBool
7783 js_GetFunctionNamespace(JSContext *cx, jsval *vp)
7785     JSRuntime *rt;
7786     JSObject *obj;
7787     JSAtom *atom;
7788     JSString *prefix, *uri;
7790     /* An invalid URI, for internal use only, guaranteed not to collide. */
7791     static const char anti_uri[] = "@mozilla.org/js/function";
7793     rt = cx->runtime;
7794     obj = rt->functionNamespaceObject;
7795     if (!obj) {
7796         atom = js_Atomize(cx, js_function_str, 8, 0);
7797         JS_ASSERT(atom);
7798         prefix = ATOM_TO_STRING(atom);
7800         atom = js_Atomize(cx, anti_uri, sizeof anti_uri - 1, ATOM_PINNED);
7801         if (!atom)
7802             return JS_FALSE;
7803         rt->atomState.lazy.functionNamespaceURIAtom = atom;
7805         uri = ATOM_TO_STRING(atom);
7806         obj = js_NewXMLNamespaceObject(cx, prefix, uri, JS_FALSE);
7807         if (!obj)
7808             return JS_FALSE;
7810         /*
7811          * Avoid entraining any in-scope Object.prototype.  The loss of
7812          * Namespace.prototype is not detectable, as there is no way to
7813          * refer to this instance in scripts.  When used to qualify method
7814          * names, its prefix and uri references are copied to the QName.
7815          */
7816         OBJ_SET_PROTO(cx, obj, NULL);
7817         OBJ_SET_PARENT(cx, obj, NULL);
7818         rt->functionNamespaceObject = obj;
7819     }
7820     *vp = OBJECT_TO_JSVAL(obj);
7821     return JS_TRUE;
7824 /*
7825  * Note the asymmetry between js_GetDefaultXMLNamespace and js_SetDefaultXML-
7826  * Namespace.  Get searches fp->scopeChain for JS_DEFAULT_XML_NAMESPACE_ID,
7827  * while Set sets JS_DEFAULT_XML_NAMESPACE_ID in fp->varobj (unless fp is a
7828  * lightweight function activation).  There's no requirement that fp->varobj
7829  * lie directly on fp->scopeChain, although it should be reachable using the
7830  * prototype chain from a scope object (cf. JSOPTION_VAROBJFIX in jsapi.h).
7831  *
7832  * If Get can't find JS_DEFAULT_XML_NAMESPACE_ID along the scope chain, it
7833  * creates a default namespace via 'new Namespace()'.  In contrast, Set uses
7834  * its v argument as the uri of a new Namespace, with "" as the prefix.  See
7835  * ECMA-357 12.1 and 12.1.1.  Note that if Set is called with a Namespace n,
7836  * the default XML namespace will be set to ("", n.uri).  So the uri string
7837  * is really the only usefully stored value of the default namespace.
7838  */
7839 JSBool
7840 js_GetDefaultXMLNamespace(JSContext *cx, jsval *vp)
7842     JSStackFrame *fp;
7843     JSObject *nsobj, *obj, *tmp;
7844     jsval v;
7846     fp = cx->fp;
7847     nsobj = fp->xmlNamespace;
7848     if (nsobj) {
7849         *vp = OBJECT_TO_JSVAL(nsobj);
7850         return JS_TRUE;
7851     }
7853     obj = NULL;
7854     for (tmp = fp->scopeChain; tmp; tmp = OBJ_GET_PARENT(cx, obj)) {
7855         obj = tmp;
7856         if (!OBJ_GET_PROPERTY(cx, obj, JS_DEFAULT_XML_NAMESPACE_ID, &v))
7857             return JS_FALSE;
7858         if (!JSVAL_IS_PRIMITIVE(v)) {
7859             fp->xmlNamespace = JSVAL_TO_OBJECT(v);
7860             *vp = v;
7861             return JS_TRUE;
7862         }
7863     }
7865     nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, obj, 0, NULL);
7866     if (!nsobj)
7867         return JS_FALSE;
7868     v = OBJECT_TO_JSVAL(nsobj);
7869     if (obj &&
7870         !OBJ_DEFINE_PROPERTY(cx, obj, JS_DEFAULT_XML_NAMESPACE_ID, v,
7871                              JS_PropertyStub, JS_PropertyStub,
7872                              JSPROP_PERMANENT, NULL)) {
7873         return JS_FALSE;
7874     }
7875     fp->xmlNamespace = nsobj;
7876     *vp = v;
7877     return JS_TRUE;
7880 JSBool
7881 js_SetDefaultXMLNamespace(JSContext *cx, jsval v)
7883     jsval argv[2];
7884     JSObject *nsobj, *varobj;
7885     JSStackFrame *fp;
7887     argv[0] = STRING_TO_JSVAL(cx->runtime->emptyString);
7888     argv[1] = v;
7889     nsobj = js_ConstructObject(cx, &js_NamespaceClass.base, NULL, NULL,
7890                                2, argv);
7891     if (!nsobj)
7892         return JS_FALSE;
7893     v = OBJECT_TO_JSVAL(nsobj);
7895     fp = cx->fp;
7896     varobj = fp->varobj;
7897     if (varobj) {
7898         if (!OBJ_DEFINE_PROPERTY(cx, varobj, JS_DEFAULT_XML_NAMESPACE_ID, v,
7899                                  JS_PropertyStub, JS_PropertyStub,
7900                                  JSPROP_PERMANENT, NULL)) {
7901             return JS_FALSE;
7902         }
7903     } else {
7904         JS_ASSERT(fp->fun && !(fp->fun->flags & JSFUN_HEAVYWEIGHT));
7905     }
7906     fp->xmlNamespace = JSVAL_TO_OBJECT(v);
7907     return JS_TRUE;
7910 JSBool
7911 js_ToAttributeName(JSContext *cx, jsval *vp)
7913     JSXMLQName *qn;
7915     qn = ToAttributeName(cx, *vp);
7916     if (!qn)
7917         return JS_FALSE;
7918     *vp = OBJECT_TO_JSVAL(qn->object);
7919     return JS_TRUE;
7922 JSString *
7923 js_EscapeAttributeValue(JSContext *cx, JSString *str)
7925     return EscapeAttributeValue(cx, NULL, str);
7928 JSString *
7929 js_AddAttributePart(JSContext *cx, JSBool isName, JSString *str, JSString *str2)
7931     size_t len, len2, newlen;
7932     jschar *chars;
7934     if (JSSTRING_IS_DEPENDENT(str) ||
7935         !(*js_GetGCThingFlags(str) & GCF_MUTABLE)) {
7936         str = js_NewStringCopyN(cx, JSSTRING_CHARS(str), JSSTRING_LENGTH(str),
7937                                 0);
7938         if (!str)
7939             return NULL;
7940     }
7942     len = str->length;
7943     len2 = JSSTRING_LENGTH(str2);
7944     newlen = (isName) ? len + 1 + len2 : len + 2 + len2 + 1;
7945     chars = (jschar *) JS_realloc(cx, str->chars, (newlen+1) * sizeof(jschar));
7946     if (!chars)
7947         return NULL;
7949     /*
7950      * Reallocating str (because we know it has no other references) requires
7951      * purging any deflated string cached for it.
7952      */
7953     js_PurgeDeflatedStringCache(str);
7955     str->chars = chars;
7956     str->length = newlen;
7957     chars += len;
7958     if (isName) {
7959         *chars++ = ' ';
7960         js_strncpy(chars, JSSTRING_CHARS(str2), len2);
7961         chars += len2;
7962     } else {
7963         *chars++ = '=';
7964         *chars++ = '"';
7965         js_strncpy(chars, JSSTRING_CHARS(str2), len2);
7966         chars += len2;
7967         *chars++ = '"';
7968     }
7969     *chars = 0;
7970     return str;
7973 JSString *
7974 js_EscapeElementValue(JSContext *cx, JSString *str)
7976     return EscapeElementValue(cx, NULL, str);
7979 JSString *
7980 js_ValueToXMLString(JSContext *cx, jsval v)
7982     return ToXMLString(cx, v);
7985 static JSBool
7986 anyname_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
7987                  jsval *rval)
7989     *rval = ATOM_KEY(cx->runtime->atomState.starAtom);
7990     return JS_TRUE;
7993 JSBool
7994 js_GetAnyName(JSContext *cx, jsval *vp)
7996     JSRuntime *rt;
7997     JSObject *obj;
7998     JSXMLQName *qn;
8000     rt = cx->runtime;
8001     obj = rt->anynameObject;
8002     if (!obj) {
8003         qn = js_NewXMLQName(cx, rt->emptyString, rt->emptyString,
8004                             ATOM_TO_STRING(rt->atomState.starAtom));
8005         if (!qn)
8006             return JS_FALSE;
8008         obj = js_NewObject(cx, &js_AnyNameClass, NULL, NULL);
8009         if (!obj || !JS_SetPrivate(cx, obj, qn)) {
8010             cx->newborn[GCX_OBJECT] = NULL;
8011             return JS_FALSE;
8012         }
8013         qn->object = obj;
8014         METER(xml_stats.qnameobj);
8015         METER(xml_stats.liveqnameobj);
8017         /*
8018          * Avoid entraining any in-scope Object.prototype.  This loses the
8019          * default toString inheritance, but no big deal: we want a better
8020          * custom one for clearer diagnostics.
8021          */
8022         if (!JS_DefineFunction(cx, obj, js_toString_str, anyname_toString,
8023                                0, 0)) {
8024             return JS_FALSE;
8025         }
8026         OBJ_SET_PROTO(cx, obj, NULL);
8027         JS_ASSERT(!OBJ_GET_PARENT(cx, obj));
8028         rt->anynameObject = obj;
8029     }
8030     *vp = OBJECT_TO_JSVAL(obj);
8031     return JS_TRUE;
8034 JSBool
8035 js_FindXMLProperty(JSContext *cx, jsval name, JSObject **objp, jsval *namep)
8037     JSXMLQName *qn;
8038     jsid funid, id;
8039     JSObject *obj, *pobj, *lastobj;
8040     JSProperty *prop;
8041     const char *printable;
8043     qn = ToXMLName(cx, name, &funid);
8044     if (!qn)
8045         return JS_FALSE;
8046     id = OBJECT_TO_JSID(qn->object);
8048     obj = cx->fp->scopeChain;
8049     do {
8050         if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
8051             return JS_FALSE;
8052         if (prop) {
8053             OBJ_DROP_PROPERTY(cx, pobj, prop);
8055             /*
8056              * Call OBJ_THIS_OBJECT to skip any With object that wraps an XML
8057              * object to carry scope chain linkage in js_FilterXMLList.
8058              */
8059             pobj = OBJ_THIS_OBJECT(cx, obj);
8060             if (OBJECT_IS_XML(cx, pobj)) {
8061                 *objp = pobj;
8062                 *namep = ID_TO_VALUE(id);
8063                 return JS_TRUE;
8064             }
8065         }
8067         lastobj = obj;
8068     } while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL);
8070     printable = js_ValueToPrintableString(cx, name);
8071     if (printable) {
8072         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR,
8073                                      js_GetErrorMessage, NULL,
8074                                      JSMSG_UNDEFINED_XML_NAME, printable);
8075     }
8076     return JS_FALSE;
8079 JSBool
8080 js_GetXMLProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
8082     return GetProperty(cx, obj, name, vp);
8085 JSBool
8086 js_SetXMLProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
8088     return PutProperty(cx, obj, name, vp);
8091 static JSXML *
8092 GetPrivate(JSContext *cx, JSObject *obj, const char *method)
8094     JSXML *xml;
8096     xml = (JSXML *) JS_GetInstancePrivate(cx, obj, &js_XMLClass, NULL);
8097     if (!xml) {
8098         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
8099                              JSMSG_INCOMPATIBLE_METHOD,
8100                              js_XML_str, method, OBJ_GET_CLASS(cx, obj)->name);
8101     }
8102     return xml;
8105 JSBool
8106 js_GetXMLDescendants(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
8108     JSXML *xml, *list;
8110     xml = GetPrivate(cx, obj, "descendants internal method");
8111     if (!xml)
8112         return JS_FALSE;
8114     list = Descendants(cx, xml, id);
8115     if (!list)
8116         return JS_FALSE;
8117     *vp = OBJECT_TO_JSVAL(list->object);
8118     return JS_TRUE;
8121 JSBool
8122 js_DeleteXMLListElements(JSContext *cx, JSObject *listobj)
8124     JSXML *list;
8125     uint32 n;
8126     jsval junk;
8128     list = (JSXML *) JS_GetPrivate(cx, listobj);
8129     for (n = list->xml_kids.length; n != 0; --n) {
8130         if (!DeleteProperty(cx, listobj, INT_TO_JSID(0), &junk))
8131             return JS_FALSE;
8132     }
8133     return JS_TRUE;
8136 JSBool
8137 js_FilterXMLList(JSContext *cx, JSObject *obj, jsbytecode *pc, jsval *vp)
8139     JSBool ok, match;
8140     JSStackFrame *fp;
8141     JSObject *scobj, *listobj, *resobj, *withobj, *kidobj;
8142     JSXML *xml, *list, *result, *kid;
8143     JSXMLArrayCursor cursor;
8145     ok = JS_EnterLocalRootScope(cx);
8146     if (!ok)
8147         return JS_FALSE;
8149     /* All control flow after this point must exit via label out or bad. */
8150     fp = cx->fp;
8151     scobj = fp->scopeChain;
8152     withobj = NULL;
8153     xml = GetPrivate(cx, obj, "filtering predicate operator");
8154     if (!xml)
8155         goto bad;
8157     if (xml->xml_class == JSXML_CLASS_LIST) {
8158         list = xml;
8159     } else {
8160         listobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
8161         if (!listobj)
8162             goto bad;
8163         list = (JSXML *) JS_GetPrivate(cx, listobj);
8164         ok = Append(cx, list, xml);
8165         if (!ok)
8166             goto out;
8167     }
8169     resobj = js_NewXMLObject(cx, JSXML_CLASS_LIST);
8170     if (!resobj)
8171         goto bad;
8172     result = (JSXML *) JS_GetPrivate(cx, resobj);
8174     /* Hoist the scope chain update out of the loop over kids. */
8175     withobj = js_NewWithObject(cx, NULL, scobj, -1);
8176     if (!withobj)
8177         goto bad;
8178     fp->scopeChain = withobj;
8180     XMLArrayCursorInit(&cursor, &list->xml_kids);
8181     while ((kid = (JSXML *) XMLArrayCursorNext(&cursor)) != NULL) {
8182         kidobj = js_GetXMLObject(cx, kid);
8183         if (!kidobj)
8184             break;
8185         OBJ_SET_PROTO(cx, withobj, kidobj);
8186         ok = js_Interpret(cx, pc, vp) && js_ValueToBoolean(cx, *vp, &match);
8187         if (ok && match)
8188             ok = Append(cx, result, kid);
8189         if (!ok)
8190             break;
8191     }
8192     XMLArrayCursorFinish(&cursor);
8193     if (!ok)
8194         goto out;
8195     if (kid)
8196         goto bad;
8198     *vp = OBJECT_TO_JSVAL(resobj);
8200 out:
8201     if (withobj) {
8202         fp->scopeChain = scobj;
8203         JS_SetPrivate(cx, withobj, NULL);
8204     }
8205     JS_LeaveLocalRootScope(cx);
8206     return ok;
8207 bad:
8208     ok = JS_FALSE;
8209     goto out;
8212 JSObject *
8213 js_ValueToXMLObject(JSContext *cx, jsval v)
8215     return ToXML(cx, v);
8218 JSObject *
8219 js_ValueToXMLListObject(JSContext *cx, jsval v)
8221     return ToXMLList(cx, v);
8224 JSObject *
8225 js_CloneXMLObject(JSContext *cx, JSObject *obj)
8227     uintN flags;
8228     JSXML *xml;
8230     if (!GetXMLSettingFlags(cx, &flags))
8231         return NULL;
8232     xml = (JSXML *) JS_GetPrivate(cx, obj);
8233     if (flags & (XSF_IGNORE_COMMENTS |
8234                  XSF_IGNORE_PROCESSING_INSTRUCTIONS |
8235                  XSF_IGNORE_WHITESPACE)) {
8236         xml = DeepCopy(cx, xml, NULL, flags);
8237         if (!xml)
8238             return NULL;
8239         return xml->object;
8240     }
8241     return NewXMLObject(cx, xml);
8244 JSObject *
8245 js_NewXMLSpecialObject(JSContext *cx, JSXMLClass xml_class, JSString *name,
8246                        JSString *value)
8248     uintN flags;
8249     JSObject *obj;
8250     JSXML *xml;
8251     JSXMLQName *qn;
8253     if (!GetXMLSettingFlags(cx, &flags))
8254         return NULL;
8256     if ((xml_class == JSXML_CLASS_COMMENT &&
8257          (flags & XSF_IGNORE_COMMENTS)) ||
8258         (xml_class == JSXML_CLASS_PROCESSING_INSTRUCTION &&
8259          (flags & XSF_IGNORE_PROCESSING_INSTRUCTIONS))) {
8260         return js_NewXMLObject(cx, JSXML_CLASS_TEXT);
8261     }
8263     obj = js_NewXMLObject(cx, xml_class);
8264     if (!obj)
8265         return NULL;
8266     xml = (JSXML *) JS_GetPrivate(cx, obj);
8267     if (name) {
8268         qn = js_NewXMLQName(cx, cx->runtime->emptyString, NULL, name);
8269         if (!qn)
8270             return NULL;
8271         xml->name = qn;
8272     }
8273     xml->xml_value = value;
8274     return obj;
8277 JSString *
8278 js_MakeXMLCDATAString(JSContext *cx, JSString *str)
8280     return MakeXMLCDATAString(cx, NULL, str);
8283 JSString *
8284 js_MakeXMLCommentString(JSContext *cx, JSString *str)
8286     return MakeXMLCommentString(cx, NULL, str);
8289 JSString *
8290 js_MakeXMLPIString(JSContext *cx, JSString *name, JSString *str)
8292     return MakeXMLPIString(cx, NULL, name, str);
8295 #endif /* JS_HAS_XML_SUPPORT */