mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	This patch is originally authored by Bevis Tseng. Differential Revision: https://phabricator.services.mozilla.com/D204361
		
			
				
	
	
		
			570 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			570 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "KeyPath.h"
 | 
						|
 | 
						|
#include "IDBObjectStore.h"
 | 
						|
#include "IndexedDBCommon.h"
 | 
						|
#include "Key.h"
 | 
						|
#include "ReportInternalError.h"
 | 
						|
#include "js/Array.h"  // JS::NewArrayObject
 | 
						|
#include "js/PropertyAndElement.h"  // JS_DefineElement, JS_DefineUCProperty, JS_DeleteUCProperty
 | 
						|
#include "js/PropertyDescriptor.h"  // JS::PropertyDescriptor, JS_GetOwnUCPropertyDescriptor
 | 
						|
#include "mozilla/ResultExtensions.h"
 | 
						|
#include "mozilla/dom/BindingDeclarations.h"
 | 
						|
#include "mozilla/dom/Blob.h"
 | 
						|
#include "mozilla/dom/BlobBinding.h"
 | 
						|
#include "mozilla/dom/File.h"
 | 
						|
#include "mozilla/dom/IDBObjectStoreBinding.h"
 | 
						|
#include "mozilla/dom/quota/ResultExtensions.h"
 | 
						|
#include "nsCharSeparatedTokenizer.h"
 | 
						|
#include "nsJSUtils.h"
 | 
						|
#include "nsPrintfCString.h"
 | 
						|
#include "xpcpublic.h"
 | 
						|
 | 
						|
namespace mozilla::dom::indexedDB {
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
using KeyPathTokenizer =
 | 
						|
    nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing>;
 | 
						|
 | 
						|
bool IsValidKeyPathString(const nsAString& aKeyPath) {
 | 
						|
  NS_ASSERTION(!aKeyPath.IsVoid(), "What?");
 | 
						|
 | 
						|
  for (const auto& token : KeyPathTokenizer(aKeyPath, '.').ToRange()) {
 | 
						|
    if (token.IsEmpty()) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!JS_IsIdentifier(token.Data(), token.Length())) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // If the very last character was a '.', the tokenizer won't give us an empty
 | 
						|
  // token, but the keyPath is still invalid.
 | 
						|
  return aKeyPath.IsEmpty() || aKeyPath.CharAt(aKeyPath.Length() - 1) != '.';
 | 
						|
}
 | 
						|
 | 
						|
enum KeyExtractionOptions { DoNotCreateProperties, CreateProperties };
 | 
						|
 | 
						|
nsresult GetJSValFromKeyPathString(
 | 
						|
    JSContext* aCx, const JS::Value& aValue, const nsAString& aKeyPathString,
 | 
						|
    JS::Value* aKeyJSVal, KeyExtractionOptions aOptions,
 | 
						|
    KeyPath::ExtractOrCreateKeyCallback aCallback, void* aClosure) {
 | 
						|
  NS_ASSERTION(aCx, "Null pointer!");
 | 
						|
  NS_ASSERTION(IsValidKeyPathString(aKeyPathString), "This will explode!");
 | 
						|
  NS_ASSERTION(!(aCallback || aClosure) || aOptions == CreateProperties,
 | 
						|
               "This is not allowed!");
 | 
						|
  NS_ASSERTION(aOptions != CreateProperties || aCallback,
 | 
						|
               "If properties are created, there must be a callback!");
 | 
						|
 | 
						|
  nsresult rv = NS_OK;
 | 
						|
  *aKeyJSVal = aValue;
 | 
						|
 | 
						|
  KeyPathTokenizer tokenizer(aKeyPathString, '.');
 | 
						|
 | 
						|
  nsString targetObjectPropName;
 | 
						|
  JS::Rooted<JSObject*> targetObject(aCx, nullptr);
 | 
						|
  JS::Rooted<JS::Value> currentVal(aCx, aValue);
 | 
						|
  JS::Rooted<JSObject*> obj(aCx);
 | 
						|
 | 
						|
  while (tokenizer.hasMoreTokens()) {
 | 
						|
    const auto& token = tokenizer.nextToken();
 | 
						|
 | 
						|
    NS_ASSERTION(!token.IsEmpty(), "Should be a valid keypath");
 | 
						|
 | 
						|
    const char16_t* keyPathChars = token.BeginReading();
 | 
						|
    const size_t keyPathLen = token.Length();
 | 
						|
 | 
						|
    if (!targetObject) {
 | 
						|
      // We're still walking the chain of existing objects
 | 
						|
      // http://w3c.github.io/IndexedDB/#evaluate-a-key-path-on-a-value
 | 
						|
      // step 4 substep 1: check for .length on a String value.
 | 
						|
      if (currentVal.isString() && !tokenizer.hasMoreTokens() &&
 | 
						|
          token.EqualsLiteral("length")) {
 | 
						|
        aKeyJSVal->setNumber(JS_GetStringLength(currentVal.toString()));
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!currentVal.isObject()) {
 | 
						|
        return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
 | 
						|
      }
 | 
						|
      obj = ¤tVal.toObject();
 | 
						|
 | 
						|
      // We call JS_GetOwnUCPropertyDescriptor on purpose (as opposed to
 | 
						|
      // JS_GetUCPropertyDescriptor) to avoid searching the prototype chain.
 | 
						|
      JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(aCx);
 | 
						|
      QM_TRY(OkIf(JS_GetOwnUCPropertyDescriptor(aCx, obj, keyPathChars,
 | 
						|
                                                keyPathLen, &desc)),
 | 
						|
             NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
 | 
						|
             IDB_REPORT_INTERNAL_ERR_LAMBDA);
 | 
						|
 | 
						|
      JS::Rooted<JS::Value> intermediate(aCx);
 | 
						|
      bool hasProp = false;
 | 
						|
 | 
						|
      if (desc.isSome() && desc->isDataDescriptor()) {
 | 
						|
        intermediate = desc->value();
 | 
						|
        hasProp = true;
 | 
						|
      } else {
 | 
						|
        // If we get here it means the object doesn't have the property or the
 | 
						|
        // property is available throuch a getter. We don't want to call any
 | 
						|
        // getters to avoid potential re-entrancy.
 | 
						|
        // The blob object is special since its properties are available
 | 
						|
        // only through getters but we still want to support them for key
 | 
						|
        // extraction. So they need to be handled manually.
 | 
						|
        Blob* blob;
 | 
						|
        if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) {
 | 
						|
          if (token.EqualsLiteral("size")) {
 | 
						|
            ErrorResult rv;
 | 
						|
            uint64_t size = blob->GetSize(rv);
 | 
						|
            MOZ_ALWAYS_TRUE(!rv.Failed());
 | 
						|
 | 
						|
            intermediate = JS_NumberValue(size);
 | 
						|
            hasProp = true;
 | 
						|
          } else if (token.EqualsLiteral("type")) {
 | 
						|
            nsString type;
 | 
						|
            blob->GetType(type);
 | 
						|
 | 
						|
            JSString* string =
 | 
						|
                JS_NewUCStringCopyN(aCx, type.get(), type.Length());
 | 
						|
 | 
						|
            intermediate = JS::StringValue(string);
 | 
						|
            hasProp = true;
 | 
						|
          } else {
 | 
						|
            RefPtr<File> file = blob->ToFile();
 | 
						|
            if (file) {
 | 
						|
              if (token.EqualsLiteral("name")) {
 | 
						|
                nsString name;
 | 
						|
                file->GetName(name);
 | 
						|
 | 
						|
                JSString* string =
 | 
						|
                    JS_NewUCStringCopyN(aCx, name.get(), name.Length());
 | 
						|
 | 
						|
                intermediate = JS::StringValue(string);
 | 
						|
                hasProp = true;
 | 
						|
              } else if (token.EqualsLiteral("lastModified")) {
 | 
						|
                ErrorResult rv;
 | 
						|
                int64_t lastModifiedDate = file->GetLastModified(rv);
 | 
						|
                MOZ_ALWAYS_TRUE(!rv.Failed());
 | 
						|
 | 
						|
                intermediate = JS_NumberValue(lastModifiedDate);
 | 
						|
                hasProp = true;
 | 
						|
              }
 | 
						|
              // The spec also lists "lastModifiedDate", but we deprecated and
 | 
						|
              // removed support for it.
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      if (hasProp) {
 | 
						|
        // Treat explicitly undefined as an error.
 | 
						|
        if (intermediate.isUndefined()) {
 | 
						|
          return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
 | 
						|
        }
 | 
						|
        if (tokenizer.hasMoreTokens()) {
 | 
						|
          // ...and walk to it if there are more steps...
 | 
						|
          currentVal = intermediate;
 | 
						|
        } else {
 | 
						|
          // ...otherwise use it as key
 | 
						|
          *aKeyJSVal = intermediate;
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        // If the property doesn't exist, fall into below path of starting
 | 
						|
        // to define properties, if allowed.
 | 
						|
        if (aOptions == DoNotCreateProperties) {
 | 
						|
          return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
 | 
						|
        }
 | 
						|
 | 
						|
        targetObject = obj;
 | 
						|
        targetObjectPropName = token;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (targetObject) {
 | 
						|
      // We have started inserting new objects or are about to just insert
 | 
						|
      // the first one.
 | 
						|
 | 
						|
      aKeyJSVal->setUndefined();
 | 
						|
 | 
						|
      if (tokenizer.hasMoreTokens()) {
 | 
						|
        // If we're not at the end, we need to add a dummy object to the
 | 
						|
        // chain.
 | 
						|
        JS::Rooted<JSObject*> dummy(aCx, JS_NewPlainObject(aCx));
 | 
						|
        if (!dummy) {
 | 
						|
          IDB_REPORT_INTERNAL_ERR();
 | 
						|
          rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(), token.Length(),
 | 
						|
                                 dummy, JSPROP_ENUMERATE)) {
 | 
						|
          IDB_REPORT_INTERNAL_ERR();
 | 
						|
          rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        obj = dummy;
 | 
						|
      } else {
 | 
						|
        JS::Rooted<JSObject*> dummy(
 | 
						|
            aCx, JS_NewObject(aCx, IDBObjectStore::DummyPropClass()));
 | 
						|
        if (!dummy) {
 | 
						|
          IDB_REPORT_INTERNAL_ERR();
 | 
						|
          rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!JS_DefineUCProperty(aCx, obj, token.BeginReading(), token.Length(),
 | 
						|
                                 dummy, JSPROP_ENUMERATE)) {
 | 
						|
          IDB_REPORT_INTERNAL_ERR();
 | 
						|
          rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
 | 
						|
        obj = dummy;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // We guard on rv being a success because we need to run the property
 | 
						|
  // deletion code below even if we should not be running the callback.
 | 
						|
  if (NS_SUCCEEDED(rv) && aCallback) {
 | 
						|
    rv = (*aCallback)(aCx, aClosure);
 | 
						|
  }
 | 
						|
 | 
						|
  if (targetObject) {
 | 
						|
    // If this fails, we lose, and the web page sees a magical property
 | 
						|
    // appear on the object :-(
 | 
						|
    JS::ObjectOpResult succeeded;
 | 
						|
    if (!JS_DeleteUCProperty(aCx, targetObject, targetObjectPropName.get(),
 | 
						|
                             targetObjectPropName.Length(), succeeded)) {
 | 
						|
      IDB_REPORT_INTERNAL_ERR();
 | 
						|
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
    }
 | 
						|
    QM_TRY(OkIf(succeeded.ok()), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
 | 
						|
           IDB_REPORT_INTERNAL_ERR_LAMBDA);
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO: It would be nicer to do the cleanup using a RAII class or something.
 | 
						|
  //       This last QM_TRY could be removed then.
 | 
						|
  QM_TRY(MOZ_TO_RESULT(rv));
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace
 | 
						|
 | 
						|
// static
 | 
						|
Result<KeyPath, nsresult> KeyPath::Parse(const nsAString& aString) {
 | 
						|
  KeyPath keyPath(0);
 | 
						|
  keyPath.SetType(KeyPathType::String);
 | 
						|
 | 
						|
  if (!keyPath.AppendStringWithValidation(aString)) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
 | 
						|
  return keyPath;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
Result<KeyPath, nsresult> KeyPath::Parse(const Sequence<nsString>& aStrings) {
 | 
						|
  KeyPath keyPath(0);
 | 
						|
  keyPath.SetType(KeyPathType::Array);
 | 
						|
 | 
						|
  for (uint32_t i = 0; i < aStrings.Length(); ++i) {
 | 
						|
    if (!keyPath.AppendStringWithValidation(aStrings[i])) {
 | 
						|
      return Err(NS_ERROR_FAILURE);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return keyPath;
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
Result<KeyPath, nsresult> KeyPath::Parse(
 | 
						|
    const Nullable<OwningStringOrStringSequence>& aValue) {
 | 
						|
  if (aValue.IsNull()) {
 | 
						|
    return KeyPath{0};
 | 
						|
  }
 | 
						|
 | 
						|
  if (aValue.Value().IsString()) {
 | 
						|
    return Parse(aValue.Value().GetAsString());
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(aValue.Value().IsStringSequence());
 | 
						|
 | 
						|
  const Sequence<nsString>& seq = aValue.Value().GetAsStringSequence();
 | 
						|
  if (seq.Length() == 0) {
 | 
						|
    return Err(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
  return Parse(seq);
 | 
						|
}
 | 
						|
 | 
						|
void KeyPath::SetType(KeyPathType aType) {
 | 
						|
  mType = aType;
 | 
						|
  mStrings.Clear();
 | 
						|
}
 | 
						|
 | 
						|
bool KeyPath::AppendStringWithValidation(const nsAString& aString) {
 | 
						|
  if (!IsValidKeyPathString(aString)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsString()) {
 | 
						|
    NS_ASSERTION(mStrings.Length() == 0, "Too many strings!");
 | 
						|
    mStrings.AppendElement(aString);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsArray()) {
 | 
						|
    mStrings.AppendElement(aString);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT_UNREACHABLE("What?!");
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey,
 | 
						|
                             const VoidOrObjectStoreKeyPathString&
 | 
						|
                                 aAutoIncrementedObjectStoreKeyPath) const {
 | 
						|
  uint32_t len = mStrings.Length();
 | 
						|
  JS::Rooted<JS::Value> value(aCx);
 | 
						|
 | 
						|
  aKey.Unset();
 | 
						|
 | 
						|
  for (uint32_t i = 0; i < len; ++i) {
 | 
						|
    nsresult rv =
 | 
						|
        GetJSValFromKeyPathString(aCx, aValue, mStrings[i], value.address(),
 | 
						|
                                  DoNotCreateProperties, nullptr, nullptr);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      if (!aAutoIncrementedObjectStoreKeyPath.IsVoid() &&
 | 
						|
          mStrings[i].Equals(aAutoIncrementedObjectStoreKeyPath)) {
 | 
						|
        // We are extracting index keys of an object to be added if
 | 
						|
        // object store key path for a string key is provided.
 | 
						|
        // Because the autoIncrement primary key is part of
 | 
						|
        // this index key but is not defined in |aValue|, so we reserve
 | 
						|
        // the space here to update the key later in parent.
 | 
						|
        aKey.ReserveAutoIncrementKey(IsArray() && i == 0);
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    auto result = aKey.AppendItem(aCx, IsArray() && i == 0, value);
 | 
						|
    if (result.isErr()) {
 | 
						|
      NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
 | 
						|
      if (result.inspectErr().Is(SpecialValues::Exception)) {
 | 
						|
        result.unwrapErr().AsException().SuppressException();
 | 
						|
      }
 | 
						|
      return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  aKey.FinishArray();
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
 | 
						|
                                    JS::Value* aOutVal) const {
 | 
						|
  NS_ASSERTION(IsValid(), "This doesn't make sense!");
 | 
						|
 | 
						|
  if (IsString()) {
 | 
						|
    return GetJSValFromKeyPathString(aCx, aValue, mStrings[0], aOutVal,
 | 
						|
                                     DoNotCreateProperties, nullptr, nullptr);
 | 
						|
  }
 | 
						|
 | 
						|
  const uint32_t len = mStrings.Length();
 | 
						|
  JS::Rooted<JSObject*> arrayObj(aCx, JS::NewArrayObject(aCx, len));
 | 
						|
  if (!arrayObj) {
 | 
						|
    return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
  }
 | 
						|
 | 
						|
  JS::Rooted<JS::Value> value(aCx);
 | 
						|
  for (uint32_t i = 0; i < len; ++i) {
 | 
						|
    nsresult rv =
 | 
						|
        GetJSValFromKeyPathString(aCx, aValue, mStrings[i], value.address(),
 | 
						|
                                  DoNotCreateProperties, nullptr, nullptr);
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!JS_DefineElement(aCx, arrayObj, i, value, JSPROP_ENUMERATE)) {
 | 
						|
      IDB_REPORT_INTERNAL_ERR();
 | 
						|
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  aOutVal->setObject(*arrayObj);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue,
 | 
						|
                                     Key& aKey,
 | 
						|
                                     ExtractOrCreateKeyCallback aCallback,
 | 
						|
                                     void* aClosure) const {
 | 
						|
  NS_ASSERTION(IsString(), "This doesn't make sense!");
 | 
						|
 | 
						|
  JS::Rooted<JS::Value> value(aCx);
 | 
						|
 | 
						|
  aKey.Unset();
 | 
						|
 | 
						|
  nsresult rv =
 | 
						|
      GetJSValFromKeyPathString(aCx, aValue, mStrings[0], value.address(),
 | 
						|
                                CreateProperties, aCallback, aClosure);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
 | 
						|
  auto result = aKey.AppendItem(aCx, false, value);
 | 
						|
  if (result.isErr()) {
 | 
						|
    NS_ASSERTION(aKey.IsUnset(), "Should be unset");
 | 
						|
    if (result.inspectErr().Is(SpecialValues::Exception)) {
 | 
						|
      result.unwrapErr().AsException().SuppressException();
 | 
						|
    }
 | 
						|
    return value.isUndefined() ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
 | 
						|
  }
 | 
						|
 | 
						|
  aKey.FinishArray();
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsAutoString KeyPath::SerializeToString() const {
 | 
						|
  NS_ASSERTION(IsValid(), "Check to see if I'm valid first!");
 | 
						|
 | 
						|
  if (IsString()) {
 | 
						|
    return nsAutoString{mStrings[0]};
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsArray()) {
 | 
						|
    nsAutoString res;
 | 
						|
 | 
						|
    // We use a comma in the beginning to indicate that it's an array of
 | 
						|
    // key paths. This is to be able to tell a string-keypath from an
 | 
						|
    // array-keypath which contains only one item.
 | 
						|
    // It also makes serializing easier :-)
 | 
						|
    const uint32_t len = mStrings.Length();
 | 
						|
    for (uint32_t i = 0; i < len; ++i) {
 | 
						|
      res.Append(',');
 | 
						|
      res.Append(mStrings[i]);
 | 
						|
    }
 | 
						|
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT_UNREACHABLE("What?");
 | 
						|
  return {};
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
KeyPath KeyPath::DeserializeFromString(const nsAString& aString) {
 | 
						|
  KeyPath keyPath(0);
 | 
						|
 | 
						|
  if (!aString.IsEmpty() && aString.First() == ',') {
 | 
						|
    keyPath.SetType(KeyPathType::Array);
 | 
						|
 | 
						|
    // We use a comma in the beginning to indicate that it's an array of
 | 
						|
    // key paths. This is to be able to tell a string-keypath from an
 | 
						|
    // array-keypath which contains only one item.
 | 
						|
    nsCharSeparatedTokenizerTemplate<NS_TokenizerIgnoreNothing> tokenizer(
 | 
						|
        aString, ',');
 | 
						|
    tokenizer.nextToken();
 | 
						|
    while (tokenizer.hasMoreTokens()) {
 | 
						|
      keyPath.mStrings.AppendElement(tokenizer.nextToken());
 | 
						|
    }
 | 
						|
 | 
						|
    if (tokenizer.separatorAfterCurrentToken()) {
 | 
						|
      // There is a trailing comma, indicating the original KeyPath has
 | 
						|
      // a trailing empty string, i.e. [..., '']. We should append this
 | 
						|
      // empty string.
 | 
						|
      keyPath.mStrings.EmplaceBack();
 | 
						|
    }
 | 
						|
 | 
						|
    return keyPath;
 | 
						|
  }
 | 
						|
 | 
						|
  keyPath.SetType(KeyPathType::String);
 | 
						|
  keyPath.mStrings.AppendElement(aString);
 | 
						|
 | 
						|
  return keyPath;
 | 
						|
}
 | 
						|
 | 
						|
nsresult KeyPath::ToJSVal(JSContext* aCx,
 | 
						|
                          JS::MutableHandle<JS::Value> aValue) const {
 | 
						|
  if (IsArray()) {
 | 
						|
    uint32_t len = mStrings.Length();
 | 
						|
    JS::Rooted<JSObject*> array(aCx, JS::NewArrayObject(aCx, len));
 | 
						|
    if (!array) {
 | 
						|
      IDB_WARNING("Failed to make array!");
 | 
						|
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
    }
 | 
						|
 | 
						|
    for (uint32_t i = 0; i < len; ++i) {
 | 
						|
      JS::Rooted<JS::Value> val(aCx);
 | 
						|
      nsString tmp(mStrings[i]);
 | 
						|
      if (!xpc::StringToJsval(aCx, tmp, &val)) {
 | 
						|
        IDB_REPORT_INTERNAL_ERR();
 | 
						|
        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!JS_DefineElement(aCx, array, i, val, JSPROP_ENUMERATE)) {
 | 
						|
        IDB_REPORT_INTERNAL_ERR();
 | 
						|
        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    aValue.setObject(*array);
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (IsString()) {
 | 
						|
    nsString tmp(mStrings[0]);
 | 
						|
    if (!xpc::StringToJsval(aCx, tmp, aValue)) {
 | 
						|
      IDB_REPORT_INTERNAL_ERR();
 | 
						|
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
 | 
						|
    }
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  aValue.setNull();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult KeyPath::ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aValue) const {
 | 
						|
  JS::Rooted<JS::Value> value(aCx);
 | 
						|
  nsresult rv = ToJSVal(aCx, &value);
 | 
						|
  if (NS_SUCCEEDED(rv)) {
 | 
						|
    aValue = value;
 | 
						|
  }
 | 
						|
  return rv;
 | 
						|
}
 | 
						|
 | 
						|
bool KeyPath::IsAllowedForObjectStore(bool aAutoIncrement) const {
 | 
						|
  // Any keypath that passed validation is allowed for non-autoIncrement
 | 
						|
  // objectStores.
 | 
						|
  if (!aAutoIncrement) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Array keypaths are not allowed for autoIncrement objectStores.
 | 
						|
  if (IsArray()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Neither are empty strings.
 | 
						|
  if (IsEmpty()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // Everything else is ok.
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla::dom::indexedDB
 |