1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "IDBObjectStore.h"
10 #include "ReportInternalError.h"
12 #include "nsCharSeparatedTokenizer.h"
13 #include "nsJSUtils.h"
14 #include "xpcpublic.h"
16 #include "mozilla/dom/BindingDeclarations.h"
18 USING_INDEXEDDB_NAMESPACE
24 IgnoreWhitespace(char16_t c
)
29 typedef nsCharSeparatedTokenizerTemplate
<IgnoreWhitespace
> KeyPathTokenizer
;
32 IsValidKeyPathString(JSContext
* aCx
, const nsAString
& aKeyPath
)
34 NS_ASSERTION(!aKeyPath
.IsVoid(), "What?");
36 KeyPathTokenizer
tokenizer(aKeyPath
, '.');
38 while (tokenizer
.hasMoreTokens()) {
39 nsString
token(tokenizer
.nextToken());
41 if (!token
.Length()) {
45 JS::Rooted
<JS::Value
> stringVal(aCx
);
46 if (!xpc::StringToJsval(aCx
, token
, &stringVal
)) {
50 NS_ASSERTION(stringVal
.toString(), "This should never happen");
51 JS::Rooted
<JSString
*> str(aCx
, stringVal
.toString());
53 bool isIdentifier
= false;
54 if (!JS_IsIdentifier(aCx
, str
, &isIdentifier
) || !isIdentifier
) {
59 // If the very last character was a '.', the tokenizer won't give us an empty
60 // token, but the keyPath is still invalid.
61 if (!aKeyPath
.IsEmpty() &&
62 aKeyPath
.CharAt(aKeyPath
.Length() - 1) == '.') {
69 enum KeyExtractionOptions
{
70 DoNotCreateProperties
,
75 GetJSValFromKeyPathString(JSContext
* aCx
,
76 const JS::Value
& aValue
,
77 const nsAString
& aKeyPathString
,
79 KeyExtractionOptions aOptions
,
80 KeyPath::ExtractOrCreateKeyCallback aCallback
,
83 NS_ASSERTION(aCx
, "Null pointer!");
84 NS_ASSERTION(IsValidKeyPathString(aCx
, aKeyPathString
),
85 "This will explode!");
86 NS_ASSERTION(!(aCallback
|| aClosure
) || aOptions
== CreateProperties
,
87 "This is not allowed!");
88 NS_ASSERTION(aOptions
!= CreateProperties
|| aCallback
,
89 "If properties are created, there must be a callback!");
94 KeyPathTokenizer
tokenizer(aKeyPathString
, '.');
96 nsString targetObjectPropName
;
97 JS::Rooted
<JSObject
*> targetObject(aCx
, nullptr);
98 JS::Rooted
<JSObject
*> obj(aCx
,
99 aValue
.isPrimitive() ? nullptr : aValue
.toObjectOrNull());
101 while (tokenizer
.hasMoreTokens()) {
102 const nsDependentSubstring
& token
= tokenizer
.nextToken();
104 NS_ASSERTION(!token
.IsEmpty(), "Should be a valid keypath");
106 const jschar
* keyPathChars
= token
.BeginReading();
107 const size_t keyPathLen
= token
.Length();
111 // We're still walking the chain of existing objects
113 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
116 bool ok
= JS_HasUCProperty(aCx
, obj
, keyPathChars
, keyPathLen
,
118 IDB_ENSURE_TRUE(ok
, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
121 // Get if the property exists...
122 JS::Rooted
<JS::Value
> intermediate(aCx
);
123 bool ok
= JS_GetUCProperty(aCx
, obj
, keyPathChars
, keyPathLen
, &intermediate
);
124 IDB_ENSURE_TRUE(ok
, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
126 // Treat explicitly undefined as an error.
127 if (intermediate
== JSVAL_VOID
) {
128 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
130 if (tokenizer
.hasMoreTokens()) {
131 // ...and walk to it if there are more steps...
132 if (intermediate
.isPrimitive()) {
133 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
135 obj
= intermediate
.toObjectOrNull();
138 // ...otherwise use it as key
139 *aKeyJSVal
= intermediate
;
143 // If the property doesn't exist, fall into below path of starting
144 // to define properties, if allowed.
145 if (aOptions
== DoNotCreateProperties
) {
146 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
150 targetObjectPropName
= token
;
155 // We have started inserting new objects or are about to just insert
158 *aKeyJSVal
= JSVAL_VOID
;
160 if (tokenizer
.hasMoreTokens()) {
161 // If we're not at the end, we need to add a dummy object to the
163 JS::Rooted
<JSObject
*> dummy(aCx
, JS_NewObject(aCx
, nullptr, JS::NullPtr(),
166 IDB_REPORT_INTERNAL_ERR();
167 rv
= NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
171 if (!JS_DefineUCProperty(aCx
, obj
, token
.BeginReading(),
172 token
.Length(), dummy
, JSPROP_ENUMERATE
)) {
173 IDB_REPORT_INTERNAL_ERR();
174 rv
= NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
181 JS::Rooted
<JSObject
*> dummy(aCx
, JS_NewObject(aCx
, &IDBObjectStore::sDummyPropJSClass
,
182 JS::NullPtr(), JS::NullPtr()));
184 IDB_REPORT_INTERNAL_ERR();
185 rv
= NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
189 if (!JS_DefineUCProperty(aCx
, obj
, token
.BeginReading(),
190 token
.Length(), dummy
, JSPROP_ENUMERATE
)) {
191 IDB_REPORT_INTERNAL_ERR();
192 rv
= NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
201 // We guard on rv being a success because we need to run the property
202 // deletion code below even if we should not be running the callback.
203 if (NS_SUCCEEDED(rv
) && aCallback
) {
204 rv
= (*aCallback
)(aCx
, aClosure
);
208 // If this fails, we lose, and the web page sees a magical property
209 // appear on the object :-(
211 if (!JS_DeleteUCProperty2(aCx
, targetObject
,
212 targetObjectPropName
.get(),
213 targetObjectPropName
.Length(),
215 IDB_REPORT_INTERNAL_ERR();
216 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
218 IDB_ENSURE_TRUE(succeeded
, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
);
221 NS_ENSURE_SUCCESS(rv
, rv
);
225 } // anonymous namespace
229 KeyPath::Parse(JSContext
* aCx
, const nsAString
& aString
, KeyPath
* aKeyPath
)
232 keyPath
.SetType(STRING
);
234 if (!keyPath
.AppendStringWithValidation(aCx
, aString
)) {
235 return NS_ERROR_FAILURE
;
244 KeyPath::Parse(JSContext
* aCx
, const mozilla::dom::Sequence
<nsString
>& aStrings
,
248 keyPath
.SetType(ARRAY
);
250 for (uint32_t i
= 0; i
< aStrings
.Length(); ++i
) {
251 if (!keyPath
.AppendStringWithValidation(aCx
, aStrings
[i
])) {
252 return NS_ERROR_FAILURE
;
262 KeyPath::Parse(JSContext
* aCx
, const JS::Value
& aValue_
, KeyPath
* aKeyPath
)
264 JS::Rooted
<JS::Value
> aValue(aCx
, aValue_
);
267 aKeyPath
->SetType(NONEXISTENT
);
269 // See if this is a JS array.
270 if (JS_IsArrayObject(aCx
, aValue
)) {
272 JS::Rooted
<JSObject
*> obj(aCx
, aValue
.toObjectOrNull());
275 if (!JS_GetArrayLength(aCx
, obj
, &length
)) {
276 return NS_ERROR_FAILURE
;
280 return NS_ERROR_FAILURE
;
283 keyPath
.SetType(ARRAY
);
285 for (uint32_t index
= 0; index
< length
; index
++) {
286 JS::Rooted
<JS::Value
> val(aCx
);
289 if (!JS_GetElement(aCx
, obj
, index
, &val
) ||
290 !(jsstr
= JS::ToString(aCx
, val
)) ||
291 !str
.init(aCx
, jsstr
)) {
292 return NS_ERROR_FAILURE
;
295 if (!keyPath
.AppendStringWithValidation(aCx
, str
)) {
296 return NS_ERROR_FAILURE
;
300 // Otherwise convert it to a string.
301 else if (!aValue
.isNull() && !aValue
.isUndefined()) {
304 if (!(jsstr
= JS::ToString(aCx
, aValue
)) ||
305 !str
.init(aCx
, jsstr
)) {
306 return NS_ERROR_FAILURE
;
309 keyPath
.SetType(STRING
);
311 if (!keyPath
.AppendStringWithValidation(aCx
, str
)) {
312 return NS_ERROR_FAILURE
;
321 KeyPath::SetType(KeyPathType aType
)
328 KeyPath::AppendStringWithValidation(JSContext
* aCx
, const nsAString
& aString
)
330 if (!IsValidKeyPathString(aCx
, aString
)) {
335 NS_ASSERTION(mStrings
.Length() == 0, "Too many strings!");
336 mStrings
.AppendElement(aString
);
341 mStrings
.AppendElement(aString
);
345 NS_NOTREACHED("What?!");
350 KeyPath::ExtractKey(JSContext
* aCx
, const JS::Value
& aValue
, Key
& aKey
) const
352 uint32_t len
= mStrings
.Length();
353 JS::Rooted
<JS::Value
> value(aCx
);
357 for (uint32_t i
= 0; i
< len
; ++i
) {
358 nsresult rv
= GetJSValFromKeyPathString(aCx
, aValue
, mStrings
[i
],
360 DoNotCreateProperties
, nullptr,
366 if (NS_FAILED(aKey
.AppendItem(aCx
, IsArray() && i
== 0, value
))) {
367 NS_ASSERTION(aKey
.IsUnset(), "Encoding error should unset");
368 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
378 KeyPath::ExtractKeyAsJSVal(JSContext
* aCx
, const JS::Value
& aValue
,
379 JS::Value
* aOutVal
) const
381 NS_ASSERTION(IsValid(), "This doesn't make sense!");
384 return GetJSValFromKeyPathString(aCx
, aValue
, mStrings
[0], aOutVal
,
385 DoNotCreateProperties
, nullptr, nullptr);
388 const uint32_t len
= mStrings
.Length();
389 JS::Rooted
<JSObject
*> arrayObj(aCx
, JS_NewArrayObject(aCx
, len
));
391 return NS_ERROR_OUT_OF_MEMORY
;
394 JS::Rooted
<JS::Value
> value(aCx
);
395 for (uint32_t i
= 0; i
< len
; ++i
) {
396 nsresult rv
= GetJSValFromKeyPathString(aCx
, aValue
, mStrings
[i
],
398 DoNotCreateProperties
, nullptr,
404 if (!JS_SetElement(aCx
, arrayObj
, i
, value
)) {
405 IDB_REPORT_INTERNAL_ERR();
406 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
410 *aOutVal
= OBJECT_TO_JSVAL(arrayObj
);
415 KeyPath::ExtractOrCreateKey(JSContext
* aCx
, const JS::Value
& aValue
,
416 Key
& aKey
, ExtractOrCreateKeyCallback aCallback
,
417 void* aClosure
) const
419 NS_ASSERTION(IsString(), "This doesn't make sense!");
421 JS::Rooted
<JS::Value
> value(aCx
);
425 nsresult rv
= GetJSValFromKeyPathString(aCx
, aValue
, mStrings
[0],
427 CreateProperties
, aCallback
,
433 if (NS_FAILED(aKey
.AppendItem(aCx
, false, value
))) {
434 NS_ASSERTION(aKey
.IsUnset(), "Should be unset");
435 return value
.isUndefined() ? NS_OK
: NS_ERROR_DOM_INDEXEDDB_DATA_ERR
;
444 KeyPath::SerializeToString(nsAString
& aString
) const
446 NS_ASSERTION(IsValid(), "Check to see if I'm valid first!");
449 aString
= mStrings
[0];
454 // We use a comma in the beginning to indicate that it's an array of
455 // key paths. This is to be able to tell a string-keypath from an
456 // array-keypath which contains only one item.
457 // It also makes serializing easier :-)
458 uint32_t len
= mStrings
.Length();
459 for (uint32_t i
= 0; i
< len
; ++i
) {
461 aString
.Append(mStrings
[i
]);
467 NS_NOTREACHED("What?");
472 KeyPath::DeserializeFromString(const nsAString
& aString
)
476 if (!aString
.IsEmpty() && aString
.First() == ',') {
477 keyPath
.SetType(ARRAY
);
479 // We use a comma in the beginning to indicate that it's an array of
480 // key paths. This is to be able to tell a string-keypath from an
481 // array-keypath which contains only one item.
482 nsCharSeparatedTokenizerTemplate
<IgnoreWhitespace
> tokenizer(aString
, ',');
483 tokenizer
.nextToken();
484 while (tokenizer
.hasMoreTokens()) {
485 keyPath
.mStrings
.AppendElement(tokenizer
.nextToken());
491 keyPath
.SetType(STRING
);
492 keyPath
.mStrings
.AppendElement(aString
);
498 KeyPath::ToJSVal(JSContext
* aCx
, JS::MutableHandle
<JS::Value
> aValue
) const
501 uint32_t len
= mStrings
.Length();
502 JS::Rooted
<JSObject
*> array(aCx
, JS_NewArrayObject(aCx
, len
));
504 IDB_WARNING("Failed to make array!");
505 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
508 for (uint32_t i
= 0; i
< len
; ++i
) {
509 JS::Rooted
<JS::Value
> val(aCx
);
510 nsString
tmp(mStrings
[i
]);
511 if (!xpc::StringToJsval(aCx
, tmp
, &val
)) {
512 IDB_REPORT_INTERNAL_ERR();
513 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
516 if (!JS_SetElement(aCx
, array
, i
, val
)) {
517 IDB_REPORT_INTERNAL_ERR();
518 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
522 aValue
.setObject(*array
);
527 nsString
tmp(mStrings
[0]);
528 if (!xpc::StringToJsval(aCx
, tmp
, aValue
)) {
529 IDB_REPORT_INTERNAL_ERR();
530 return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR
;
540 KeyPath::ToJSVal(JSContext
* aCx
, JS::Heap
<JS::Value
>& aValue
) const
542 JS::Rooted
<JS::Value
> value(aCx
);
543 nsresult rv
= ToJSVal(aCx
, &value
);
544 if (NS_SUCCEEDED(rv
)) {
551 KeyPath::IsAllowedForObjectStore(bool aAutoIncrement
) const
553 // Any keypath that passed validation is allowed for non-autoIncrement
555 if (!aAutoIncrement
) {
559 // Array keypaths are not allowed for autoIncrement objectStores.
564 // Neither are empty strings.
569 // Everything else is ok.