/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "avmplus.h" namespace avmplus { XMLListObject::XMLListObject(XMLListClass *type, Atom tObject, const Multiname* tProperty) : ScriptObject(type->ivtable(), type->prototypePtr()) #ifdef DEBUGGER , m_created(false) , m_flushBeforeConstructionComplete(false) #endif , m_targetObject(tObject) , m_appended(false) , m_children(type->gc(), 0) { if (tProperty) m_targetProperty = *tProperty; // unnecessary: the default ctor for HeapMultiname (and Multiname) init to an anyName //else // should be equivalent to a null m_targetProperty // m_targetProperty.setAnyName(); #ifdef DEBUGGER m_created = true; if (m_flushBeforeConstructionComplete) { registerDependentObjects(getSampler()); getSampler()->recordObjectReallocation(this); } #endif } ////////////////////////////////////////////////////////////////////// // E4X Section 9.2.1 below (internal methods) ////////////////////////////////////////////////////////////////////// // this = argv[0] (ignored) // arg1 = argv[1] // argN = argv[argc] Atom XMLListObject::callProperty(const Multiname* multiname, int argc, Atom* argv) { // sec 11.2.2.1 CallMethod(r,args) Atom f = getDelegate()->getMultinameProperty(multiname); if (f == undefinedAtom) { f = getMultinameProperty(multiname); // If our method returned is a 0 element XMLList, it means that we did not // find a matching property for this method name. In this case, if our XMLList // has one child, we get the child and callproperty on the child object. // This allows node elements to be treated as simple strings even if they // are XML or XMLList objects. See 11.2.2.1 in the E4X spec for CallMethod. if (AvmCore::isXMLList(f) && !AvmCore::atomToXMLList(f)->_length() && (this->_length() == 1)) { XMLObject *x0 = _getAt(0); return x0->callProperty (multiname, argc, argv); } } argv[0] = atom(); // replace receiver return avmplus::op_call(toplevel(), f, argc, argv); } Atom XMLListObject::getMultinameProperty(const Multiname* m) const { AvmCore *core = this->core(); Toplevel* toplevel = this->toplevel(); // step 1 - We have an integer argument - direct child lookup if (!m->isAnyName() && !m->isAttr()) { uint32_t index; if (!m->isAnyName() && m->getName()->parseIndex(index)) { return getUintProperty (index); } } // step 2 //QNameObject* qname = new (core->GetGC()) QNameObject(toplevel->qnameClass(), *m); XMLListObject *l = XMLListObject::create(core->GetGC(), toplevel->xmlListClass(), this->atom(), m); // step 3 for (uint32_t i = 0; i < _length(); i++) { E4XNode* node = _getNodeAt(i); if (node->getClass() == E4XNode::kElement) { // step 3ai XMLObject* xml = _getAt(i); Atom gq = xml->getMultinameProperty(m); if (AvmCore::atomToXML(gq)) { XMLObject *x = AvmCore::atomToXMLObject(gq); if (x && x->_length()) l->_append (gq); } else { XMLListObject *xl = AvmCore::atomToXMLList(gq); if (xl && xl->_length()) { l->_append (gq); } } } } return l->atom(); } void XMLListObject::setMultinameProperty(const Multiname* m, Atom V) { Toplevel *toplevel = this->toplevel(); uint32_t i; if (!m->isAnyName() && !m->isAttr() && m->getName()->parseIndex(i)) { setUintProperty (i, V); return; } // step 3 else if (_length() <= 1) { //step3a if (_length() == 0) { Atom r = this->_resolveValue(); if (AvmCore::isNull(r)) return; if (AvmCore::isXML(r) && (AvmCore::atomToXML(r)->_length() != 1)) return; if (AvmCore::isXMLList(r) && (AvmCore::atomToXMLList(r)->_length() != 1)) return; this->_append (r); } XMLObject *x = _getAt (0); x->setMultinameProperty(m, V); } else if (_length() > 1) { toplevel->throwTypeError(kXMLAssigmentOneItemLists); } #ifdef _DEBUG // The only thing allowed in XMLLists is XML objects for (uint32_t z = 0; z < _length(); z++) { AvmAssert(AvmCore::isXML(m_children.get(z))); } #endif return; } bool XMLListObject::deleteMultinameProperty(const Multiname* m) { if (!m->isAnyName() && !m->isAttr()) { Stringp name = m->getName(); uint32_t index; if (name->parseIndex(index)) { return this->delUintProperty (index); } } for (uint32_t i = 0; i < numChildren(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *xm = _getAt(i); xm->deleteMultinameProperty(m); } } return true; } Atom XMLListObject::getDescendants(const Multiname* m) const { AvmCore *core = this->core(); XMLListObject *l = XMLListObject::create(core->GetGC(), toplevel()->xmlListClass()); for (uint32_t i = 0; i < numChildren(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *xm = _getAt(i); Atom dq = xm->getDescendants (m); XMLListObject *dqx = AvmCore::atomToXMLList(dq); if (dqx && dqx->_length()) { l->_append (dq); } } } return l->atom(); } // E4X 9.2.1.1, page 21 Atom XMLListObject::getAtomProperty(Atom P) const { Multiname m; toplevel()->ToXMLName (P, m); return getMultinameProperty(&m); } // E4X 9.2.1.2, pg 21 - [[PUT]] void XMLListObject::setAtomProperty(Atom P, Atom V) { Multiname m; toplevel()->ToXMLName (P, m); setMultinameProperty(&m, V); } Atom XMLListObject::getUintProperty(uint32_t index) const { if (index < _length()) { return _getAt(index)->atom(); } else { return undefinedAtom; } } void XMLListObject::setUintProperty(uint32_t i, Atom V) { AvmCore* core = this->core(); Toplevel* toplevel = this->toplevel(); Atom r = nullStringAtom; fixTargetObject(); // step 2a if (!AvmCore::isNull(this->m_targetObject)) { if (AvmCore::isXML(m_targetObject)) { r = AvmCore::atomToXMLObject(m_targetObject)->_resolveValue(); } else if (AvmCore::isXMLList(m_targetObject)) { r = AvmCore::atomToXMLList(m_targetObject)->_resolveValue(); } if (AvmCore::isNull(r)) return; } // step 2c if (i >= _length()) { // step 2 c i if (AvmCore::isXMLList(r)) { XMLListObject *xl = AvmCore::atomToXMLList(r); if (xl->_length() != 1) return; else r = xl->_getAt(0)->atom(); // r should now be an XMLObject atom from this point forward } // step 2cii - newer spec XMLObject *rx = AvmCore::atomToXMLObject(r); if (rx && rx->getClass() != E4XNode::kElement) return; E4XNode *y = 0; // step 2 c iii if (m_targetProperty.isAttr()) { Atom attributesExist = rx->getMultinameProperty(m_targetProperty); if (AvmCore::isXMLList(attributesExist) && (AvmCore::atomToXMLList(attributesExist)->_length() > 0)) return; y = AttributeE4XNode::create(core->GetGC(), AvmCore::atomToXML(r), 0); y->setQName (core, m_targetProperty); // warning: it looks like this is sent prior to the attribute being added. rx->nonChildChanges(core->kattributeAdded, this->m_targetProperty.getName()->atom()); } // if targetProperty is null or targetProperty.localName = "*" else if (m_targetProperty.isAnyName() || // Added because of bug 145184 - appendChild with a text node not working right // Also handle attribute nodes ((AvmCore::isXML(V) && AvmCore::atomToXML(V)->getClass() == E4XNode::kAttribute)) || ((AvmCore::isXML(V) && AvmCore::atomToXML(V)->getClass() == E4XNode::kText))) { y = TextE4XNode::create(core->GetGC(), AvmCore::atomToXML(r), 0); } else { y = ElementE4XNode::create(core->GetGC(), AvmCore::atomToXML(r)); y->setQName (core, this->m_targetProperty); } i = _length(); // step 2.c.viii if (y->getClass() != E4XNode::kAttribute) { AvmAssert(y->getParent() == (rx ? rx->getNode() : 0)); if (y->getParent() != NULL) { // y->m_parent = r = XMLObject * E4XNode *parent = y->getParent(); AvmAssert(parent != 0); uint32_t j = 0; if (i > 0) { // step 2 c vii 1 ii // spec says "y.[[parent]][j] is not the same object as x[i-1]" and I'm // assuming that means pointer equality and not "equals" comparison while ((j < (parent->_length() - 1)) && parent->_getAt (j) != _getNodeAt(i - 1)) { j++; } j = j + 1; // slightly different than spec but our insert call does not have a + 1 on it } else { j = parent->_length(); // slightly different than spec but our insert call does not have a + 1 on it } // These two lines are equivalent to _insert which takes // an Atom since we know that y is an XMLObject/E4XNode y->setParent (parent); parent->insertChild (j, y); } if (AvmCore::isXML(V)) { Multiname mV; if (AvmCore::atomToXML(V)->getQName(&mV, core->findPublicNamespace())) y->setQName (core, &mV); } else if (AvmCore::isXMLList(V)) { //ERRATA : 9.2.1.2 step 2(c)(vii)(3) what is V.[[PropertyName]]? s.b. [[TargetProperty]] if (!AvmCore::atomToXMLList(V)->m_targetProperty.isAnyName()) y->setQName (core, AvmCore::atomToXMLList(V)->m_targetProperty); } } this->_appendNode (y); } // step 2d if (AvmCore::atomToXMLList(V)) { XMLListObject *src = AvmCore::atomToXMLList(V); if ((src->_length() == 1) && src->_getNodeAt(0)->getClass() & (E4XNode::kText | E4XNode::kAttribute)) { V = core->string(V)->atom(); } } else if (AvmCore::atomToXML(V)) { E4XNode *v = AvmCore::atomToXML(V); if (v->getClass() & (E4XNode::kText | E4XNode::kAttribute)) { // This string is converted into a XML object below in step 2(g)(iii) V = core->string(V)->atom(); } } else { V = core->string(V)->atom(); } // step 2e E4XNode *xi = _getNodeAt(i); if (xi->getClass() == E4XNode::kAttribute) { XMLObject* xio = _getAt(i); XMLObject *parent = xio->getParent(); Multiname mxi; xio->getQName (&mxi); parent->setMultinameProperty(&mxi, V); Atom attr = parent->getMultinameProperty(&mxi); XMLListObject *attrx = AvmCore::atomToXMLList(attr); // x[i] = attr[0]; m_children.set(i, attrx->m_children.get(0)); } // step 2f else if (AvmCore::isXMLList(V)) { // create a shallow copy c of V XMLListObject *src = AvmCore::atomToXMLList(V); XMLListObject *c = XMLListObject::create(core->GetGC(), toplevel->xmlListClass()); c->m_children.ensureCapacity(src->numChildren()); for (uint32_t i2 = 0; i2 < src->numChildren(); i2++) c->m_children.add(src->m_children.get(i2)); E4XNode *parent = _getNodeAt(i)->getParent(); // step 2 f iii if (parent) { uint32_t q = 0; while (q < parent->numChildren()) { if (parent->_getAt(q) == _getNodeAt(i)) { parent->_replace (core, toplevel, q, c->atom()); for (uint32_t j = 0; j < c->_length(); j++) { c->m_children.set(j, AvmCore::genericObjectToAtom(parent->_getAt(q + j))); } break; } q++; } } // resize our buffer to ([[Length]] + c.[[Length]]) (2 f vi) bool notify = (parent && XMLObject::notifyNeeded(parent)); XMLObject* prior = (notify) ? _getAt(i) : 0; XMLObject* target = (notify) ? XMLObject::create(core->GetGC(), toplevel->xmlClass(), parent) : 0; m_children.removeAt(i); for (uint32_t i2 = 0; i2 < src->numChildren(); i2++) { m_children.insert(i + i2, c->m_children.get(i2)); E4XNode* node = c->_getNodeAt(i2); if (notify && (parent == node->getParent())) { XMLObject* obj = c->_getAt(i2); if (i2 == 0) { // @todo; is this condition ever true? if (node != prior->getNode()) target->childChanges(core->knodeChanged, obj->atom(), prior->getNode()); } else { target->childChanges(core->knodeAdded, obj->atom()); } } } } // step 2g // !!@ always going into this step supports how Rhino acts with setting XMLList props //var y = new XMLList("onetwo"); //y[0] = "five"; //print(y); // one which is an element node is converted to a text node with "five" as it's label // !!@ but in this case, the node's contents are replaced and not the node itself... // o = ABCD; // o.i[1] = "BB"; // print(o); else if (AvmCore::isXML(V) || xi->getClass() & (E4XNode::kText | E4XNode::kCDATA | E4XNode::kComment | E4XNode::kProcessingInstruction)) { E4XNode *parent = _getNodeAt(i)->getParent(); if (parent) { // search our parent for a match between a child and x[i] uint32_t q = 0; while (q < parent->numChildren()) { if (parent->_getAt(q) == _getNodeAt(i)) { parent->_replace (core, toplevel, q, V); XMLObject *xo = XMLObject::create(core->GetGC(), toplevel->xmlClass(), parent->_getAt(q)); V = xo->atom(); if (XMLObject::notifyNeeded(parent)) { XMLObject *po = XMLObject::create(core->GetGC(), toplevel->xmlClass(), parent); po->childChanges(core->knodeAdded, xo->atom()); } break; } q++; } } // From E4X errrata //9.2.1.2 step 2(g)(iii) erratum: _V_ may not be of type XML, but all index-named // properties _x[i]_ in an XMLList _x_ must be of type XML, according to // 9.2.1.1 Overview and other places in the spec. // Thanks to 2(d), we know _V_ (*vp here) is either a string or an // XML/XMLList object. If _V_ is a string, call ToXML on it to satisfy // the constraint before setting _x[i] = V_. if (AvmCore::isXML(V)) m_children.set(i, V); else m_children.set(i, xmlClass()->ToXML (V)); } // step 2h else { _getAt(i)->setStringProperty(core->kAsterisk, V); } } bool XMLListObject::delUintProperty(uint32_t index) { if (index >= _length()) { return true; } else { XMLObject *xi = _getAt(index); XMLObject *px = xi->getParent(); if (px != NULL) { if (xi->getClass() == E4XNode::kAttribute) { Multiname mx; xi->getQName (&mx); px->deleteMultinameProperty(&mx); } else { // let q be the property of parent, where parent[q] is the same object as x[i]. int q = xi->childIndex (); E4XNode *x = px->getNode()->_getAt(q); px->getNode()->_deleteByIndex (q); if (XMLObject::notifyNeeded(px->getNode()) && x->getClass() == E4XNode::kElement) { AvmCore *core = this->core(); XMLObject *r = XMLObject::create(core->GetGC(), xmlClass(), x); px->childChanges(core->knodeRemoved, r->atom()); } } } // delete index from this list m_children.removeAt(index); return true; } } // E4X 9.2.1.3, pg 23 bool XMLListObject::deleteAtomProperty(Atom P) { Multiname m; toplevel()->ToXMLName (P, m); return deleteMultinameProperty(&m); } bool XMLListObject::hasUintProperty(uint32_t index) const { return (index < _length()); } bool XMLListObject::hasMultinameProperty(const Multiname* m) const { if (!m->isAnyName() && !m->isAttr()) { Stringp name = m->getName(); uint32_t index; if (name->parseIndex(index)) { return (index < _length()); } } for (uint32_t i = 0; i < numChildren(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *xm = _getAt(i); if (xm->hasMultinameProperty(m)) return true; } } return false; } // E4X 9.2.1.5, pg 24 bool XMLListObject::hasAtomProperty(Atom P) const { Multiname m; toplevel()->ToXMLName (P, m); return hasMultinameProperty(&m); } /* This code is extracted from the former _append(E4XNode*) method. It is unclear why this code is required, but I left it in to not break any existing code. It does not make sense to execute the code for every call to _appendNode(). That method now sets m_appended, and code that accesses m_targetObject and/or m_targetProperty needs to call fixTargetObject() before. */ void XMLListObject::fixTargetObject() const { if (m_appended && numChildren() > 0) { AvmCore *core = this->core(); E4XNode* v = _getNodeAt(numChildren()-1); if (v->getParent()) { XMLObject* target = AvmCore::atomToXMLObject(m_targetObject); if (target && target->getNode() != v->getParent()) { // create only a new target object if the old one does not match XMLObject *p = XMLObject::create(core->GetGC(), toplevel()->xmlClass(), v->getParent()); setTargetObject(p->atom()); } } else setTargetObject(nullObjectAtom); if (v->getClass() != E4XNode::kProcessingInstruction) { Multiname m; if (v->getQName (&m, core->findPublicNamespace())) this->m_targetProperty = m; } m_appended = false; } } void XMLListObject::_appendNode(E4XNode *v) { m_children.add(AvmCore::genericObjectToAtom(v)); m_appended = true; } // E4X 9.2.1.6, pg 24 void XMLListObject::_append(Atom V) { // !!@ what the docs say // Atom children = this->get ("*"); // children->put (children->length(), child) // return x; XMLListObject *v = AvmCore::atomToXMLList(V); if (v) { setTargetObject(v->m_targetObject); this->m_targetProperty = v->m_targetProperty; m_appended = false; if (v->_length()) { m_children.ensureCapacity(m_children.length() + v->_length()); for (uint32_t j = 0; j < v->_length(); j++) { m_children.add(v->m_children.get(j)); } } } else { XMLObject* v = AvmCore::atomToXMLObject(V); if (v) { m_appended = true; m_children.add(V); } } } // E4X 9.2.1.7, pg 25 XMLListObject *XMLListObject::_deepCopy () const { AvmCore *core = this->core(); fixTargetObject(); XMLListObject *l = XMLListObject::create(core->GetGC(), toplevel()->xmlListClass(), m_targetObject, m_targetProperty); l->m_children.ensureCapacity(numChildren()); for (uint32_t i = 0; i < numChildren(); i++) { l->m_children.add(_getAt(i)->_deepCopy()->atom()); } return l; } // E4X 9.2.1.8, pg 25 XMLListObject *XMLListObject::AS3_descendants (Atom P) { Multiname m; toplevel()->ToXMLName (P, m); return AvmCore::atomToXMLList (getDescendants (&m)); } // E4X 9.2.1.9, pg 26 Atom XMLListObject::_equals(Atom V) const { AvmCore *core = this->core(); // null or "" return false if ((V == undefinedAtom) && (_length() == 0)) return trueAtom; if (AvmCore::isXMLList(V)) { XMLListObject *v = AvmCore::atomToXMLList(V); if (_length() != v->_length()) return falseAtom; for (uint32_t i = 0; i < _length(); i++) { Atom a1 = m_children.get(i); Atom a2 = v->m_children.get(i); if ((a1 != a2) && core->equals (_getAt(i)->atom(), v->_getAt(i)->atom()) == falseAtom) return falseAtom; } return trueAtom; } else if (_length() == 1) { return core->equals(_getAt(0)->atom(), V); } return falseAtom; } // E4X 9.2.1.10, pg 26 Atom XMLListObject::_resolveValue() { if (_length() > 0) return this->atom(); AvmCore *core = this->core(); core->stackCheck(toplevel()); fixTargetObject(); if (AvmCore::isNull(m_targetObject) || (m_targetProperty.isAttr()) || (m_targetProperty.isAnyName())) { return nullObjectAtom; } Atom base = nullObjectAtom; XMLObject *x = AvmCore::atomToXMLObject(m_targetObject); if (x) { base = x->_resolveValue(); } XMLListObject *xl = AvmCore::atomToXMLList(m_targetObject); if (xl) { base = xl->_resolveValue (); } if (AvmCore::isNull(base)) return nullObjectAtom; XMLListObject *target = 0; if (AvmCore::isXML(base)) { target = AvmCore::atomToXMLList(AvmCore::atomToXMLObject(base)->getMultinameProperty(m_targetProperty)); } else if (AvmCore::isXMLList(base)) { target = AvmCore::atomToXMLList(AvmCore::atomToXMLList(base)->getMultinameProperty(m_targetProperty)); } else { AvmAssert(0);// base should be an XML or XMLList always } if (!target) return nullObjectAtom; if (!target->_length()) { if (AvmCore::isXMLList(base) && AvmCore::atomToXMLList(base)->_length() > 1) { toplevel()->throwTypeError(kXMLAssigmentOneItemLists); return nullObjectAtom; } if (AvmCore::isXML(base)) { AvmCore::atomToXMLObject(base)->setMultinameProperty(m_targetProperty, core->kEmptyString->atom()); return AvmCore::atomToXMLObject(base)->getMultinameProperty(m_targetProperty); } else if (AvmCore::isXMLList(base)) { AvmCore::atomToXMLList(base)->setMultinameProperty(m_targetProperty, core->kEmptyString->atom()); return AvmCore::atomToXMLList(base)->getMultinameProperty(m_targetProperty); } else { AvmAssert(0); // b should be one or the other at this point since target is non-null } } return target->atom(); } XMLObject *XMLListObject::_getAt (uint32_t i) const { if (i >= _length()) return 0; Atom a = m_children.get(i); XMLObject* obj = AvmCore::atomToXMLObject(a); if (!obj) { obj = XMLObject::create(core()->GetGC(), toplevel()->xmlClass(), (E4XNode*) AvmCore::atomToGenericObject(a)); m_children.set(i, obj->atom()); } return obj; } E4XNode *XMLListObject::_getNodeAt (uint32_t i) const { if (i >= _length()) return 0; Atom a = m_children.get(i); XMLObject* obj = AvmCore::atomToXMLObject(a); if (obj) return obj->getNode(); return (E4XNode*) AvmCore::atomToGenericObject(a); } // E4X 12.2, page 59 // Support for for-in, for-each for XMLListObjects Atom XMLListObject::nextName(int index) { AvmAssert(index > 0); if (index <= (int)_length()) { AvmCore *core = this->core(); return core->internInt (index-1)->atom(); } else { return nullStringAtom; } } Atom XMLListObject::nextValue(int index) { AvmAssert(index > 0); if (index <= (int)_length()) return _getAt (index-1)->atom(); else return undefinedAtom; } int XMLListObject::nextNameIndex(int index) { AvmAssert(index >= 0); // XMLList types return the same number of indexes as children // We return a 1-N iterator (and then subtract off the one in // the nextName and nextValue functions). if (index < (int) _length()) return index + 1; else return 0; } // E4X 13.5.4.21, page 93 which just refers to 10.2, page 31 (XMLList case) void XMLListObject::__toXMLString(PrintWriter &output, Atom, int /*indentLevel*/) { AvmCore *core = this->core(); // For i = 0 to x.[[length]-1 // if (xml.prettyprinting == true and i is not equal to 0) // s = s + line terminator // s = s + toxmlstring(x[i], ancestor namespace) for (uint32_t i = 0; i < _length(); i++) { // iterate over entire array. If any prop is an "element" type, return false XMLObject *xm = _getAt(i); if (xm) { // !!@ This does not seem be affected by the prettyPrinting flag // but always occurs (comparing to Rhino) if (/*xmlClass()->getPrettyPrinting() && */i) output << "\n"; NamespaceList AncestorNamespaces(core->GetGC(), kListInitialCapacity); xm->__toXMLString(output, AncestorNamespaces, 0); } } return; } ////////////////////////////////////////////////////////////////////// // E4X Section 13.5.4 below (AS methods) ////////////////////////////////////////////////////////////////////// // E4X 13.5.4.2, pg 88 XMLListObject *XMLListObject::AS3_attribute (Atom arg) { // name= ToAttributeName (attributeName); // return [[get]](name) return AvmCore::atomToXMLList(getAtomProperty(toplevel()->ToAttributeName(arg)->atom())); } // E4X 13.5.4.3, pg 88 XMLListObject *XMLListObject::AS3_attributes () { return AvmCore::atomToXMLList(getAtomProperty(toplevel()->ToAttributeName(core()->kAsterisk)->atom())); } // E4X 13.5.4.4, pg 88 XMLListObject *XMLListObject::AS3_child (Atom propertyName) { AvmCore *core = this->core(); XMLListObject *m = XMLListObject::create(core->GetGC(), toplevel()->xmlListClass(), this->atom()); for (uint32_t i = 0; i < _length(); i++) { XMLObject *x = _getAt(i); XMLListObject *rxl = x->child (propertyName); if (rxl && rxl->_length()) { m->_append (rxl->atom()); } } return m; } XMLListObject *XMLListObject::AS3_children () { return AvmCore::atomToXMLList(getStringProperty(core()->kAsterisk)); } XMLListObject *XMLListObject::AS3_comments () { AvmCore *core = this->core(); XMLListObject *m = XMLListObject::create(core->GetGC(), toplevel()->xmlListClass(), this->atom()); for (uint32_t i = 0; i < _length(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *x = _getAt(i); XMLListObject *rxl = x->comments(); if (rxl && rxl->_length()) { m->_append (rxl->atom()); } } } return m; } bool XMLListObject::AS3_contains (Atom value) { AvmCore *core = this->core();; for (uint32_t i = 0; i < _length(); i++) { // Spec says "comparison l[i] == value)" which is different than _equals if (core->equals(_getAt(i)->atom(), value) == trueAtom) return true; } return false; } XMLListObject *XMLListObject::AS3_copy () { return _deepCopy(); } // E4X 13.5.4.10, pg 90 XMLListObject *XMLListObject::AS3_elements (Atom name) // name defaults to '*' { AvmCore *core = this->core(); Toplevel* toplevel = this->toplevel(); Multiname m; toplevel->ToXMLName (name, m); XMLListObject *xl = XMLListObject::create(core->GetGC(), toplevel->xmlListClass(), this->atom(), &m); for (uint32_t i = 0; i < _length(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *x = _getAt(i); XMLListObject *rxl = x->elements(name); if (rxl && rxl->_length()) { xl->_append (rxl->atom()); } } } return xl; } // E4X 13.5.4.11, pg 90 bool XMLListObject::XMLList_AS3_hasOwnProperty (Atom P) { if (hasAtomProperty(P)) return true; // If x has a property with name ToString(P), return true; // !!@ (prototype different than regular object?? return false; } // E4X 13.5.4.12, pg 90 bool XMLListObject::AS3_hasComplexContent () { if (_length() == 0) return false; if (_length() == 1) { return _getNodeAt(0)->hasComplexContent(); } for (uint32_t i = 0; i < _length(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { return true; } } return false; } // E4X 13.5.4.13, pg 91 bool XMLListObject::AS3_hasSimpleContent () { if (!_length()) { return true; } else if (_length() == 1) { return _getNodeAt(0)->hasSimpleContent(); } else { for (uint32_t i = 0; i < _length(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { return false; } } } return true; } uint32_t XMLListObject::AS3_length() const { return _length(); } XMLListObject *XMLListObject::AS3_normalize () { AvmCore *core = this->core(); uint32_t i = 0; while (i < _length()) { E4XNode* xn = _getNodeAt(i); if (xn->getClass() == E4XNode::kElement) { XMLObject *x = _getAt(i); x->normalize(); i++; } else if ((xn->getClass() & (E4XNode::kText | E4XNode::kCDATA))) { while (((i + 1) < _length()) && ((_getNodeAt(i + 1)->getClass() & (E4XNode::kText | E4XNode::kCDATA)))) { xn->setValue (core->concatStrings(xn->getValue(), _getNodeAt(i + 1)->getValue())); deleteAtomProperty(core->intToAtom(i + 1)); } if (xn->getValue()->length() == 0) { deleteAtomProperty(core->intToAtom(i)); } else { i++; } } else { i++; } } return this; } Atom XMLListObject::AS3_parent () { if (!_length()) return undefinedAtom; E4XNode *parent = _getNodeAt(0)->getParent(); for (uint32_t i = 1; i < _length(); i++) { E4XNode *p = _getNodeAt(i)->getParent(); if (parent != p) return undefinedAtom; } if (parent) return _getAt(0)->getParent()->atom(); else return undefinedAtom; } XMLListObject *XMLListObject::AS3_processingInstructions (Atom name) // name defaults to '*' { AvmCore *core = this->core(); XMLListObject *m = XMLListObject::create(core->GetGC(), toplevel()->xmlListClass(), this->atom()); for (uint32_t i = 0; i < _length(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *x = _getAt(i); XMLListObject *rxl = x->processingInstructions(name); if (rxl && rxl->_length()) { m->_append (rxl->atom()); } } } return m; } bool XMLListObject::XMLList_AS3_propertyIsEnumerable(Atom P) // NOT virtual, NOT an override { double index = AvmCore::number(P); if ((index >= 0.0) && (index < _length())) return true; return false; } XMLListObject *XMLListObject::AS3_text () { AvmCore *core = this->core(); XMLListObject *m = XMLListObject::create(core->GetGC(), toplevel()->xmlListClass(), this->atom()); for (uint32_t i = 0; i < _length(); i++) { if (_getNodeAt(i)->getClass() == E4XNode::kElement) { XMLObject *x = _getAt(i); XMLListObject *rxl = x->text(); if (rxl && rxl->_length()) { m->_append (rxl->atom()); } } } return m; } // E4X 10.1.2, page 28 Stringp XMLListObject::toString() { AvmCore *core = this->core(); if (hasSimpleContent ()) { Stringp output = core->kEmptyString; // s is the empty string // for all props in children array // if (class != comment or processing instruction) // s = s + toString(prop) for (uint32_t i = 0; i < _length(); i++) { E4XNode* xn = _getNodeAt(i); if ((xn->getClass() != E4XNode::kComment) && (xn->getClass() != E4XNode::kProcessingInstruction)) { XMLObject *xm = _getAt(i); output = core->concatStrings(output, xm->toString()); } } return output; } else { StringBuffer output(core); this->__toXMLString(output, nullStringAtom, 0); return core->newStringUTF8(output.c_str()); } } Stringp XMLListObject::AS3_toString() { return toString(); } String *XMLListObject::AS3_toXMLString () { StringBuffer output(core()); this->__toXMLString(output, nullStringAtom, 0); return core()->newStringUTF8(output.c_str()); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // Routines below are not in the spec but work if XMLList has one element ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// XMLObject *XMLListObject::AS3_addNamespace (Atom _namespace) { if (_length() == 1) { return _getAt(0)->addNamespace(_namespace); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("addNamespace")); return 0; } } XMLObject *XMLListObject::AS3_appendChild (Atom child) { if (_length() == 1) { return _getAt(0)->appendChild (child); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("appendChild")); return 0; } } int XMLListObject::AS3_childIndex () { if (_length() == 1) { return _getNodeAt(0)->childIndex (); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("childIndex")); return -1; } } ArrayObject *XMLListObject::AS3_inScopeNamespaces () { if (_length() == 1) { return _getAt(0)->inScopeNamespaces(); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("inScopeNamespaces")); return 0; } } Atom XMLListObject::AS3_insertChildAfter (Atom child1, Atom child2) { if (_length() == 1) { return _getAt(0)->insertChildAfter (child1, child2); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("insertChildAfter")); return undefinedAtom; } } Atom XMLListObject::AS3_insertChildBefore (Atom child1, Atom child2) { if (_length() == 1) { return _getAt(0)->insertChildBefore (child1, child2); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("insertChildBefore")); return undefinedAtom; } } Atom XMLListObject::AS3_name() { if (_length() == 1) { return _getAt(0)->name(); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("name")); return nullStringAtom; } } Atom XMLListObject::_namespace (Atom prefix, int argc) // prefix is optional { AvmAssert(argc == 0 || argc == 1); if (_length() == 1) { return _getAt(0)->_namespace(prefix, argc); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("namespace")); return nullStringAtom; } } Atom XMLListObject::AS3_localName () { if (_length() == 1) { return _getAt(0)->localName(); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("localName")); return nullStringAtom; } } ArrayObject *XMLListObject::AS3_namespaceDeclarations () { if (_length() == 1) { return _getAt(0)->namespaceDeclarations(); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("namespaceDeclarations")); return 0; } } String *XMLListObject::AS3_nodeKind () { // if our list has one element, return the nodeKind of the first element if (_length() == 1) { return _getNodeAt(0)->nodeKind(toplevel()); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("nodeKind")); return 0; } } XMLObject *XMLListObject::AS3_prependChild (Atom value) { if (_length() == 1) { return _getAt(0)->prependChild(value); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("prependChild")); return 0; } } XMLObject *XMLListObject::AS3_removeNamespace (Atom _namespace) { if (_length() == 1) { return _getAt(0)->removeNamespace (_namespace); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("removeNamespace")); return 0; } } XMLObject *XMLListObject::AS3_replace (Atom propertyName, Atom value) { if (_length() == 1) { return _getAt(0)->replace(propertyName, value); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("replace")); return 0; } } XMLObject *XMLListObject::AS3_setChildren (Atom value) { if (_length() == 1) { return _getAt(0)->setChildren (value); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("setChildren")); return 0; } } void XMLListObject::AS3_setLocalName (Atom name) { if (_length() == 1) { _getAt(0)->setLocalName (name); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("setLocalName")); } } void XMLListObject::AS3_setName (Atom name) { if (_length() == 1) { _getAt(0)->setName(name); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("setName")); } } void XMLListObject::AS3_setNamespace (Atom ns) { if (_length() == 1) { _getAt(0)->setNamespace (ns); } else { // throw type error toplevel()->throwTypeError(kXMLOnlyWorksWithOneItemLists, core()->toErrorString("setNamespace")); } } #ifdef XML_FILTER_EXPERIMENT XMLListObject * XMLListObject::filter (Atom propertyName, Atom value) { Multiname m; toplevel()->ToXMLName(propertyName, m); Multiname name; toplevel()->CoerceE4XMultiname(&m, name); // filter opcode experiment XMLListObject *l = XMLListObject::create(core()->gc, toplevel()->xmlListClass()); for (uint32_t i = 0; i < _length(); i++) { XMLObject *xm = _getAt(i); xm->_filter (l, name, value); } return l; } #endif // XML_FILTER_EXPERIMENT ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // reserved for now void XMLListObject::setNotification(ScriptObject* /*f*/) { // nop } ScriptObject* XMLListObject::getNotification() { return 0; } #ifdef DEBUGGER uint64_t XMLListObject::bytesUsedShallow() const { if (!m_created) { m_flushBeforeConstructionComplete = true; return 0; } uint64_t size = ScriptObject::bytesUsed(); size += m_children.bytesUsed(); return size; } uint64_t XMLListObject::bytesUsed() const { if (getSampler()->getSamplerType() == Sampler::SAMPLER_TYPE_AVMPLUS) { // To calculate the memory used by an XMLList, we add the memory taken // by each XML or E4XNode that we point to. // // FIXME As described in https://bugzilla.mozilla.org/show_bug.cgi?id=552307 , // this implementation can cause the same memory to be accounted for more // than once in the profiler. It is easy for the user to have multiple // XMLLists and XMLs that point into the same tree of E4XNodes. For now, // there is not much we can do about this. A possible fix is described in // https://bugzilla.mozilla.org/show_bug.cgi?id=558385 uint64_t size = ScriptObject::bytesUsed(); size += m_children.bytesUsed(); // It's possible -- likely, in fact -- that several XML elements in an // XMLList will actually be pointers into the same tree of E4XNodes. // We don't want to count their memory twice, so this hashtable keeps // track of the root E4XNodes whose memory we have already accounted // for. HeapHashtable* seenXmlRoots = HeapHashtable::create(gc()); for (uint32_t i=0, n=m_children.length(); igetNode(); else node = (E4XNode*)AvmCore::atomToGenericObject(child); if (node) { // find the root node of the tree E4XNode* root = node; while (root->getParent()) root = root->getParent(); Atom atomizedRoot = AvmCore::genericObjectToAtom(root); // Have we already seen that root node, as the root node of // some other XML object in this list? if (!seenXmlRoots->contains(atomizedRoot)) { seenXmlRoots->add(atomizedRoot, trueAtom); // Again, the XMLList might contain XMLObjects or it might // contain E4XNodes if (xmlobj) size += xmlobj->bytesUsed(); else size += node->bytesUsed(); } else { // We have already accounted for the memory taken by the // tree of E4XNodes, but we still need to take into // account the small amount of memory taken by the XMLObject // that points to that tree. if (xmlobj) size += xmlobj->bytesUsedShallow(); } } } delete seenXmlRoots; return size; } else { // For newer samplers, we report the bytes used only for this XMLListObject, not for // the underlying E4XNodes. We will report the memory used for E4XNodes individually, // eliminating problems with reporting the same memory more than once as described above. return bytesUsedShallow(); } } void XMLListObject::registerDependentObjects(IMemorySampler* memSampler) { if (!m_created) { m_flushBeforeConstructionComplete = true; return; } // Add the children list as a dependent object. memSampler->addDependentObject(this, &m_children); // Call the base class. ScriptObject::registerDependentObjects(memSampler); } #endif }