зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1544750 - Part 2: Refactor Key::EncodeJSValInternal to show direct correspondence to spec r=asuth,tcampbell
This commit adds the text of the spec as inline comments and refactors the code such that it directly corresponds to the spec's steps. This makes it easier to understand how the spec's algorithm is implemented. Differential Revision: https://phabricator.services.mozilla.com/D28976 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
334edd038c
Коммит
94fda141b7
|
@ -2134,7 +2134,12 @@ class EncodeKeysFunction final : public mozIStorageFunction {
|
|||
} else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) {
|
||||
nsString stringKey;
|
||||
aArguments->GetString(0, stringKey);
|
||||
key.SetFromString(stringKey);
|
||||
ErrorResult errorResult;
|
||||
auto result = key.SetFromString(stringKey, errorResult);
|
||||
if (!result.Is(Ok, errorResult)) {
|
||||
return result.Is(Invalid, errorResult) ? NS_ERROR_DOM_INDEXEDDB_DATA_ERR
|
||||
: errorResult.StealNSResult();
|
||||
}
|
||||
} else {
|
||||
NS_WARNING("Don't call me with the wrong type of arguments!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
|
@ -18337,9 +18342,13 @@ nsresult DatabaseOperationBase::BindKeyRangeToStatement(
|
|||
|
||||
if (!aKeyRange.lower().IsUnset()) {
|
||||
Key lower;
|
||||
rv = aKeyRange.lower().ToLocaleBasedKey(lower, aLocale);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
ErrorResult errorResult;
|
||||
auto result =
|
||||
aKeyRange.lower().ToLocaleBasedKey(lower, aLocale, errorResult);
|
||||
if (!result.Is(Ok, errorResult)) {
|
||||
return NS_WARN_IF(result.Is(Exception, errorResult))
|
||||
? errorResult.StealNSResult()
|
||||
: NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
}
|
||||
|
||||
rv = lower.BindToStatement(aStatement, NS_LITERAL_CSTRING("lower_key"));
|
||||
|
@ -18354,9 +18363,13 @@ nsresult DatabaseOperationBase::BindKeyRangeToStatement(
|
|||
|
||||
if (!aKeyRange.upper().IsUnset()) {
|
||||
Key upper;
|
||||
rv = aKeyRange.upper().ToLocaleBasedKey(upper, aLocale);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
ErrorResult errorResult;
|
||||
auto result =
|
||||
aKeyRange.upper().ToLocaleBasedKey(upper, aLocale, errorResult);
|
||||
if (!result.Is(Ok, errorResult)) {
|
||||
return NS_WARN_IF(result.Is(Exception, errorResult))
|
||||
? errorResult.StealNSResult()
|
||||
: NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
}
|
||||
|
||||
rv = upper.BindToStatement(aStatement, NS_LITERAL_CSTRING("upper_key"));
|
||||
|
@ -20571,9 +20584,12 @@ nsresult OpenDatabaseOp::UpdateLocaleAwareIndex(
|
|||
return rv;
|
||||
}
|
||||
|
||||
rv = oldKey.ToLocaleBasedKey(newSortKey, aLocale);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
ErrorResult errorResult;
|
||||
auto result = oldKey.ToLocaleBasedKey(newSortKey, aLocale, errorResult);
|
||||
if (!result.Is(Ok, errorResult)) {
|
||||
return NS_WARN_IF(result.Is(Exception, errorResult))
|
||||
? errorResult.StealNSResult()
|
||||
: NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
}
|
||||
|
||||
rv = newSortKey.BindToStatement(writeStmt,
|
||||
|
@ -25777,24 +25793,31 @@ void Cursor::OpenOp::GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen) {
|
|||
MOZ_ASSERT(aOpen);
|
||||
|
||||
if (mOptionalKeyRange.isSome()) {
|
||||
ErrorResult rv;
|
||||
const SerializedKeyRange& range = mOptionalKeyRange.ref();
|
||||
if (range.isOnly()) {
|
||||
*aKey = range.lower();
|
||||
*aOpen = false;
|
||||
if (mCursor->IsLocaleAware()) {
|
||||
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
||||
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale, rv);
|
||||
}
|
||||
} else {
|
||||
*aKey = aLowerBound ? range.lower() : range.upper();
|
||||
*aOpen = aLowerBound ? range.lowerOpen() : range.upperOpen();
|
||||
if (mCursor->IsLocaleAware()) {
|
||||
if (aLowerBound) {
|
||||
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
||||
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale, rv);
|
||||
} else {
|
||||
range.upper().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
||||
range.upper().ToLocaleBasedKey(*aKey, mCursor->mLocale, rv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXX Explain why the error is ignored here (If it's impossible, then we
|
||||
// should change this to an assertion.)
|
||||
if (rv.Failed()) {
|
||||
rv.SuppressException();
|
||||
}
|
||||
} else {
|
||||
*aOpen = false;
|
||||
}
|
||||
|
|
|
@ -381,15 +381,21 @@ void IDBCursor::Continue(JSContext* aCx, JS::Handle<JS::Value> aKey,
|
|||
}
|
||||
|
||||
Key key;
|
||||
key.SetFromJSVal(aCx, aKey, aRv);
|
||||
if (aRv.Failed()) {
|
||||
auto result = key.SetFromJSVal(aCx, aKey, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsLocaleAware() && !key.IsUnset()) {
|
||||
Key tmp;
|
||||
aRv = key.ToLocaleBasedKey(tmp, mSourceIndex->Locale());
|
||||
if (aRv.Failed()) {
|
||||
result = key.ToLocaleBasedKey(tmp, mSourceIndex->Locale(), aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
key = tmp;
|
||||
|
@ -479,15 +485,21 @@ void IDBCursor::ContinuePrimaryKey(JSContext* aCx, JS::Handle<JS::Value> aKey,
|
|||
}
|
||||
|
||||
Key key;
|
||||
key.SetFromJSVal(aCx, aKey, aRv);
|
||||
if (aRv.Failed()) {
|
||||
auto result = key.SetFromJSVal(aCx, aKey, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsLocaleAware() && !key.IsUnset()) {
|
||||
Key tmp;
|
||||
aRv = key.ToLocaleBasedKey(tmp, mSourceIndex->Locale());
|
||||
if (aRv.Failed()) {
|
||||
result = key.ToLocaleBasedKey(tmp, mSourceIndex->Locale(), aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
key = tmp;
|
||||
|
@ -501,8 +513,11 @@ void IDBCursor::ContinuePrimaryKey(JSContext* aCx, JS::Handle<JS::Value> aKey,
|
|||
}
|
||||
|
||||
Key primaryKey;
|
||||
primaryKey.SetFromJSVal(aCx, aPrimaryKey, aRv);
|
||||
if (aRv.Failed()) {
|
||||
result = primaryKey.SetFromJSVal(aCx, aPrimaryKey, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -502,13 +502,19 @@ already_AddRefed<IDBOpenDBRequest> IDBFactory::DeleteDatabase(
|
|||
int16_t IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst,
|
||||
JS::Handle<JS::Value> aSecond, ErrorResult& aRv) {
|
||||
Key first, second;
|
||||
first.SetFromJSVal(aCx, aFirst, aRv);
|
||||
if (aRv.Failed()) {
|
||||
auto result = first.SetFromJSVal(aCx, aFirst, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
second.SetFromJSVal(aCx, aSecond, aRv);
|
||||
if (aRv.Failed()) {
|
||||
result = second.SetFromJSVal(aCx, aSecond, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,8 +21,11 @@ namespace {
|
|||
|
||||
void GetKeyFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aVal, Key& aKey,
|
||||
ErrorResult& aRv) {
|
||||
aKey.SetFromJSVal(aCx, aVal, aRv);
|
||||
if (aRv.Failed()) {
|
||||
auto result = aKey.SetFromJSVal(aCx, aVal, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -947,9 +947,12 @@ void IDBObjectStore::AppendIndexUpdateInfo(
|
|||
updateInfo->indexId() = aIndexID;
|
||||
updateInfo->value() = key;
|
||||
if (localeAware) {
|
||||
aRv = key.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
auto result =
|
||||
key.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale, aRv);
|
||||
if (NS_WARN_IF(!result.Is(Ok, aRv))) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -986,8 +989,8 @@ void IDBObjectStore::AppendIndexUpdateInfo(
|
|||
}
|
||||
|
||||
Key value;
|
||||
value.SetFromJSVal(aCx, arrayItem, aRv);
|
||||
if (aRv.Failed() || value.IsUnset()) {
|
||||
auto result = value.SetFromJSVal(aCx, arrayItem, aRv);
|
||||
if (!result.Is(Ok, aRv) || value.IsUnset()) {
|
||||
// Not a value we can do anything with, ignore it.
|
||||
aRv.SuppressException();
|
||||
continue;
|
||||
|
@ -997,17 +1000,20 @@ void IDBObjectStore::AppendIndexUpdateInfo(
|
|||
updateInfo->indexId() = aIndexID;
|
||||
updateInfo->value() = value;
|
||||
if (localeAware) {
|
||||
aRv = value.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
auto result =
|
||||
value.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale, aRv);
|
||||
if (NS_WARN_IF(!result.Is(Ok, aRv))) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Key value;
|
||||
value.SetFromJSVal(aCx, val, aRv);
|
||||
if (aRv.Failed() || value.IsUnset()) {
|
||||
auto result = value.SetFromJSVal(aCx, val, aRv);
|
||||
if (!result.Is(Ok, aRv) || value.IsUnset()) {
|
||||
// Not a value we can do anything with, ignore it.
|
||||
aRv.SuppressException();
|
||||
return;
|
||||
|
@ -1017,9 +1023,12 @@ void IDBObjectStore::AppendIndexUpdateInfo(
|
|||
updateInfo->indexId() = aIndexID;
|
||||
updateInfo->value() = value;
|
||||
if (localeAware) {
|
||||
aRv = value.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
auto result =
|
||||
value.ToLocaleBasedKey(updateInfo->localizedValue(), aLocale, aRv);
|
||||
if (NS_WARN_IF(!result.Is(Ok, aRv))) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1417,8 +1426,11 @@ void IDBObjectStore::GetAddInfo(JSContext* aCx, ValueWrapper& aValueWrapper,
|
|||
|
||||
if (!HasValidKeyPath()) {
|
||||
// Out-of-line keys must be passed in.
|
||||
aKey.SetFromJSVal(aCx, aKeyVal, aRv);
|
||||
if (aRv.Failed()) {
|
||||
auto result = aKey.SetFromJSVal(aCx, aKeyVal, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
if (result.Is(Invalid, aRv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (!isAutoIncrement) {
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_indexeddb_idbresult_h__
|
||||
#define mozilla_dom_indexeddb_idbresult_h__
|
||||
|
||||
#include <mozilla/ErrorResult.h>
|
||||
#include <mozilla/Variant.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace indexedDB {
|
||||
|
||||
// IDBSpecialValue represents two special return values, distinct from any other
|
||||
// value, used in several places in the IndexedDB spec.
|
||||
enum class IDBSpecialValue {
|
||||
Failure,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
struct OkType final {
|
||||
T mValue;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct OkType<void> final {};
|
||||
|
||||
template <IDBSpecialValue Value>
|
||||
using SpecialConstant = std::integral_constant<IDBSpecialValue, Value>;
|
||||
using FailureType = detail::SpecialConstant<IDBSpecialValue::Failure>;
|
||||
using InvalidType = detail::SpecialConstant<IDBSpecialValue::Invalid>;
|
||||
struct ExceptionType final {};
|
||||
struct VoidType final {};
|
||||
} // namespace detail
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
constexpr inline detail::OkType<std::remove_reference_t<T>> Ok(T&& aValue) {
|
||||
return {std::forward<T>(aValue)};
|
||||
}
|
||||
|
||||
constexpr inline detail::OkType<void> Ok() { return {}; }
|
||||
|
||||
constexpr const detail::FailureType Failure;
|
||||
constexpr const detail::InvalidType Invalid;
|
||||
constexpr const detail::ExceptionType Exception;
|
||||
} // namespace
|
||||
|
||||
namespace detail {
|
||||
template <IDBSpecialValue... Elements>
|
||||
struct IsSortedSet;
|
||||
|
||||
template <IDBSpecialValue First, IDBSpecialValue Second,
|
||||
IDBSpecialValue... Rest>
|
||||
struct IsSortedSet<First, Second, Rest...>
|
||||
: std::integral_constant<bool, IsSortedSet<First, Second>::value &&
|
||||
IsSortedSet<Second, Rest...>::value> {};
|
||||
|
||||
template <IDBSpecialValue First, IDBSpecialValue Second>
|
||||
struct IsSortedSet<First, Second>
|
||||
: std::integral_constant<bool, (First < Second)> {};
|
||||
|
||||
template <IDBSpecialValue First>
|
||||
struct IsSortedSet<First> : std::true_type {};
|
||||
|
||||
template <>
|
||||
struct IsSortedSet<> : std::true_type {};
|
||||
|
||||
// IDBResultBase contains the bulk of the implementation of IDBResult, namely
|
||||
// functionality that's applicable to all values of T.
|
||||
template <typename T, IDBSpecialValue... S>
|
||||
class IDBResultBase {
|
||||
// This assertion ensures that permutations of the set of possible special
|
||||
// values don't create distinct types.
|
||||
static_assert(detail::IsSortedSet<S...>::value,
|
||||
"special value list must be sorted and unique");
|
||||
|
||||
template <typename R, IDBSpecialValue... U>
|
||||
friend class IDBResultBase;
|
||||
|
||||
protected:
|
||||
using ValueType = detail::OkType<T>;
|
||||
|
||||
public:
|
||||
// Construct a normal result. Use the Ok function to create an object of type
|
||||
// ValueType.
|
||||
MOZ_IMPLICIT IDBResultBase(const ValueType& aValue) : mVariant(aValue) {}
|
||||
|
||||
MOZ_IMPLICIT IDBResultBase(detail::ExceptionType)
|
||||
: mVariant(detail::ExceptionType{}) {}
|
||||
|
||||
template <IDBSpecialValue Special>
|
||||
MOZ_IMPLICIT IDBResultBase(detail::SpecialConstant<Special>)
|
||||
: mVariant(detail::SpecialConstant<Special>{}) {}
|
||||
|
||||
// Construct an IDBResult from another IDBResult whose set of possible special
|
||||
// values is a subset of this one's.
|
||||
template <IDBSpecialValue... U>
|
||||
MOZ_IMPLICIT IDBResultBase(const IDBResultBase<T, U...>& aOther)
|
||||
: mVariant(aOther.mVariant.match(
|
||||
[](auto& aVariant) { return VariantType{aVariant}; })) {}
|
||||
|
||||
// Test whether the result is a normal return value. The choice of the first
|
||||
// parameter's type makes it possible to write `result.Is(Ok, rv)`, promoting
|
||||
// readability and uniformity with other functions in the overload set.
|
||||
bool Is(detail::OkType<void> (*)(), const ErrorResult& aRv) const {
|
||||
AssertConsistency(aRv);
|
||||
return mVariant.template is<ValueType>();
|
||||
}
|
||||
|
||||
bool Is(detail::ExceptionType, const ErrorResult& aRv) const {
|
||||
AssertConsistency(aRv);
|
||||
return mVariant.template is<detail::ExceptionType>();
|
||||
}
|
||||
|
||||
template <IDBSpecialValue Special>
|
||||
bool Is(detail::SpecialConstant<Special>, const ErrorResult& aRv) const {
|
||||
AssertConsistency(aRv);
|
||||
return mVariant.template is<detail::SpecialConstant<Special>>();
|
||||
}
|
||||
|
||||
protected:
|
||||
void AssertConsistency(const ErrorResult& aRv) const {
|
||||
MOZ_ASSERT(aRv.Failed() == mVariant.template is<detail::ExceptionType>());
|
||||
}
|
||||
|
||||
using VariantType =
|
||||
Variant<ValueType, detail::ExceptionType, detail::SpecialConstant<S>...>;
|
||||
|
||||
VariantType mVariant;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// Represents a return value of an IndexedDB algorithm. T is the type of the
|
||||
// regular return value, while S is a list of special values that can be
|
||||
// returned by the particular algorithm.
|
||||
template <typename T, IDBSpecialValue... S>
|
||||
class MOZ_MUST_USE_TYPE IDBResult : public detail::IDBResultBase<T, S...> {
|
||||
public:
|
||||
using IDBResult::IDBResultBase::IDBResultBase;
|
||||
|
||||
// Get a reference to the regular return value, asserting that this object
|
||||
// is indeed a regular return value.
|
||||
T& Unwrap(const ErrorResult& aRv) {
|
||||
return const_cast<T&>(static_cast<const IDBResult*>(this)->Unwrap(aRv));
|
||||
}
|
||||
|
||||
const T& Unwrap(const ErrorResult& aRv) const {
|
||||
this->AssertConsistency(aRv);
|
||||
return this->mVariant.template as<typename IDBResult::ValueType>().mValue;
|
||||
}
|
||||
};
|
||||
|
||||
template <IDBSpecialValue... S>
|
||||
class MOZ_MUST_USE_TYPE IDBResult<void, S...>
|
||||
: public detail::IDBResultBase<void, S...> {
|
||||
public:
|
||||
using IDBResult::IDBResultBase::IDBResultBase;
|
||||
};
|
||||
|
||||
} // namespace indexedDB
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_indexeddb_idbresult_h__
|
|
@ -110,6 +110,16 @@ namespace indexedDB {
|
|||
[[]] // 0x80
|
||||
*/
|
||||
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::SetFromString(
|
||||
const nsAString& aString, ErrorResult& aRv) {
|
||||
mBuffer.Truncate();
|
||||
auto result = EncodeString(aString, 0, aRv);
|
||||
if (result.Is(Ok, aRv)) {
|
||||
TrimBuffer();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// |aPos| should point to the type indicator.
|
||||
// The returned length doesn't include the type indicator
|
||||
// or the terminator.
|
||||
|
@ -125,15 +135,16 @@ static size_t LengthOfEncodedBinary(const unsigned char* aPos,
|
|||
return iter - aPos;
|
||||
}
|
||||
|
||||
nsresult Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::ToLocaleBasedKey(
|
||||
Key& aTarget, const nsCString& aLocale, ErrorResult& aRv) const {
|
||||
if (IsUnset()) {
|
||||
aTarget.Unset();
|
||||
return NS_OK;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
if (IsFloat() || IsDate() || IsBinary()) {
|
||||
aTarget.mBuffer = mBuffer;
|
||||
return NS_OK;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
aTarget.mBuffer.Truncate();
|
||||
|
@ -166,7 +177,7 @@ nsresult Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const {
|
|||
if (canShareBuffers) {
|
||||
MOZ_ASSERT(it == end);
|
||||
aTarget.mBuffer = mBuffer;
|
||||
return NS_OK;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
aTarget.mBuffer.SetCapacity(mBuffer.Length());
|
||||
|
@ -176,7 +187,8 @@ nsresult Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const {
|
|||
if (it > start) {
|
||||
char* buffer;
|
||||
if (!aTarget.mBuffer.GetMutableData(&buffer, it - start)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
std::copy(start, it, buffer);
|
||||
|
@ -205,21 +217,24 @@ nsresult Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const {
|
|||
if (type == eTerminator) {
|
||||
// Copy array TypeID and terminator from raw key
|
||||
if (!updateBufferAndIter(0)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return Exception;
|
||||
}
|
||||
} else if (type == eFloat || type == eDate) {
|
||||
// Copy number from raw key
|
||||
const size_t byteCount = std::min(sizeof(uint64_t), size_t(end - it - 1));
|
||||
|
||||
if (!updateBufferAndIter(byteCount)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return Exception;
|
||||
}
|
||||
} else if (type == eBinary) {
|
||||
// skip all binary data
|
||||
auto binaryLength = LengthOfEncodedBinary(it, end);
|
||||
|
||||
if (!updateBufferAndIter(binaryLength)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return Exception;
|
||||
}
|
||||
} else {
|
||||
// Decode string and reencode
|
||||
|
@ -228,117 +243,146 @@ nsresult Key::ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const {
|
|||
|
||||
nsDependentString str;
|
||||
DecodeString(it, end, str);
|
||||
nsresult rv = aTarget.EncodeLocaleString(str, typeOffset, aLocale);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
auto result = aTarget.EncodeLocaleString(str, typeOffset, aLocale, aRv);
|
||||
if (NS_WARN_IF(!result.Is(Ok, aRv))) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
aTarget.TrimBuffer();
|
||||
return NS_OK;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
nsresult Key::EncodeJSValInternal(JSContext* aCx, JS::Handle<JS::Value> aVal,
|
||||
uint8_t aTypeOffset,
|
||||
uint16_t aRecursionDepth) {
|
||||
class MOZ_STACK_CLASS Key::ArrayValueEncoder final {
|
||||
public:
|
||||
ArrayValueEncoder(Key& aKey, const uint8_t aTypeOffset,
|
||||
const uint16_t aRecursionDepth)
|
||||
: mKey(aKey),
|
||||
mTypeOffset(aTypeOffset),
|
||||
mRecursionDepth(aRecursionDepth) {}
|
||||
|
||||
void AddToSeenSet(JSContext* const aCx, JS::HandleObject) {
|
||||
++mRecursionDepth;
|
||||
}
|
||||
|
||||
void BeginSubkeyList() {
|
||||
mTypeOffset += Key::eMaxType;
|
||||
if (mTypeOffset == eMaxType * kMaxArrayCollapse) {
|
||||
mKey.mBuffer.Append(mTypeOffset);
|
||||
mTypeOffset = 0;
|
||||
}
|
||||
MOZ_ASSERT(mTypeOffset % eMaxType == 0,
|
||||
"Current type offset must indicate beginning of array");
|
||||
MOZ_ASSERT(mTypeOffset < eMaxType * kMaxArrayCollapse);
|
||||
}
|
||||
|
||||
IDBResult<void, IDBSpecialValue::Invalid> ConvertSubkey(
|
||||
JSContext* const aCx, JS::HandleValue aEntry, const uint32_t aIndex,
|
||||
ErrorResult& aRv) {
|
||||
const auto result = mKey.EncodeJSValInternal(aCx, aEntry, mTypeOffset,
|
||||
mRecursionDepth, aRv);
|
||||
mTypeOffset = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void EndSubkeyList() const { mKey.mBuffer.Append(eTerminator + mTypeOffset); }
|
||||
|
||||
private:
|
||||
Key& mKey;
|
||||
uint8_t mTypeOffset;
|
||||
uint16_t mRecursionDepth;
|
||||
};
|
||||
|
||||
// Implements the following algorithm:
|
||||
// https://w3c.github.io/IndexedDB/#convert-a-value-to-a-key
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeJSValInternal(
|
||||
JSContext* const aCx, JS::Handle<JS::Value> aVal, uint8_t aTypeOffset,
|
||||
const uint16_t aRecursionDepth, ErrorResult& aRv) {
|
||||
static_assert(eMaxType * kMaxArrayCollapse < 256, "Unable to encode jsvals.");
|
||||
|
||||
// 1. If `seen` was not given, let `seen` be a new empty set.
|
||||
// 2. If `input` is in `seen` return invalid.
|
||||
// Note: we replace this check with a simple recursion depth check.
|
||||
if (NS_WARN_IF(aRecursionDepth == kMaxRecursionDepth)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
if (aVal.isString()) {
|
||||
nsAutoJSString str;
|
||||
if (!str.init(aCx, aVal)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
return EncodeString(str, aTypeOffset);
|
||||
}
|
||||
// 3. Jump to the appropriate step below:
|
||||
// Note: some cases appear out of order to make the implementation more
|
||||
// straightforward. This shouldn't affect observable behavior.
|
||||
|
||||
// If Type(`input`) is Number
|
||||
if (aVal.isNumber()) {
|
||||
double d = aVal.toNumber();
|
||||
if (mozilla::IsNaN(d)) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
const auto number = aVal.toNumber();
|
||||
|
||||
// 1. If `input` is NaN then return invalid.
|
||||
if (mozilla::IsNaN(number)) {
|
||||
return Invalid;
|
||||
}
|
||||
EncodeNumber(d, eFloat + aTypeOffset);
|
||||
return NS_OK;
|
||||
|
||||
// 2. Otherwise, return a new key with type `number` and value `input`.
|
||||
EncodeNumber(number, eFloat + aTypeOffset);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// If Type(`input`) is String
|
||||
if (aVal.isString()) {
|
||||
// 1. Return a new key with type `string` and value `input`.
|
||||
nsAutoJSString string;
|
||||
if (!string.init(aCx, aVal)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
return EncodeString(string, aTypeOffset, aRv);
|
||||
}
|
||||
|
||||
if (aVal.isObject()) {
|
||||
JS::Rooted<JSObject*> obj(aCx, &aVal.toObject());
|
||||
JS::RootedObject object(aCx, &aVal.toObject());
|
||||
|
||||
js::ESClass cls;
|
||||
if (!js::GetBuiltinClass(aCx, obj, &cls)) {
|
||||
js::ESClass builtinClass;
|
||||
if (!js::GetBuiltinClass(aCx, object, &builtinClass)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
if (cls == js::ESClass::Array) {
|
||||
aTypeOffset += eMaxType;
|
||||
|
||||
if (aTypeOffset == eMaxType * kMaxArrayCollapse) {
|
||||
mBuffer.Append(aTypeOffset);
|
||||
aTypeOffset = 0;
|
||||
}
|
||||
NS_ASSERTION((aTypeOffset % eMaxType) == 0 &&
|
||||
aTypeOffset < (eMaxType * kMaxArrayCollapse),
|
||||
"Wrong typeoffset");
|
||||
|
||||
uint32_t length;
|
||||
if (!JS_GetArrayLength(aCx, obj, &length)) {
|
||||
// If `input` is a Date (has a [[DateValue]] internal slot)
|
||||
if (builtinClass == js::ESClass::Date) {
|
||||
// 1. Let `ms` be the value of `input`’s [[DateValue]] internal slot.
|
||||
double ms;
|
||||
if (!js::DateGetMsecSinceEpoch(aCx, object, &ms)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
for (uint32_t index = 0; index < length; index++) {
|
||||
JS::Rooted<JS::Value> val(aCx);
|
||||
if (!JS_GetElement(aCx, obj, index, &val)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
// 2. If `ms` is NaN then return invalid.
|
||||
if (mozilla::IsNaN(ms)) {
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
nsresult rv =
|
||||
EncodeJSValInternal(aCx, val, aTypeOffset, aRecursionDepth + 1);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
// 3. Otherwise, return a new key with type `date` and value `ms`.
|
||||
EncodeNumber(ms, eDate + aTypeOffset);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
aTypeOffset = 0;
|
||||
// If `input` is a buffer source type
|
||||
if (JS::IsArrayBufferObject(object) || JS_IsArrayBufferViewObject(object)) {
|
||||
const bool isViewObject = JS_IsArrayBufferViewObject(object);
|
||||
return EncodeBinary(object, isViewObject, aTypeOffset, aRv);
|
||||
}
|
||||
|
||||
mBuffer.Append(eTerminator + aTypeOffset);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (cls == js::ESClass::Date) {
|
||||
bool valid;
|
||||
if (!js::DateIsValid(aCx, obj, &valid)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
if (!valid) {
|
||||
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
}
|
||||
double t;
|
||||
if (!js::DateGetMsecSinceEpoch(aCx, obj, &t)) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
}
|
||||
EncodeNumber(t, eDate + aTypeOffset);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (JS::IsArrayBufferObject(obj)) {
|
||||
return EncodeBinary(obj, /* aIsViewObject */ false, aTypeOffset);
|
||||
}
|
||||
|
||||
if (JS_IsArrayBufferViewObject(obj)) {
|
||||
return EncodeBinary(obj, /* aIsViewObject */ true, aTypeOffset);
|
||||
// If IsArray(`input`)
|
||||
if (builtinClass == js::ESClass::Array) {
|
||||
ArrayValueEncoder encoder(*this, aTypeOffset, aRecursionDepth);
|
||||
return ConvertArrayValueToKey(aCx, object, encoder, aRv);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
// Otherwise
|
||||
// Return invalid.
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -431,29 +475,35 @@ nsresult Key::DecodeJSValInternal(const unsigned char*& aPos,
|
|||
#define TWO_BYTE_ADJUST (-0x7F)
|
||||
#define THREE_BYTE_SHIFT 6
|
||||
|
||||
nsresult Key::EncodeJSVal(JSContext* aCx, JS::Handle<JS::Value> aVal,
|
||||
uint8_t aTypeOffset) {
|
||||
return EncodeJSValInternal(aCx, aVal, aTypeOffset, 0);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeJSVal(
|
||||
JSContext* aCx, JS::Handle<JS::Value> aVal, uint8_t aTypeOffset,
|
||||
ErrorResult& aRv) {
|
||||
return EncodeJSValInternal(aCx, aVal, aTypeOffset, 0, aRv);
|
||||
}
|
||||
|
||||
nsresult Key::EncodeString(const nsAString& aString, uint8_t aTypeOffset) {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeString(
|
||||
const nsAString& aString, uint8_t aTypeOffset, ErrorResult& aRv) {
|
||||
const char16_t* start = aString.BeginReading();
|
||||
const char16_t* end = aString.EndReading();
|
||||
return EncodeString(start, end, aTypeOffset);
|
||||
return EncodeString(start, end, aTypeOffset, aRv);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
nsresult Key::EncodeString(const T* aStart, const T* aEnd,
|
||||
uint8_t aTypeOffset) {
|
||||
return EncodeAsString(aStart, aEnd, eString + aTypeOffset);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeString(const T* aStart,
|
||||
const T* aEnd,
|
||||
uint8_t aTypeOffset,
|
||||
ErrorResult& aRv) {
|
||||
return EncodeAsString(aStart, aEnd, eString + aTypeOffset, aRv);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
nsresult Key::EncodeAsString(const T* aStart, const T* aEnd, uint8_t aType) {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeAsString(
|
||||
const T* aStart, const T* aEnd, uint8_t aType, ErrorResult& aRv) {
|
||||
// First measure how long the encoded string will be.
|
||||
if (NS_WARN_IF(aStart > aEnd || UINT32_MAX - 2 < uintptr_t(aEnd - aStart))) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// The +2 is for initial 3 and trailing 0. We'll compensate for multi-byte
|
||||
|
@ -471,7 +521,8 @@ nsresult Key::EncodeAsString(const T* aStart, const T* aEnd, uint8_t aType) {
|
|||
size += char16_t(*iter) > TWO_BYTE_LIMIT ? 2 : 1;
|
||||
if (!size.isValid()) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -482,13 +533,15 @@ nsresult Key::EncodeAsString(const T* aStart, const T* aEnd, uint8_t aType) {
|
|||
|
||||
if (!size.isValid()) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
char* buffer;
|
||||
if (!mBuffer.GetMutableData(&buffer, size.value())) {
|
||||
IDB_REPORT_INTERNAL_ERR();
|
||||
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
buffer += oldLen;
|
||||
|
||||
|
@ -516,22 +569,23 @@ nsresult Key::EncodeAsString(const T* aStart, const T* aEnd, uint8_t aType) {
|
|||
|
||||
NS_ASSERTION(buffer == mBuffer.EndReading(), "Wrote wrong number of bytes");
|
||||
|
||||
return NS_OK;
|
||||
return indexedDB::Ok();
|
||||
}
|
||||
|
||||
nsresult Key::EncodeLocaleString(const nsDependentString& aString,
|
||||
uint8_t aTypeOffset,
|
||||
const nsCString& aLocale) {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeLocaleString(
|
||||
const nsDependentString& aString, uint8_t aTypeOffset,
|
||||
const nsCString& aLocale, ErrorResult& aRv) {
|
||||
const int length = aString.Length();
|
||||
if (length == 0) {
|
||||
return NS_OK;
|
||||
return Ok();
|
||||
}
|
||||
const UChar* ustr = reinterpret_cast<const UChar*>(aString.BeginReading());
|
||||
|
||||
UErrorCode uerror = U_ZERO_ERROR;
|
||||
UCollator* collator = ucol_open(aLocale.get(), &uerror);
|
||||
if (NS_WARN_IF(U_FAILURE(uerror))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return Exception;
|
||||
}
|
||||
MOZ_ASSERT(collator);
|
||||
|
||||
|
@ -546,11 +600,12 @@ nsresult Key::EncodeLocaleString(const nsDependentString& aString,
|
|||
|
||||
ucol_close(collator);
|
||||
if (NS_WARN_IF(sortKeyLength == 0)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
return EncodeString(keyBuffer.Elements(),
|
||||
keyBuffer.Elements() + sortKeyLength, aTypeOffset);
|
||||
keyBuffer.Elements() + sortKeyLength, aTypeOffset, aRv);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -658,8 +713,10 @@ double Key::DecodeNumber(const unsigned char*& aPos,
|
|||
return BitwiseCast<double>(bits);
|
||||
}
|
||||
|
||||
nsresult Key::EncodeBinary(JSObject* aObject, bool aIsViewObject,
|
||||
uint8_t aTypeOffset) {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::EncodeBinary(JSObject* aObject,
|
||||
bool aIsViewObject,
|
||||
uint8_t aTypeOffset,
|
||||
ErrorResult& aRv) {
|
||||
uint8_t* bufferData;
|
||||
uint32_t bufferLength;
|
||||
bool unused;
|
||||
|
@ -677,7 +734,7 @@ nsresult Key::EncodeBinary(JSObject* aObject, bool aIsViewObject,
|
|||
}
|
||||
|
||||
return EncodeAsString(bufferData, bufferData + bufferLength,
|
||||
eBinary + aTypeOffset);
|
||||
eBinary + aTypeOffset, aRv);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -760,21 +817,22 @@ nsresult Key::SetFromValueArray(mozIStorageValueArray* aValues,
|
|||
return SetFromSource(aValues, aIndex);
|
||||
}
|
||||
|
||||
void Key::SetFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aVal,
|
||||
ErrorResult& aRv) {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::SetFromJSVal(
|
||||
JSContext* aCx, JS::Handle<JS::Value> aVal, ErrorResult& aRv) {
|
||||
mBuffer.Truncate();
|
||||
|
||||
if (aVal.isNull() || aVal.isUndefined()) {
|
||||
Unset();
|
||||
return;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
aRv = EncodeJSVal(aCx, aVal, 0);
|
||||
if (aRv.Failed()) {
|
||||
auto result = EncodeJSVal(aCx, aVal, 0, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
Unset();
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
TrimBuffer();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
nsresult Key::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) const {
|
||||
|
@ -803,15 +861,14 @@ nsresult Key::ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aVal) const {
|
|||
return rv;
|
||||
}
|
||||
|
||||
nsresult Key::AppendItem(JSContext* aCx, bool aFirstOfArray,
|
||||
JS::Handle<JS::Value> aVal) {
|
||||
nsresult rv = EncodeJSVal(aCx, aVal, aFirstOfArray ? eMaxType : 0);
|
||||
if (NS_FAILED(rv)) {
|
||||
IDBResult<void, IDBSpecialValue::Invalid> Key::AppendItem(
|
||||
JSContext* aCx, bool aFirstOfArray, JS::Handle<JS::Value> aVal,
|
||||
ErrorResult& aRv) {
|
||||
auto result = EncodeJSVal(aCx, aVal, aFirstOfArray ? eMaxType : 0, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
Unset();
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
#ifndef mozilla_dom_indexeddb_key_h__
|
||||
#define mozilla_dom_indexeddb_key_h__
|
||||
|
||||
#include "mozilla/dom/indexedDB/IDBResult.h"
|
||||
|
||||
#include "js/RootingAPI.h"
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "nsString.h"
|
||||
|
||||
class mozIStorageStatement;
|
||||
|
@ -21,9 +25,6 @@ struct ParamTraits;
|
|||
} // namespace IPC
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ErrorResult;
|
||||
|
||||
namespace dom {
|
||||
namespace indexedDB {
|
||||
|
||||
|
@ -50,11 +51,6 @@ class Key {
|
|||
|
||||
explicit Key(const nsACString& aBuffer) : mBuffer(aBuffer) {}
|
||||
|
||||
Key& operator=(const nsAString& aString) {
|
||||
SetFromString(aString);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Key& operator=(int64_t aInt) {
|
||||
SetFromInteger(aInt);
|
||||
return *this;
|
||||
|
@ -133,11 +129,8 @@ class Key {
|
|||
Assert(pos >= BufferEnd());
|
||||
}
|
||||
|
||||
void SetFromString(const nsAString& aString) {
|
||||
mBuffer.Truncate();
|
||||
EncodeString(aString, 0);
|
||||
TrimBuffer();
|
||||
}
|
||||
IDBResult<void, IDBSpecialValue::Invalid> SetFromString(
|
||||
const nsAString& aString, ErrorResult& aRv);
|
||||
|
||||
void SetFromInteger(int64_t aInt) {
|
||||
mBuffer.Truncate();
|
||||
|
@ -145,17 +138,23 @@ class Key {
|
|||
TrimBuffer();
|
||||
}
|
||||
|
||||
void SetFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aVal,
|
||||
ErrorResult& aRv);
|
||||
// This function implements the standard algorithm "convert a value to a key".
|
||||
// A key return value is indicated by returning `true` whereas `false` means
|
||||
// either invalid (if `aRv.Failed()` is `false`) or an exception (otherwise).
|
||||
IDBResult<void, IDBSpecialValue::Invalid> SetFromJSVal(
|
||||
JSContext* aCx, JS::Handle<JS::Value> aVal, ErrorResult& aRv);
|
||||
|
||||
nsresult ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) const;
|
||||
|
||||
nsresult ToJSVal(JSContext* aCx, JS::Heap<JS::Value>& aVal) const;
|
||||
|
||||
nsresult AppendItem(JSContext* aCx, bool aFirstOfArray,
|
||||
JS::Handle<JS::Value> aVal);
|
||||
// See SetFromJSVal() for the meaning of values returned by this function.
|
||||
IDBResult<void, IDBSpecialValue::Invalid> AppendItem(
|
||||
JSContext* aCx, bool aFirstOfArray, JS::Handle<JS::Value> aVal,
|
||||
ErrorResult& aRv);
|
||||
|
||||
nsresult ToLocaleBasedKey(Key& aTarget, const nsCString& aLocale) const;
|
||||
IDBResult<void, IDBSpecialValue::Invalid> ToLocaleBasedKey(
|
||||
Key& aTarget, const nsCString& aLocale, ErrorResult& aRv) const;
|
||||
|
||||
void FinishArray() { TrimBuffer(); }
|
||||
|
||||
|
@ -182,7 +181,77 @@ class Key {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Implementation of the array branch of step 3 of
|
||||
// https://w3c.github.io/IndexedDB/#convert-value-to-key
|
||||
template <typename ArrayConversionPolicy>
|
||||
static IDBResult<void, IDBSpecialValue::Invalid> ConvertArrayValueToKey(
|
||||
JSContext* const aCx, JS::HandleObject aObject,
|
||||
ArrayConversionPolicy&& aPolicy, ErrorResult& aRv) {
|
||||
// 1. Let `len` be ? ToLength( ? Get(`input`, "length")).
|
||||
uint32_t len;
|
||||
if (!JS_GetArrayLength(aCx, aObject, &len)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// 2. Add `input` to `seen`.
|
||||
aPolicy.AddToSeenSet(aCx, aObject);
|
||||
|
||||
// 3. Let `keys` be a new empty list.
|
||||
aPolicy.BeginSubkeyList();
|
||||
|
||||
// 4. Let `index` be 0.
|
||||
uint32_t index = 0;
|
||||
|
||||
// 5. While `index` is less than `len`:
|
||||
while (index < len) {
|
||||
JS::RootedId indexId(aCx);
|
||||
if (!JS_IndexToId(aCx, index, &indexId)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// 1. Let `hop` be ? HasOwnProperty(`input`, `index`).
|
||||
bool hop;
|
||||
if (!JS_HasOwnPropertyById(aCx, aObject, indexId, &hop)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// 2. If `hop` is false, return invalid.
|
||||
if (!hop) {
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
// 3. Let `entry` be ? Get(`input`, `index`).
|
||||
JS::RootedValue entry(aCx);
|
||||
if (!JS_GetPropertyById(aCx, aObject, indexId, &entry)) {
|
||||
aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
// 4. Let `key` be the result of running the steps to convert a value to a
|
||||
// key with arguments `entry` and `seen`.
|
||||
// 5. ReturnIfAbrupt(`key`).
|
||||
// 6. If `key` is invalid abort these steps and return invalid.
|
||||
// 7. Append `key` to `keys`.
|
||||
auto result = aPolicy.ConvertSubkey(aCx, entry, index, aRv);
|
||||
if (!result.Is(Ok, aRv)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 8. Increase `index` by 1.
|
||||
index += 1;
|
||||
}
|
||||
|
||||
// 6. Return a new array key with value `keys`.
|
||||
aPolicy.EndSubkeyList();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private:
|
||||
class MOZ_STACK_CLASS ArrayValueEncoder;
|
||||
|
||||
const unsigned char* BufferStart() const {
|
||||
return reinterpret_cast<const unsigned char*>(mBuffer.BeginReading());
|
||||
}
|
||||
|
@ -203,24 +272,35 @@ class Key {
|
|||
}
|
||||
|
||||
// Encoding functions. These append the encoded value to the end of mBuffer
|
||||
nsresult EncodeJSVal(JSContext* aCx, JS::Handle<JS::Value> aVal,
|
||||
uint8_t aTypeOffset);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeJSVal(
|
||||
JSContext* aCx, JS::Handle<JS::Value> aVal, uint8_t aTypeOffset,
|
||||
ErrorResult& aRv);
|
||||
|
||||
nsresult EncodeString(const nsAString& aString, uint8_t aTypeOffset);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeString(
|
||||
const nsAString& aString, uint8_t aTypeOffset, ErrorResult& aRv);
|
||||
|
||||
template <typename T>
|
||||
nsresult EncodeString(const T* aStart, const T* aEnd, uint8_t aTypeOffset);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeString(const T* aStart,
|
||||
const T* aEnd,
|
||||
uint8_t aTypeOffset,
|
||||
ErrorResult& aRv);
|
||||
|
||||
template <typename T>
|
||||
nsresult EncodeAsString(const T* aStart, const T* aEnd, uint8_t aType);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeAsString(const T* aStart,
|
||||
const T* aEnd,
|
||||
uint8_t aType,
|
||||
ErrorResult& aRv);
|
||||
|
||||
nsresult EncodeLocaleString(const nsDependentString& aString,
|
||||
uint8_t aTypeOffset, const nsCString& aLocale);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeLocaleString(
|
||||
const nsDependentString& aString, uint8_t aTypeOffset,
|
||||
const nsCString& aLocale, ErrorResult& aRv);
|
||||
|
||||
void EncodeNumber(double aFloat, uint8_t aType);
|
||||
|
||||
nsresult EncodeBinary(JSObject* aObject, bool aIsViewObject,
|
||||
uint8_t aTypeOffset);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeBinary(JSObject* aObject,
|
||||
bool aIsViewObject,
|
||||
uint8_t aTypeOffset,
|
||||
ErrorResult& aRv);
|
||||
|
||||
// Decoding functions. aPos points into mBuffer and is adjusted to point
|
||||
// past the consumed value.
|
||||
|
@ -237,8 +317,9 @@ class Key {
|
|||
static JSObject* DecodeBinary(const unsigned char*& aPos,
|
||||
const unsigned char* aEnd, JSContext* aCx);
|
||||
|
||||
nsresult EncodeJSValInternal(JSContext* aCx, JS::Handle<JS::Value> aVal,
|
||||
uint8_t aTypeOffset, uint16_t aRecursionDepth);
|
||||
IDBResult<void, IDBSpecialValue::Invalid> EncodeJSValInternal(
|
||||
JSContext* aCx, JS::Handle<JS::Value> aVal, uint8_t aTypeOffset,
|
||||
uint16_t aRecursionDepth, ErrorResult& aRv);
|
||||
|
||||
static nsresult DecodeJSValInternal(const unsigned char*& aPos,
|
||||
const unsigned char* aEnd, JSContext* aCx,
|
||||
|
|
|
@ -353,8 +353,11 @@ nsresult KeyPath::ExtractKey(JSContext* aCx, const JS::Value& aValue,
|
|||
return rv;
|
||||
}
|
||||
|
||||
if (NS_FAILED(aKey.AppendItem(aCx, IsArray() && i == 0, value))) {
|
||||
ErrorResult errorResult;
|
||||
auto result = aKey.AppendItem(aCx, IsArray() && i == 0, value, errorResult);
|
||||
if (!result.Is(Ok, errorResult)) {
|
||||
NS_ASSERTION(aKey.IsUnset(), "Encoding error should unset");
|
||||
errorResult.SuppressException();
|
||||
return NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
}
|
||||
}
|
||||
|
@ -415,8 +418,11 @@ nsresult KeyPath::ExtractOrCreateKey(JSContext* aCx, const JS::Value& aValue,
|
|||
return rv;
|
||||
}
|
||||
|
||||
if (NS_FAILED(aKey.AppendItem(aCx, false, value))) {
|
||||
ErrorResult errorResult;
|
||||
auto result = aKey.AppendItem(aCx, false, value, errorResult);
|
||||
if (!result.Is(Ok, errorResult)) {
|
||||
NS_ASSERTION(aKey.IsUnset(), "Should be unset");
|
||||
errorResult.SuppressException();
|
||||
return value.isUndefined() ? NS_OK : NS_ERROR_DOM_INDEXEDDB_DATA_ERR;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ XPCSHELL_TESTS_MANIFESTS += [
|
|||
'test/unit/xpcshell-parent-process.ini'
|
||||
]
|
||||
|
||||
TEST_DIRS += ['test/gtest']
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'IDBCursor.h',
|
||||
'IDBDatabase.h',
|
||||
|
@ -41,6 +43,7 @@ EXPORTS.mozilla.dom += [
|
|||
EXPORTS.mozilla.dom.indexedDB += [
|
||||
'ActorsParent.h',
|
||||
'FileSnapshot.h',
|
||||
'IDBResult.h',
|
||||
'Key.h',
|
||||
'KeyPath.h',
|
||||
'PermissionRequestBase.h',
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* 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 "IDBResult.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using mozilla::ErrorResult;
|
||||
using namespace mozilla::dom::indexedDB;
|
||||
|
||||
TEST(IDBResultTest, ConstructWithValue)
|
||||
{
|
||||
ErrorResult rv;
|
||||
IDBResult<int, IDBSpecialValue::Failure> result(Ok(0));
|
||||
EXPECT_FALSE(result.Is(Failure, rv));
|
||||
EXPECT_TRUE(result.Is(Ok, rv));
|
||||
EXPECT_EQ(result.Unwrap(rv), 0);
|
||||
}
|
||||
|
||||
TEST(IDBResultTest, Expand)
|
||||
{
|
||||
ErrorResult rv;
|
||||
IDBResult<int, IDBSpecialValue::Failure> narrow{Failure};
|
||||
IDBResult<int, IDBSpecialValue::Failure, IDBSpecialValue::Invalid> wide{
|
||||
narrow};
|
||||
EXPECT_TRUE(wide.Is(Failure, rv));
|
||||
}
|
||||
|
||||
IDBResult<int, IDBSpecialValue::Failure> ThrowException(ErrorResult& aRv) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return Exception;
|
||||
}
|
||||
|
||||
TEST(IDBResultTest, ThrowException)
|
||||
{
|
||||
ErrorResult rv;
|
||||
const auto result = ThrowException(rv);
|
||||
EXPECT_TRUE(result.Is(Exception, rv));
|
||||
rv.SuppressException();
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
# 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/.
|
||||
|
||||
UNIFIED_SOURCES = [
|
||||
'TestIDBResult.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/dom/indexedDB',
|
||||
]
|
Загрузка…
Ссылка в новой задаче