Bumping manifests a=b2g-bump
[gecko.git] / dom / indexedDB / KeyPath.cpp
blobf139134a808f2ee12d4720c93f74c58fc752f061
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/. */
7 #include "KeyPath.h"
8 #include "IDBObjectStore.h"
9 #include "Key.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
20 namespace {
22 inline
23 bool
24 IgnoreWhitespace(char16_t c)
26 return false;
29 typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
31 bool
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()) {
42 return false;
45 JS::Rooted<JS::Value> stringVal(aCx);
46 if (!xpc::StringToJsval(aCx, token, &stringVal)) {
47 return false;
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) {
55 return false;
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) == '.') {
63 return false;
66 return true;
69 enum KeyExtractionOptions {
70 DoNotCreateProperties,
71 CreateProperties
74 nsresult
75 GetJSValFromKeyPathString(JSContext* aCx,
76 const JS::Value& aValue,
77 const nsAString& aKeyPathString,
78 JS::Value* aKeyJSVal,
79 KeyExtractionOptions aOptions,
80 KeyPath::ExtractOrCreateKeyCallback aCallback,
81 void* aClosure)
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!");
91 nsresult rv = NS_OK;
92 *aKeyJSVal = aValue;
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();
109 bool hasProp;
110 if (!targetObject) {
111 // We're still walking the chain of existing objects
112 if (!obj) {
113 return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
116 bool ok = JS_HasUCProperty(aCx, obj, keyPathChars, keyPathLen,
117 &hasProp);
118 IDB_ENSURE_TRUE(ok, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
120 if (hasProp) {
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();
137 else {
138 // ...otherwise use it as key
139 *aKeyJSVal = intermediate;
142 else {
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;
149 targetObject = obj;
150 targetObjectPropName = token;
154 if (targetObject) {
155 // We have started inserting new objects or are about to just insert
156 // the first one.
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
162 // chain.
163 JS::Rooted<JSObject*> dummy(aCx, JS_NewObject(aCx, nullptr, JS::NullPtr(),
164 JS::NullPtr()));
165 if (!dummy) {
166 IDB_REPORT_INTERNAL_ERR();
167 rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
168 break;
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;
175 break;
178 obj = dummy;
180 else {
181 JS::Rooted<JSObject*> dummy(aCx, JS_NewObject(aCx, &IDBObjectStore::sDummyPropJSClass,
182 JS::NullPtr(), JS::NullPtr()));
183 if (!dummy) {
184 IDB_REPORT_INTERNAL_ERR();
185 rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
186 break;
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;
193 break;
196 obj = dummy;
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);
207 if (targetObject) {
208 // If this fails, we lose, and the web page sees a magical property
209 // appear on the object :-(
210 bool succeeded;
211 if (!JS_DeleteUCProperty2(aCx, targetObject,
212 targetObjectPropName.get(),
213 targetObjectPropName.Length(),
214 &succeeded)) {
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);
222 return rv;
225 } // anonymous namespace
227 // static
228 nsresult
229 KeyPath::Parse(JSContext* aCx, const nsAString& aString, KeyPath* aKeyPath)
231 KeyPath keyPath(0);
232 keyPath.SetType(STRING);
234 if (!keyPath.AppendStringWithValidation(aCx, aString)) {
235 return NS_ERROR_FAILURE;
238 *aKeyPath = keyPath;
239 return NS_OK;
242 //static
243 nsresult
244 KeyPath::Parse(JSContext* aCx, const mozilla::dom::Sequence<nsString>& aStrings,
245 KeyPath* aKeyPath)
247 KeyPath keyPath(0);
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;
256 *aKeyPath = keyPath;
257 return NS_OK;
260 // static
261 nsresult
262 KeyPath::Parse(JSContext* aCx, const JS::Value& aValue_, KeyPath* aKeyPath)
264 JS::Rooted<JS::Value> aValue(aCx, aValue_);
265 KeyPath keyPath(0);
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());
274 uint32_t length;
275 if (!JS_GetArrayLength(aCx, obj, &length)) {
276 return NS_ERROR_FAILURE;
279 if (!length) {
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);
287 JSString* jsstr;
288 nsAutoJSString str;
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()) {
302 JSString* jsstr;
303 nsAutoJSString str;
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;
316 *aKeyPath = keyPath;
317 return NS_OK;
320 void
321 KeyPath::SetType(KeyPathType aType)
323 mType = aType;
324 mStrings.Clear();
327 bool
328 KeyPath::AppendStringWithValidation(JSContext* aCx, const nsAString& aString)
330 if (!IsValidKeyPathString(aCx, aString)) {
331 return false;
334 if (IsString()) {
335 NS_ASSERTION(mStrings.Length() == 0, "Too many strings!");
336 mStrings.AppendElement(aString);
337 return true;
340 if (IsArray()) {
341 mStrings.AppendElement(aString);
342 return true;
345 NS_NOTREACHED("What?!");
346 return false;
349 nsresult
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);
355 aKey.Unset();
357 for (uint32_t i = 0; i < len; ++i) {
358 nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[i],
359 value.address(),
360 DoNotCreateProperties, nullptr,
361 nullptr);
362 if (NS_FAILED(rv)) {
363 return rv;
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;
372 aKey.FinishArray();
374 return NS_OK;
377 nsresult
378 KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
379 JS::Value* aOutVal) const
381 NS_ASSERTION(IsValid(), "This doesn't make sense!");
383 if (IsString()) {
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));
390 if (!arrayObj) {
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],
397 value.address(),
398 DoNotCreateProperties, nullptr,
399 nullptr);
400 if (NS_FAILED(rv)) {
401 return rv;
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);
411 return NS_OK;
414 nsresult
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);
423 aKey.Unset();
425 nsresult rv = GetJSValFromKeyPathString(aCx, aValue, mStrings[0],
426 value.address(),
427 CreateProperties, aCallback,
428 aClosure);
429 if (NS_FAILED(rv)) {
430 return rv;
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;
438 aKey.FinishArray();
440 return NS_OK;
443 void
444 KeyPath::SerializeToString(nsAString& aString) const
446 NS_ASSERTION(IsValid(), "Check to see if I'm valid first!");
448 if (IsString()) {
449 aString = mStrings[0];
450 return;
453 if (IsArray()) {
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) {
460 aString.Append(',');
461 aString.Append(mStrings[i]);
464 return;
467 NS_NOTREACHED("What?");
470 // static
471 KeyPath
472 KeyPath::DeserializeFromString(const nsAString& aString)
474 KeyPath keyPath(0);
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());
488 return keyPath;
491 keyPath.SetType(STRING);
492 keyPath.mStrings.AppendElement(aString);
494 return keyPath;
497 nsresult
498 KeyPath::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aValue) const
500 if (IsArray()) {
501 uint32_t len = mStrings.Length();
502 JS::Rooted<JSObject*> array(aCx, JS_NewArrayObject(aCx, len));
503 if (!array) {
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);
523 return NS_OK;
526 if (IsString()) {
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;
532 return NS_OK;
535 aValue.setNull();
536 return NS_OK;
539 nsresult
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)) {
545 aValue = value;
547 return rv;
550 bool
551 KeyPath::IsAllowedForObjectStore(bool aAutoIncrement) const
553 // Any keypath that passed validation is allowed for non-autoIncrement
554 // objectStores.
555 if (!aAutoIncrement) {
556 return true;
559 // Array keypaths are not allowed for autoIncrement objectStores.
560 if (IsArray()) {
561 return false;
564 // Neither are empty strings.
565 if (IsEmpty()) {
566 return false;
569 // Everything else is ok.
570 return true;