1/*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "JSHTMLCollection.h"
22
23#include "HTMLCollection.h"
24#include "HTMLOptionsCollection.h"
25#include "HTMLAllCollection.h"
26#include "JSDOMBinding.h"
27#include "JSHTMLAllCollection.h"
28#include "JSHTMLOptionsCollection.h"
29#include "JSNode.h"
30#include "JSNodeList.h"
31#include "Node.h"
32#include "StaticNodeList.h"
33#include <wtf/Vector.h>
34#include <wtf/text/AtomicString.h>
35
36using namespace JSC;
37
38namespace WebCore {
39
40static JSValue getNamedItems(ExecState* exec, JSHTMLCollection* collection, const Identifier& propertyName)
41{
42    Vector<RefPtr<Node> > namedItems;
43    collection->impl()->namedItems(identifierToAtomicString(propertyName), namedItems);
44
45    if (namedItems.isEmpty())
46        return jsUndefined();
47    if (namedItems.size() == 1)
48        return toJS(exec, collection->globalObject(), namedItems[0].get());
49
50    // FIXME: HTML5 specifies that this should be a DynamicNodeList.
51    // FIXME: HTML5 specifies that non-HTMLOptionsCollection collections should return
52    // the first matching item instead of a NodeList.
53    return toJS(exec, collection->globalObject(), StaticNodeList::adopt(namedItems).get());
54}
55
56// HTMLCollections are strange objects, they support both get and call,
57// so that document.forms.item(0) and document.forms(0) both work.
58static EncodedJSValue JSC_HOST_CALL callHTMLCollection(ExecState* exec)
59{
60    if (exec->argumentCount() < 1)
61        return JSValue::encode(jsUndefined());
62
63    // Do not use thisObj here. It can be the JSHTMLDocument, in the document.forms(i) case.
64    JSHTMLCollection* jsCollection = static_cast<JSHTMLCollection*>(exec->callee());
65    HTMLCollection* collection = jsCollection->impl();
66
67    // Also, do we need the TypeError test here ?
68
69    if (exec->argumentCount() == 1) {
70        // Support for document.all(<index>) etc.
71        bool ok;
72        UString string = exec->argument(0).toString(exec);
73        unsigned index = Identifier::toUInt32(string, ok);
74        if (ok)
75            return JSValue::encode(toJS(exec, jsCollection->globalObject(), collection->item(index)));
76
77        // Support for document.images('<name>') etc.
78        return JSValue::encode(getNamedItems(exec, jsCollection, Identifier(exec, string)));
79    }
80
81    // The second arg, if set, is the index of the item we want
82    bool ok;
83    UString string = exec->argument(0).toString(exec);
84    unsigned index = Identifier::toUInt32(exec->argument(1).toString(exec), ok);
85    if (ok) {
86        String pstr = ustringToString(string);
87        Node* node = collection->namedItem(pstr);
88        while (node) {
89            if (!index)
90                return JSValue::encode(toJS(exec, jsCollection->globalObject(), node));
91            node = collection->nextNamedItem(pstr);
92            --index;
93        }
94    }
95
96    return JSValue::encode(jsUndefined());
97}
98
99CallType JSHTMLCollection::getCallData(CallData& callData)
100{
101    callData.native.function = callHTMLCollection;
102    return CallTypeHost;
103}
104
105bool JSHTMLCollection::canGetItemsForName(ExecState*, HTMLCollection* collection, const Identifier& propertyName)
106{
107    Vector<RefPtr<Node> > namedItems;
108    collection->namedItems(identifierToAtomicString(propertyName), namedItems);
109    return !namedItems.isEmpty();
110}
111
112JSValue JSHTMLCollection::nameGetter(ExecState* exec, JSValue slotBase, const Identifier& propertyName)
113{
114    JSHTMLCollection* thisObj = static_cast<JSHTMLCollection*>(asObject(slotBase));
115    return getNamedItems(exec, thisObj, propertyName);
116}
117
118JSValue JSHTMLCollection::item(ExecState* exec)
119{
120    bool ok;
121    uint32_t index = Identifier::toUInt32(exec->argument(0).toString(exec), ok);
122    if (ok)
123        return toJS(exec, globalObject(), impl()->item(index));
124    return getNamedItems(exec, this, Identifier(exec, exec->argument(0).toString(exec)));
125}
126
127JSValue JSHTMLCollection::namedItem(ExecState* exec)
128{
129    return getNamedItems(exec, this, Identifier(exec, exec->argument(0).toString(exec)));
130}
131
132JSValue toJS(ExecState* exec, JSDOMGlobalObject* globalObject, HTMLCollection* collection)
133{
134    if (!collection)
135        return jsNull();
136
137    JSDOMWrapper* wrapper = getCachedWrapper(currentWorld(exec), collection);
138
139    if (wrapper)
140        return wrapper;
141
142    switch (collection->type()) {
143        case SelectOptions:
144            wrapper = CREATE_DOM_OBJECT_WRAPPER(exec, globalObject, HTMLOptionsCollection, collection);
145            break;
146        case DocAll:
147            wrapper = CREATE_DOM_OBJECT_WRAPPER(exec, globalObject, HTMLAllCollection, collection);
148            break;
149        default:
150            wrapper = CREATE_DOM_OBJECT_WRAPPER(exec, globalObject, HTMLCollection, collection);
151            break;
152    }
153
154    return wrapper;
155}
156
157} // namespace WebCore
158