Foo::s_info should be Foo::info(), so that you can change how the s_info is actually...
[WebKit-https.git] / Source / WebCore / bindings / js / IDBBindingUtilities.cpp
1 /*
2  * Copyright (C) 2010 Google Inc. All rights reserved.
3  * Copyright (C) 2012 Michael Pruett <michael@68k.org>
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  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28
29 #if ENABLE(INDEXED_DATABASE)
30 #include "IDBBindingUtilities.h"
31
32 #include "DOMRequestState.h"
33 #include "IDBKey.h"
34 #include "IDBKeyPath.h"
35 #include "IDBTracing.h"
36 #include "SharedBuffer.h"
37
38 #include <runtime/DateInstance.h>
39 #include <runtime/ObjectConstructor.h>
40
41 using namespace JSC;
42
43 namespace WebCore {
44
45 static bool get(ExecState* exec, JSValue object, const String& keyPathElement, JSValue& result)
46 {
47     if (object.isString() && keyPathElement == "length") {
48         result = jsNumber(object.toString(exec)->length());
49         return true;
50     }
51     if (!object.isObject())
52         return false;
53     Identifier identifier(&exec->vm(), keyPathElement.utf8().data());
54     if (!asObject(object)->hasProperty(exec, identifier))
55         return false;
56     result = asObject(object)->get(exec, identifier);
57     return true;
58 }
59
60 static bool canSet(JSValue object, const String& keyPathElement)
61 {
62     UNUSED_PARAM(keyPathElement);
63     return object.isObject();
64 }
65
66 static bool set(ExecState* exec, JSValue& object, const String& keyPathElement, JSValue jsValue)
67 {
68     if (!canSet(object, keyPathElement))
69         return false;
70     Identifier identifier(&exec->vm(), keyPathElement.utf8().data());
71     asObject(object)->putDirect(exec->vm(), identifier, jsValue);
72     return true;
73 }
74
75 static JSValue idbKeyToJSValue(ExecState* exec, JSDOMGlobalObject* globalObject, IDBKey* key)
76 {
77     if (!key) {
78         // This should be undefined, not null.
79         // Spec: http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBKeyRange
80         return jsUndefined();
81     }
82
83     switch (key->type()) {
84     case IDBKey::ArrayType:
85         {
86             const IDBKey::KeyArray& inArray = key->array();
87             size_t size = inArray.size();
88             JSArray* outArray = constructEmptyArray(exec, 0, globalObject, size);
89             for (size_t i = 0; i < size; ++i) {
90                 IDBKey* arrayKey = inArray.at(i).get();
91                 outArray->putDirectIndex(exec, i, idbKeyToJSValue(exec, globalObject, arrayKey));
92             }
93             return JSValue(outArray);
94         }
95     case IDBKey::StringType:
96         return jsStringWithCache(exec, key->string());
97     case IDBKey::DateType:
98         return jsDateOrNull(exec, key->date());
99     case IDBKey::NumberType:
100         return jsNumber(key->number());
101     case IDBKey::MinType:
102     case IDBKey::InvalidType:
103         ASSERT_NOT_REACHED();
104         return jsUndefined();
105     }
106
107     ASSERT_NOT_REACHED();
108     return jsUndefined();
109 }
110
111 static const size_t maximumDepth = 2000;
112
113 static PassRefPtr<IDBKey> createIDBKeyFromValue(ExecState* exec, JSValue value, Vector<JSArray*>& stack)
114 {
115     if (value.isNumber() && !std::isnan(value.toNumber(exec)))
116         return IDBKey::createNumber(value.toNumber(exec));
117     if (value.isString())
118         return IDBKey::createString(value.toString(exec)->value(exec));
119     if (value.inherits(DateInstance::info()) && !std::isnan(valueToDate(exec, value)))
120         return IDBKey::createDate(valueToDate(exec, value));
121     if (value.isObject()) {
122         JSObject* object = asObject(value);
123         if (isJSArray(object) || object->inherits(JSArray::info())) {
124             JSArray* array = asArray(object);
125             size_t length = array->length();
126
127             if (stack.contains(array))
128                 return 0;
129             if (stack.size() >= maximumDepth)
130                 return 0;
131             stack.append(array);
132
133             IDBKey::KeyArray subkeys;
134             for (size_t i = 0; i < length; i++) {
135                 JSValue item = array->getIndex(exec, i);
136                 RefPtr<IDBKey> subkey = createIDBKeyFromValue(exec, item, stack);
137                 if (!subkey)
138                     subkeys.append(IDBKey::createInvalid());
139                 else
140                     subkeys.append(subkey);
141             }
142
143             stack.removeLast();
144             return IDBKey::createArray(subkeys);
145         }
146     }
147     return 0;
148 }
149
150 PassRefPtr<IDBKey> createIDBKeyFromValue(ExecState* exec, JSValue value)
151 {
152     Vector<JSArray*> stack;
153     RefPtr<IDBKey> key = createIDBKeyFromValue(exec, value, stack);
154     if (key)
155         return key;
156     return IDBKey::createInvalid();
157 }
158
159 IDBKeyPath idbKeyPathFromValue(ExecState* exec, JSValue keyPathValue)
160 {
161     IDBKeyPath keyPath;
162     if (isJSArray(keyPathValue))
163         keyPath = IDBKeyPath(toNativeArray<String>(exec, keyPathValue));
164     else
165         keyPath = IDBKeyPath(keyPathValue.toString(exec)->value(exec));
166     return keyPath;
167 }
168
169 static JSValue getNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
170 {
171     JSValue currentValue(rootValue);
172     ASSERT(index <= keyPathElements.size());
173     for (size_t i = 0; i < index; i++) {
174         JSValue parentValue(currentValue);
175         if (!get(exec, parentValue, keyPathElements[i], currentValue))
176             return jsUndefined();
177     }
178     return currentValue;
179 }
180
181 static PassRefPtr<IDBKey> createIDBKeyFromScriptValueAndKeyPath(ExecState* exec, const ScriptValue& value, const String& keyPath)
182 {
183     Vector<String> keyPathElements;
184     IDBKeyPathParseError error;
185     IDBParseKeyPath(keyPath, keyPathElements, error);
186     ASSERT(error == IDBKeyPathParseErrorNone);
187
188     JSValue jsValue = value.jsValue();
189     jsValue = getNthValueOnKeyPath(exec, jsValue, keyPathElements, keyPathElements.size());
190     if (jsValue.isUndefined())
191         return 0;
192     return createIDBKeyFromValue(exec, jsValue);
193 }
194
195 static JSValue ensureNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
196 {
197     JSValue currentValue(rootValue);
198
199     ASSERT(index <= keyPathElements.size());
200     for (size_t i = 0; i < index; i++) {
201         JSValue parentValue(currentValue);
202         const String& keyPathElement = keyPathElements[i];
203         if (!get(exec, parentValue, keyPathElement, currentValue)) {
204             JSObject* object = constructEmptyObject(exec);
205             if (!set(exec, parentValue, keyPathElement, JSValue(object)))
206                 return jsUndefined();
207             currentValue = JSValue(object);
208         }
209     }
210
211     return currentValue;
212 }
213
214 static bool canInjectNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
215 {
216     if (!rootValue.isObject())
217         return false;
218
219     JSValue currentValue(rootValue);
220
221     ASSERT(index <= keyPathElements.size());
222     for (size_t i = 0; i < index; ++i) {
223         JSValue parentValue(currentValue);
224         const String& keyPathElement = keyPathElements[i];
225         if (!get(exec, parentValue, keyPathElement, currentValue))
226             return canSet(parentValue, keyPathElement);
227     }
228     return true;
229 }
230
231 bool injectIDBKeyIntoScriptValue(DOMRequestState* requestState, PassRefPtr<IDBKey> key, ScriptValue& value, const IDBKeyPath& keyPath)
232 {
233     IDB_TRACE("injectIDBKeyIntoScriptValue");
234
235     ASSERT(keyPath.type() == IDBKeyPath::StringType);
236
237     Vector<String> keyPathElements;
238     IDBKeyPathParseError error;
239     IDBParseKeyPath(keyPath.string(), keyPathElements, error);
240     ASSERT(error == IDBKeyPathParseErrorNone);
241
242     if (keyPathElements.isEmpty())
243         return false;
244
245     ExecState* exec = requestState->exec();
246
247     JSValue parent = ensureNthValueOnKeyPath(exec, value.jsValue(), keyPathElements, keyPathElements.size() - 1);
248     if (parent.isUndefined())
249         return false;
250
251     if (!set(exec, parent, keyPathElements.last(), idbKeyToJSValue(exec, jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), key.get())))
252         return false;
253
254     return true;
255 }
256
257 PassRefPtr<IDBKey> createIDBKeyFromScriptValueAndKeyPath(DOMRequestState* requestState, const ScriptValue& value, const IDBKeyPath& keyPath)
258 {
259     IDB_TRACE("createIDBKeyFromScriptValueAndKeyPath");
260     ASSERT(!keyPath.isNull());
261
262     ExecState* exec = requestState->exec();
263
264     if (keyPath.type() == IDBKeyPath::ArrayType) {
265         IDBKey::KeyArray result;
266         const Vector<String>& array = keyPath.array();
267         for (size_t i = 0; i < array.size(); i++) {
268             RefPtr<IDBKey> key = createIDBKeyFromScriptValueAndKeyPath(exec, value, array[i]);
269             if (!key)
270                 return 0;
271             result.append(key);
272         }
273         return IDBKey::createArray(result);
274     }
275
276     ASSERT(keyPath.type() == IDBKeyPath::StringType);
277     return createIDBKeyFromScriptValueAndKeyPath(exec, value, keyPath.string());
278 }
279
280 bool canInjectIDBKeyIntoScriptValue(DOMRequestState* requestState, const ScriptValue& scriptValue, const IDBKeyPath& keyPath)
281 {
282     IDB_TRACE("canInjectIDBKeyIntoScriptValue");
283
284     ASSERT(keyPath.type() == IDBKeyPath::StringType);
285     Vector<String> keyPathElements;
286     IDBKeyPathParseError error;
287     IDBParseKeyPath(keyPath.string(), keyPathElements, error);
288     ASSERT(error == IDBKeyPathParseErrorNone);
289
290     if (!keyPathElements.size())
291         return false;
292
293     JSC::ExecState* exec = requestState->exec();
294     return canInjectNthValueOnKeyPath(exec, scriptValue.jsValue(), keyPathElements, keyPathElements.size() - 1);
295 }
296
297 ScriptValue deserializeIDBValue(DOMRequestState* requestState, PassRefPtr<SerializedScriptValue> prpValue)
298 {
299     ExecState* exec = requestState->exec();
300     RefPtr<SerializedScriptValue> serializedValue = prpValue;
301     if (serializedValue)
302         return ScriptValue::deserialize(exec, serializedValue.get(), NonThrowing);
303     return ScriptValue(exec->vm(), jsNull());
304 }
305
306 ScriptValue deserializeIDBValueBuffer(DOMRequestState* requestState, PassRefPtr<SharedBuffer> prpBuffer)
307 {
308     ExecState* exec = requestState->exec();
309     RefPtr<SharedBuffer> buffer = prpBuffer;
310     if (buffer) {
311         // FIXME: The extra copy here can be eliminated by allowing SerializedScriptValue to take a raw const char* or const uint8_t*.
312         Vector<uint8_t> value;
313         value.append(buffer->data(), buffer->size());
314         RefPtr<SerializedScriptValue> serializedValue = SerializedScriptValue::createFromWireBytes(value);
315         return ScriptValue::deserialize(exec, serializedValue.get(), NonThrowing);
316     }
317     return ScriptValue(exec->vm(), jsNull());
318 }
319
320 ScriptValue idbKeyToScriptValue(DOMRequestState* requestState, PassRefPtr<IDBKey> key)
321 {
322     ExecState* exec = requestState->exec();
323     return ScriptValue(exec->vm(), idbKeyToJSValue(exec, jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), key.get()));
324 }
325
326 PassRefPtr<IDBKey> scriptValueToIDBKey(DOMRequestState* requestState, const ScriptValue& scriptValue)
327 {
328     ExecState* exec = requestState->exec();
329     return createIDBKeyFromValue(exec, scriptValue.jsValue());
330 }
331
332 } // namespace WebCore
333
334 #endif // ENABLE(INDEXED_DATABASE)