Replace WTF::move with WTFMove
[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 "IDBIndexInfo.h"
34 #include "IDBIndexMetadata.h"
35 #include "IDBKey.h"
36 #include "IDBKeyData.h"
37 #include "IDBKeyPath.h"
38 #include "IndexKey.h"
39 #include "JSDOMBinding.h"
40 #include "Logging.h"
41 #include "SharedBuffer.h"
42 #include "ThreadSafeDataBuffer.h"
43
44 #include <runtime/DateInstance.h>
45 #include <runtime/ObjectConstructor.h>
46
47 using namespace JSC;
48
49 namespace WebCore {
50
51 static bool get(ExecState* exec, JSValue object, const String& keyPathElement, JSValue& result)
52 {
53     if (object.isString() && keyPathElement == "length") {
54         result = jsNumber(object.toString(exec)->length());
55         return true;
56     }
57     if (!object.isObject())
58         return false;
59     Identifier identifier = Identifier::fromString(&exec->vm(), keyPathElement.utf8().data());
60     if (!asObject(object)->hasProperty(exec, identifier))
61         return false;
62     result = asObject(object)->get(exec, identifier);
63     return true;
64 }
65
66 static bool canSet(JSValue object, const String& keyPathElement)
67 {
68     UNUSED_PARAM(keyPathElement);
69     return object.isObject();
70 }
71
72 static bool set(ExecState* exec, JSValue& object, const String& keyPathElement, JSValue jsValue)
73 {
74     if (!canSet(object, keyPathElement))
75         return false;
76     Identifier identifier = Identifier::fromString(&exec->vm(), keyPathElement.utf8().data());
77     asObject(object)->putDirect(exec->vm(), identifier, jsValue);
78     return true;
79 }
80
81 JSValue idbKeyDataToJSValue(JSC::ExecState& exec, const IDBKeyData& keyData)
82 {
83     if (keyData.isNull())
84         return jsUndefined();
85
86     Locker<JSLock> locker(exec.vm().apiLock());
87
88     switch (keyData.type()) {
89     case KeyType::Array:
90         {
91             const Vector<IDBKeyData>& inArray = keyData.array();
92             size_t size = inArray.size();
93             JSArray* outArray = constructEmptyArray(&exec, 0, exec.lexicalGlobalObject(), size);
94             for (size_t i = 0; i < size; ++i) {
95                 auto& arrayKey = inArray.at(i);
96                 outArray->putDirectIndex(&exec, i, idbKeyDataToJSValue(exec, arrayKey));
97             }
98             return JSValue(outArray);
99         }
100     case KeyType::String:
101         return jsStringWithCache(&exec, keyData.string());
102     case KeyType::Date:
103         return jsDateOrNull(&exec, keyData.date());
104     case KeyType::Number:
105         return jsNumber(keyData.number());
106     case KeyType::Min:
107     case KeyType::Max:
108     case KeyType::Invalid:
109         ASSERT_NOT_REACHED();
110         return jsUndefined();
111     }
112
113     ASSERT_NOT_REACHED();
114     return jsUndefined();
115
116 }
117
118 static JSValue idbKeyToJSValue(ExecState* exec, JSGlobalObject* globalObject, IDBKey* key)
119 {
120     if (!key || !exec) {
121         // This should be undefined, not null.
122         // Spec: http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBKeyRange
123         return jsUndefined();
124     }
125
126     Locker<JSLock> locker(exec->vm().apiLock());
127
128     switch (key->type()) {
129     case KeyType::Array:
130         {
131             const Vector<RefPtr<IDBKey>>& inArray = key->array();
132             size_t size = inArray.size();
133             JSArray* outArray = constructEmptyArray(exec, 0, globalObject, size);
134             for (size_t i = 0; i < size; ++i) {
135                 IDBKey* arrayKey = inArray.at(i).get();
136                 outArray->putDirectIndex(exec, i, idbKeyToJSValue(exec, globalObject, arrayKey));
137             }
138             return JSValue(outArray);
139         }
140     case KeyType::String:
141         return jsStringWithCache(exec, key->string());
142     case KeyType::Date:
143         return jsDateOrNull(exec, key->date());
144     case KeyType::Number:
145         return jsNumber(key->number());
146     case KeyType::Min:
147     case KeyType::Max:
148     case KeyType::Invalid:
149         ASSERT_NOT_REACHED();
150         return jsUndefined();
151     }
152
153     ASSERT_NOT_REACHED();
154     return jsUndefined();
155 }
156
157 static const size_t maximumDepth = 2000;
158
159 static RefPtr<IDBKey> createIDBKeyFromValue(ExecState* exec, JSValue value, Vector<JSArray*>& stack)
160 {
161     if (value.isNumber() && !std::isnan(value.toNumber(exec)))
162         return IDBKey::createNumber(value.toNumber(exec));
163     if (value.isString())
164         return IDBKey::createString(value.toString(exec)->value(exec));
165     if (value.inherits(DateInstance::info()) && !std::isnan(valueToDate(exec, value)))
166         return IDBKey::createDate(valueToDate(exec, value));
167     if (value.isObject()) {
168         JSObject* object = asObject(value);
169         if (isJSArray(object) || object->inherits(JSArray::info())) {
170             JSArray* array = asArray(object);
171             size_t length = array->length();
172
173             if (stack.contains(array))
174                 return nullptr;
175             if (stack.size() >= maximumDepth)
176                 return nullptr;
177             stack.append(array);
178
179             Vector<RefPtr<IDBKey>> subkeys;
180             for (size_t i = 0; i < length; i++) {
181                 JSValue item = array->getIndex(exec, i);
182                 RefPtr<IDBKey> subkey = createIDBKeyFromValue(exec, item, stack);
183                 if (!subkey)
184                     subkeys.append(IDBKey::createInvalid());
185                 else
186                     subkeys.append(subkey);
187             }
188
189             stack.removeLast();
190             return IDBKey::createArray(subkeys);
191         }
192     }
193     return nullptr;
194 }
195
196 static RefPtr<IDBKey> createIDBKeyFromValue(ExecState* exec, JSValue value)
197 {
198     Vector<JSArray*> stack;
199     RefPtr<IDBKey> key = createIDBKeyFromValue(exec, value, stack);
200     if (key)
201         return key;
202     return IDBKey::createInvalid();
203 }
204
205 IDBKeyPath idbKeyPathFromValue(ExecState* exec, JSValue keyPathValue)
206 {
207     IDBKeyPath keyPath;
208     if (isJSArray(keyPathValue))
209         keyPath = IDBKeyPath(toNativeArray<String>(exec, keyPathValue));
210     else
211         keyPath = IDBKeyPath(keyPathValue.toString(exec)->value(exec));
212     return keyPath;
213 }
214
215 static JSValue getNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
216 {
217     JSValue currentValue(rootValue);
218     ASSERT(index <= keyPathElements.size());
219     for (size_t i = 0; i < index; i++) {
220         JSValue parentValue(currentValue);
221         if (!get(exec, parentValue, keyPathElements[i], currentValue))
222             return jsUndefined();
223     }
224     return currentValue;
225 }
226
227 static RefPtr<IDBKey> internalCreateIDBKeyFromScriptValueAndKeyPath(ExecState* exec, const JSC::JSValue& value, const String& keyPath)
228 {
229     Vector<String> keyPathElements;
230     IDBKeyPathParseError error;
231     IDBParseKeyPath(keyPath, keyPathElements, error);
232     ASSERT(error == IDBKeyPathParseError::None);
233
234     JSValue jsValue = value;
235     jsValue = getNthValueOnKeyPath(exec, jsValue, keyPathElements, keyPathElements.size());
236     if (jsValue.isUndefined())
237         return nullptr;
238     return createIDBKeyFromValue(exec, jsValue);
239 }
240
241 static JSValue ensureNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
242 {
243     JSValue currentValue(rootValue);
244
245     ASSERT(index <= keyPathElements.size());
246     for (size_t i = 0; i < index; i++) {
247         JSValue parentValue(currentValue);
248         const String& keyPathElement = keyPathElements[i];
249         if (!get(exec, parentValue, keyPathElement, currentValue)) {
250             JSObject* object = constructEmptyObject(exec);
251             if (!set(exec, parentValue, keyPathElement, JSValue(object)))
252                 return jsUndefined();
253             currentValue = JSValue(object);
254         }
255     }
256
257     return currentValue;
258 }
259
260 static bool canInjectNthValueOnKeyPath(ExecState* exec, JSValue rootValue, const Vector<String>& keyPathElements, size_t index)
261 {
262     if (!rootValue.isObject())
263         return false;
264
265     JSValue currentValue(rootValue);
266
267     ASSERT(index <= keyPathElements.size());
268     for (size_t i = 0; i < index; ++i) {
269         JSValue parentValue(currentValue);
270         const String& keyPathElement = keyPathElements[i];
271         if (!get(exec, parentValue, keyPathElement, currentValue))
272             return canSet(parentValue, keyPathElement);
273     }
274     return true;
275 }
276
277 bool injectIDBKeyIntoScriptValue(DOMRequestState* requestState, PassRefPtr<IDBKey> key, Deprecated::ScriptValue& value, const IDBKeyPath& keyPath)
278 {
279     LOG(StorageAPI, "injectIDBKeyIntoScriptValue");
280
281     ASSERT(keyPath.type() == IndexedDB::KeyPathType::String);
282
283     Vector<String> keyPathElements;
284     IDBKeyPathParseError error;
285     IDBParseKeyPath(keyPath.string(), keyPathElements, error);
286     ASSERT(error == IDBKeyPathParseError::None);
287
288     if (keyPathElements.isEmpty())
289         return false;
290
291     ExecState* exec = requestState->exec();
292
293     JSValue parent = ensureNthValueOnKeyPath(exec, value.jsValue(), keyPathElements, keyPathElements.size() - 1);
294     if (parent.isUndefined())
295         return false;
296
297     if (!set(exec, parent, keyPathElements.last(), idbKeyToJSValue(exec, exec->lexicalGlobalObject(), key.get())))
298         return false;
299
300     return true;
301 }
302
303 bool injectIDBKeyIntoScriptValue(JSC::ExecState& exec, const IDBKeyData& keyData, JSC::JSValue value, const IDBKeyPath& keyPath)
304 {
305     LOG(IndexedDB, "injectIDBKeyIntoScriptValue");
306
307     ASSERT(keyPath.type() == IndexedDB::KeyPathType::String);
308
309     Vector<String> keyPathElements;
310     IDBKeyPathParseError error;
311     IDBParseKeyPath(keyPath.string(), keyPathElements, error);
312     ASSERT(error == IDBKeyPathParseError::None);
313
314     if (keyPathElements.isEmpty())
315         return false;
316
317     JSValue parent = ensureNthValueOnKeyPath(&exec, value, keyPathElements, keyPathElements.size() - 1);
318     if (parent.isUndefined())
319         return false;
320
321     auto key = keyData.maybeCreateIDBKey();
322     if (!key)
323         return false;
324
325     if (!set(&exec, parent, keyPathElements.last(), idbKeyToJSValue(&exec, exec.lexicalGlobalObject(), key.get())))
326         return false;
327
328     return true;
329 }
330
331 RefPtr<IDBKey> createIDBKeyFromScriptValueAndKeyPath(ExecState* exec, const Deprecated::ScriptValue& value, const IDBKeyPath& keyPath)
332 {
333     LOG(StorageAPI, "createIDBKeyFromScriptValueAndKeyPath");
334     ASSERT(!keyPath.isNull());
335
336     if (keyPath.type() == IndexedDB::KeyPathType::Array) {
337         Vector<RefPtr<IDBKey>> result;
338         const Vector<String>& array = keyPath.array();
339         for (size_t i = 0; i < array.size(); i++) {
340             RefPtr<IDBKey> key = internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, array[i]);
341             if (!key)
342                 return nullptr;
343             result.append(key);
344         }
345         return IDBKey::createArray(result);
346     }
347
348     ASSERT(keyPath.type() == IndexedDB::KeyPathType::String);
349     return internalCreateIDBKeyFromScriptValueAndKeyPath(exec, value, keyPath.string());
350 }
351
352 RefPtr<IDBKey> maybeCreateIDBKeyFromScriptValueAndKeyPath(ExecState& exec, const Deprecated::ScriptValue& value, const IDBKeyPath& keyPath)
353 {
354     ASSERT(!keyPath.isNull());
355
356     if (keyPath.type() == IndexedDB::KeyPathType::Array) {
357         Vector<RefPtr<IDBKey>> result;
358         const Vector<String>& array = keyPath.array();
359         for (size_t i = 0; i < array.size(); i++) {
360             RefPtr<IDBKey> key = internalCreateIDBKeyFromScriptValueAndKeyPath(&exec, value, array[i]);
361             if (!key)
362                 return nullptr;
363             result.append(key);
364         }
365         return IDBKey::createArray(result);
366     }
367
368     ASSERT(keyPath.type() == IndexedDB::KeyPathType::String);
369     return internalCreateIDBKeyFromScriptValueAndKeyPath(&exec, value, keyPath.string());
370 }
371
372 RefPtr<IDBKey> maybeCreateIDBKeyFromScriptValueAndKeyPath(ExecState& exec, const JSC::JSValue& value, const IDBKeyPath& keyPath)
373 {
374     ASSERT(!keyPath.isNull());
375
376     if (keyPath.type() == IndexedDB::KeyPathType::Array) {
377         const Vector<String>& array = keyPath.array();
378         Vector<RefPtr<IDBKey>> result;
379         result.reserveInitialCapacity(array.size());
380         for (auto& string : array) {
381             RefPtr<IDBKey> key = internalCreateIDBKeyFromScriptValueAndKeyPath(&exec, value, string);
382             if (!key)
383                 return nullptr;
384             result.uncheckedAppend(WTFMove(key));
385         }
386         return IDBKey::createArray(WTFMove(result));
387     }
388
389     ASSERT(keyPath.type() == IndexedDB::KeyPathType::String);
390     return internalCreateIDBKeyFromScriptValueAndKeyPath(&exec, value, keyPath.string());
391 }
392
393 bool canInjectIDBKeyIntoScriptValue(DOMRequestState* requestState, const JSC::JSValue& scriptValue, const IDBKeyPath& keyPath)
394 {
395     LOG(StorageAPI, "canInjectIDBKeyIntoScriptValue");
396
397     JSC::ExecState* exec = requestState->exec();
398     if (!exec)
399         return false;
400
401     return canInjectIDBKeyIntoScriptValue(*exec, scriptValue, keyPath);
402 }
403
404 bool canInjectIDBKeyIntoScriptValue(JSC::ExecState& execState, const JSC::JSValue& scriptValue, const IDBKeyPath& keyPath)
405 {
406     LOG(StorageAPI, "canInjectIDBKeyIntoScriptValue");
407
408     ASSERT(keyPath.type() == IndexedDB::KeyPathType::String);
409     Vector<String> keyPathElements;
410     IDBKeyPathParseError error;
411     IDBParseKeyPath(keyPath.string(), keyPathElements, error);
412     ASSERT(error == IDBKeyPathParseError::None);
413
414     if (!keyPathElements.size())
415         return false;
416
417     return canInjectNthValueOnKeyPath(&execState, scriptValue, keyPathElements, keyPathElements.size() - 1);
418 }
419
420 Deprecated::ScriptValue deserializeIDBValue(DOMRequestState* requestState, PassRefPtr<SerializedScriptValue> prpValue)
421 {
422     ExecState* exec = requestState->exec();
423     RefPtr<SerializedScriptValue> serializedValue = prpValue;
424     JSValue result;
425     if (serializedValue)
426         result = serializedValue->deserialize(exec, exec->lexicalGlobalObject(), 0);
427     else
428         result = jsNull();
429     return Deprecated::ScriptValue(exec->vm(), result);
430 }
431
432 Deprecated::ScriptValue deserializeIDBValueData(ScriptExecutionContext& context, const ThreadSafeDataBuffer& valueData)
433 {
434     DOMRequestState state(&context);
435     auto* execState = state.exec();
436
437     if (!execState)
438         return Deprecated::ScriptValue();
439
440     return Deprecated::ScriptValue(execState->vm(), deserializeIDBValueDataToJSValue(*execState, valueData));
441 }
442
443 JSC::JSValue deserializeIDBValueDataToJSValue(JSC::ExecState& exec, const ThreadSafeDataBuffer& valueData)
444 {
445     if (!valueData.data())
446         return jsUndefined();
447
448     const Vector<uint8_t>& data = *valueData.data();
449     JSValue result;
450     if (data.size()) {
451         RefPtr<SerializedScriptValue> serializedValue = SerializedScriptValue::createFromWireBytes(data);
452
453         exec.vm().apiLock().lock();
454         result = serializedValue->deserialize(&exec, exec.lexicalGlobalObject(), 0, NonThrowing);
455         exec.vm().apiLock().unlock();
456     } else
457         result = jsNull();
458
459     return result;
460 }
461
462 Deprecated::ScriptValue deserializeIDBValueBuffer(DOMRequestState* requestState, PassRefPtr<SharedBuffer> prpBuffer, bool keyIsDefined)
463 {
464     if (prpBuffer) {
465         Vector<uint8_t> value;
466         value.append(prpBuffer->data(), prpBuffer->size());
467         return deserializeIDBValueBuffer(requestState->exec(), value, keyIsDefined);
468     }
469
470     return Deprecated::ScriptValue(requestState->exec()->vm(), jsNull());
471 }
472
473 static JSValue idbValueDataToJSValue(JSC::ExecState& exec, const Vector<uint8_t>& buffer)
474 {
475     if (buffer.isEmpty())
476         return jsNull();
477
478     RefPtr<SerializedScriptValue> serializedValue = SerializedScriptValue::createFromWireBytes(buffer);
479     return serializedValue->deserialize(&exec, exec.lexicalGlobalObject(), 0, NonThrowing);
480 }
481
482 Deprecated::ScriptValue deserializeIDBValueBuffer(JSC::ExecState* exec, const Vector<uint8_t>& buffer, bool keyIsDefined)
483 {
484     ASSERT(exec);
485
486     // If the key doesn't exist, then the value must be undefined (as opposed to null).
487     if (!keyIsDefined) {
488         // We either shouldn't have a buffer or it should be of size 0.
489         ASSERT(!buffer.size());
490         return Deprecated::ScriptValue(exec->vm(), jsUndefined());
491     }
492
493     JSValue result = idbValueDataToJSValue(*exec, buffer);
494     return Deprecated::ScriptValue(exec->vm(), result);
495 }
496
497 JSValue idbValueDataToJSValue(JSC::ExecState& exec, const ThreadSafeDataBuffer& valueData)
498 {
499     if (!valueData.data())
500         return jsUndefined();
501
502     return idbValueDataToJSValue(exec, *valueData.data());
503 }
504
505 Deprecated::ScriptValue idbKeyToScriptValue(DOMRequestState* requestState, PassRefPtr<IDBKey> key)
506 {
507     ExecState* exec = requestState->exec();
508     if (!exec)
509         return { };
510
511     return Deprecated::ScriptValue(exec->vm(), idbKeyToJSValue(exec, jsCast<JSDOMGlobalObject*>(exec->lexicalGlobalObject()), key.get()));
512 }
513
514 RefPtr<IDBKey> scriptValueToIDBKey(DOMRequestState* requestState, const JSC::JSValue& scriptValue)
515 {
516     ExecState* exec = requestState->exec();
517     return createIDBKeyFromValue(exec, scriptValue);
518 }
519
520 RefPtr<IDBKey> scriptValueToIDBKey(ExecState& exec, const JSC::JSValue& scriptValue)
521 {
522     return createIDBKeyFromValue(&exec, scriptValue);
523 }
524
525 Deprecated::ScriptValue idbKeyDataToScriptValue(ScriptExecutionContext* context, const IDBKeyData& keyData)
526 {
527     RefPtr<IDBKey> key = keyData.maybeCreateIDBKey();
528     DOMRequestState requestState(context);
529     return idbKeyToScriptValue(&requestState, key.get());
530 }
531
532 void generateIndexKeysForValue(ExecState* exec, const IDBIndexMetadata& indexMetadata, const Deprecated::ScriptValue& objectValue, Vector<IDBKeyData>& indexKeys)
533 {
534     RefPtr<IDBKey> indexKey = createIDBKeyFromScriptValueAndKeyPath(exec, objectValue, indexMetadata.keyPath);
535
536     if (!indexKey)
537         return;
538
539     if (!indexMetadata.multiEntry || indexKey->type() != KeyType::Array) {
540         if (!indexKey->isValid())
541             return;
542
543         indexKeys.append(IDBKeyData(indexKey.get()));
544     } else {
545         ASSERT(indexMetadata.multiEntry);
546         ASSERT(indexKey->type() == KeyType::Array);
547         indexKey = IDBKey::createMultiEntryArray(indexKey->array());
548
549         if (!indexKey->isValid())
550             return;
551
552         for (auto& i : indexKey->array())
553             indexKeys.append(IDBKeyData(i.get()));
554     }
555 }
556
557 static Vector<IDBKeyData> createKeyPathArray(ExecState& exec, JSValue value, const IDBIndexInfo& info)
558 {
559     Vector<IDBKeyData> keys;
560
561     switch (info.keyPath().type()) {
562     case IndexedDB::KeyPathType::Array:
563         for (auto& entry : info.keyPath().array()) {
564             auto key = internalCreateIDBKeyFromScriptValueAndKeyPath(&exec, value, entry);
565             if (!key)
566                 return { };
567             keys.append(key.get());
568         }
569         break;
570     case IndexedDB::KeyPathType::String: {
571         auto idbKey = internalCreateIDBKeyFromScriptValueAndKeyPath(&exec, value, info.keyPath().string());
572         if (!idbKey)
573             return { };
574
575         if (info.multiEntry() && idbKey->type() == IndexedDB::Array) {
576             for (auto& key : idbKey->array())
577                 keys.append(key.get());
578         } else
579             keys.append(idbKey.get());
580
581         break;
582     }
583     case IndexedDB::KeyPathType::Null:
584         RELEASE_ASSERT_NOT_REACHED();
585     }
586
587     return keys;
588 }
589
590 void generateIndexKeyForValue(ExecState& exec, const IDBIndexInfo& info, JSValue value, IndexKey& outKey)
591 {
592     auto keyDatas = createKeyPathArray(exec, value, info);
593
594     if (keyDatas.isEmpty())
595         return;
596
597     outKey = IndexKey(WTFMove(keyDatas));
598 }
599
600 } // namespace WebCore
601
602 #endif // ENABLE(INDEXED_DATABASE)