1/*
2 * Copyright (C) 2016 Canon, Inc. All rights reserved.
3 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY CANON INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CANON INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#pragma once
28
29#include "JSDOMConvert.h"
30#include <JavaScriptCore/IteratorPrototype.h>
31#include <type_traits>
32
33namespace WebCore {
34
35void addValueIterableMethods(JSC::JSGlobalObject&, JSC::JSObject&);
36
37enum class JSDOMIteratorType { Set, Map };
38
39// struct IteratorTraits {
40// static constexpr JSDOMIteratorType type = [Map|Set];
41// using KeyType = [IDLType|void];
42// using ValueType = [IDLType];
43// };
44
45template<typename T, typename U = void> using EnableIfMap = typename std::enable_if<T::type == JSDOMIteratorType::Map, U>::type;
46template<typename T, typename U = void> using EnableIfSet = typename std::enable_if<T::type == JSDOMIteratorType::Set, U>::type;
47
48template<typename JSWrapper, typename IteratorTraits> class JSDOMIteratorPrototype : public JSC::JSNonFinalObject {
49public:
50 using Base = JSC::JSNonFinalObject;
51 using DOMWrapped = typename JSWrapper::DOMWrapped;
52
53 static JSDOMIteratorPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
54 {
55 JSDOMIteratorPrototype* prototype = new (NotNull, JSC::allocateCell<JSDOMIteratorPrototype>(vm.heap)) JSDOMIteratorPrototype(vm, structure);
56 prototype->finishCreation(vm, globalObject);
57 return prototype;
58 }
59
60 DECLARE_INFO;
61
62 static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
63 {
64 return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
65 }
66
67 static JSC::EncodedJSValue JSC_HOST_CALL next(JSC::ExecState*);
68
69private:
70 JSDOMIteratorPrototype(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) { }
71
72 void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
73};
74
75enum class IterationKind { Key, Value, KeyValue };
76
77template<typename JSWrapper, typename IteratorTraits> class JSDOMIterator : public JSDOMObject {
78public:
79 using Base = JSDOMObject;
80
81 using Wrapper = JSWrapper;
82 using Traits = IteratorTraits;
83
84 using DOMWrapped = typename Wrapper::DOMWrapped;
85 using Prototype = JSDOMIteratorPrototype<Wrapper, Traits>;
86
87 DECLARE_INFO;
88
89 static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
90 {
91 return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
92 }
93
94 static JSDOMIterator* create(JSC::VM& vm, JSC::Structure* structure, JSWrapper& iteratedObject, IterationKind kind)
95 {
96 JSDOMIterator* instance = new (NotNull, JSC::allocateCell<JSDOMIterator>(vm.heap)) JSDOMIterator(structure, iteratedObject, kind);
97 instance->finishCreation(vm);
98 return instance;
99 }
100
101 static Prototype* createPrototype(JSC::VM& vm, JSC::JSGlobalObject& globalObject)
102 {
103 return Prototype::create(vm, &globalObject, Prototype::createStructure(vm, &globalObject, globalObject.iteratorPrototype()));
104 }
105
106 JSC::JSValue next(JSC::ExecState&);
107
108private:
109 JSDOMIterator(JSC::Structure* structure, JSWrapper& iteratedObject, IterationKind kind)
110 : Base(structure, *iteratedObject.globalObject())
111 , m_iterator(iteratedObject.wrapped().createIterator())
112 , m_kind(kind)
113 {
114 }
115
116 template<typename IteratorValue, typename T = Traits> EnableIfMap<T, JSC::JSValue> asJS(JSC::ExecState&, IteratorValue&);
117 template<typename IteratorValue, typename T = Traits> EnableIfSet<T, JSC::JSValue> asJS(JSC::ExecState&, IteratorValue&);
118
119 static void destroy(JSC::JSCell*);
120
121 Optional<typename DOMWrapped::Iterator> m_iterator;
122 IterationKind m_kind;
123};
124
125inline JSC::JSValue jsPair(JSC::ExecState& state, JSDOMGlobalObject& globalObject, JSC::JSValue value1, JSC::JSValue value2)
126{
127 JSC::MarkedArgumentBuffer arguments;
128 arguments.append(value1);
129 arguments.append(value2);
130 ASSERT(!arguments.hasOverflowed());
131 return constructArray(&state, nullptr, &globalObject, arguments);
132}
133
134template<typename FirstType, typename SecondType, typename T, typename U>
135inline JSC::JSValue jsPair(JSC::ExecState& state, JSDOMGlobalObject& globalObject, const T& value1, const U& value2)
136{
137 return jsPair(state, globalObject, toJS<FirstType>(state, globalObject, value1), toJS<SecondType>(state, globalObject, value2));
138}
139
140template<typename JSIterator> JSC::JSValue iteratorCreate(typename JSIterator::Wrapper&, IterationKind);
141template<typename JSIterator> JSC::JSValue iteratorForEach(JSC::ExecState&, typename JSIterator::Wrapper&, JSC::ThrowScope&);
142
143template<typename JSIterator> JSC::JSValue iteratorCreate(typename JSIterator::Wrapper& thisObject, IterationKind kind)
144{
145 ASSERT(thisObject.globalObject());
146 JSDOMGlobalObject& globalObject = *thisObject.globalObject();
147 return JSIterator::create(globalObject.vm(), getDOMStructure<JSIterator>(globalObject.vm(), globalObject), thisObject, kind);
148}
149
150template<typename JSWrapper, typename IteratorTraits>
151template<typename IteratorValue, typename T> inline EnableIfMap<T, JSC::JSValue> JSDOMIterator<JSWrapper, IteratorTraits>::asJS(JSC::ExecState& state, IteratorValue& value)
152{
153 ASSERT(value);
154
155 switch (m_kind) {
156 case IterationKind::Key:
157 return toJS<typename Traits::KeyType>(state, *globalObject(), value->key);
158 case IterationKind::Value:
159 return toJS<typename Traits::ValueType>(state, *globalObject(), value->value);
160 case IterationKind::KeyValue:
161 return jsPair<typename Traits::KeyType, typename Traits::ValueType>(state, *globalObject(), value->key, value->value);
162 };
163
164 ASSERT_NOT_REACHED();
165 return { };
166}
167
168template<typename JSWrapper, typename IteratorTraits>
169template<typename IteratorValue, typename T> inline EnableIfSet<T, JSC::JSValue> JSDOMIterator<JSWrapper, IteratorTraits>::asJS(JSC::ExecState& state, IteratorValue& value)
170{
171 ASSERT(value);
172
173 auto globalObject = this->globalObject();
174 auto result = toJS<typename Traits::ValueType>(state, *globalObject, value);
175
176 switch (m_kind) {
177 case IterationKind::Key:
178 case IterationKind::Value:
179 return result;
180 case IterationKind::KeyValue:
181 return jsPair(state, *globalObject, result, result);
182 };
183
184 ASSERT_NOT_REACHED();
185 return { };
186}
187
188template<typename JSIterator, typename IteratorValue> EnableIfMap<typename JSIterator::Traits> appendForEachArguments(JSC::ExecState& state, JSDOMGlobalObject& globalObject, JSC::MarkedArgumentBuffer& arguments, IteratorValue& value)
189{
190 ASSERT(value);
191 arguments.append(toJS<typename JSIterator::Traits::ValueType>(state, globalObject, value->value));
192 arguments.append(toJS<typename JSIterator::Traits::KeyType>(state, globalObject, value->key));
193}
194
195template<typename JSIterator, typename IteratorValue> EnableIfSet<typename JSIterator::Traits> appendForEachArguments(JSC::ExecState& state, JSDOMGlobalObject& globalObject, JSC::MarkedArgumentBuffer& arguments, IteratorValue& value)
196{
197 ASSERT(value);
198 auto argument = toJS<typename JSIterator::Traits::ValueType>(state, globalObject, value);
199 arguments.append(argument);
200 arguments.append(argument);
201}
202
203template<typename JSIterator> JSC::JSValue iteratorForEach(JSC::ExecState& state, typename JSIterator::Wrapper& thisObject, JSC::ThrowScope& scope)
204{
205 JSC::JSValue callback = state.argument(0);
206 JSC::JSValue thisValue = state.argument(1);
207
208 JSC::CallData callData;
209 JSC::CallType callType = JSC::getCallData(state.vm(), callback, callData);
210 if (callType == JSC::CallType::None)
211 return throwTypeError(&state, scope, "Cannot call callback"_s);
212
213 auto iterator = thisObject.wrapped().createIterator();
214 while (auto value = iterator.next()) {
215 JSC::MarkedArgumentBuffer arguments;
216 appendForEachArguments<JSIterator>(state, *thisObject.globalObject(), arguments, value);
217 arguments.append(&thisObject);
218 if (UNLIKELY(arguments.hasOverflowed())) {
219 throwOutOfMemoryError(&state, scope);
220 return { };
221 }
222 JSC::call(&state, callback, callType, callData, thisValue, arguments);
223 if (UNLIKELY(scope.exception()))
224 break;
225 }
226 return JSC::jsUndefined();
227}
228
229template<typename JSWrapper, typename IteratorTraits>
230void JSDOMIterator<JSWrapper, IteratorTraits>::destroy(JSCell* cell)
231{
232 JSDOMIterator<JSWrapper, IteratorTraits>* thisObject = static_cast<JSDOMIterator<JSWrapper, IteratorTraits>*>(cell);
233 thisObject->JSDOMIterator<JSWrapper, IteratorTraits>::~JSDOMIterator();
234}
235
236template<typename JSWrapper, typename IteratorTraits>
237JSC::JSValue JSDOMIterator<JSWrapper, IteratorTraits>::next(JSC::ExecState& state)
238{
239 if (m_iterator) {
240 auto iteratorValue = m_iterator->next();
241 if (iteratorValue)
242 return createIteratorResultObject(&state, asJS(state, iteratorValue), false);
243 m_iterator = WTF::nullopt;
244 }
245 return createIteratorResultObject(&state, JSC::jsUndefined(), true);
246}
247
248template<typename JSWrapper, typename IteratorTraits>
249JSC::EncodedJSValue JSC_HOST_CALL JSDOMIteratorPrototype<JSWrapper, IteratorTraits>::next(JSC::ExecState* state)
250{
251 JSC::VM& vm = state->vm();
252 auto scope = DECLARE_THROW_SCOPE(vm);
253
254 auto iterator = JSC::jsDynamicCast<JSDOMIterator<JSWrapper, IteratorTraits>*>(vm, state->thisValue());
255 if (!iterator)
256 return JSC::JSValue::encode(throwTypeError(state, scope, "Cannot call next() on a non-Iterator object"_s));
257
258 return JSC::JSValue::encode(iterator->next(*state));
259}
260
261template<typename JSWrapper, typename IteratorTraits>
262void JSDOMIteratorPrototype<JSWrapper, IteratorTraits>::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
263{
264 Base::finishCreation(vm);
265 ASSERT(inherits(vm, info()));
266
267 JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->next, next, 0, 0, JSC::NoIntrinsic);
268}
269
270}
271