diff options
Diffstat (limited to 'dom/indexedDB')
-rw-r--r-- | dom/indexedDB/IDBFactory.cpp | 171 | ||||
-rw-r--r-- | dom/indexedDB/IDBFactory.h | 32 | ||||
-rw-r--r-- | dom/indexedDB/IDBObjectStore.cpp | 22 | ||||
-rw-r--r-- | dom/indexedDB/Key.cpp | 125 | ||||
-rw-r--r-- | dom/indexedDB/Key.h | 8 | ||||
-rw-r--r-- | dom/indexedDB/crashtests/1499854-1.html | 2 | ||||
-rw-r--r-- | dom/indexedDB/crashtests/1857979-1.html | 4 | ||||
-rw-r--r-- | dom/indexedDB/test/perfdocs/index.rst | 2 | ||||
-rw-r--r-- | dom/indexedDB/test/unit/test_invalid_version.js | 16 | ||||
-rw-r--r-- | dom/indexedDB/test/unit/test_metadata2Restore.js | 40 | ||||
-rw-r--r-- | dom/indexedDB/test/unit/test_metadataRestore.js | 24 |
11 files changed, 257 insertions, 189 deletions
diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index c0dc5aeab2..b59efd9124 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -35,6 +35,8 @@ #include "nsIURI.h" #include "nsIUUIDGenerator.h" #include "nsIWebNavigation.h" +#include "nsLiteralString.h" +#include "nsStringFwd.h" #include "nsNetUtil.h" #include "nsSandboxFlags.h" #include "nsServiceManagerUtils.h" @@ -53,50 +55,7 @@ using namespace mozilla::ipc; namespace { -Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT IdentifyPrincipalType( - const mozilla::ipc::PrincipalInfo& aPrincipalInfo) { - switch (aPrincipalInfo.type()) { - case PrincipalInfo::TSystemPrincipalInfo: - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::system; - case PrincipalInfo::TContentPrincipalInfo: { - const ContentPrincipalInfo& info = - aPrincipalInfo.get_ContentPrincipalInfo(); - - nsCOMPtr<nsIURI> uri; - - if (NS_WARN_IF(NS_FAILED(NS_NewURI(getter_AddRefs(uri), info.spec())))) { - // This could be discriminated as an extra error value, but this is - // extremely unlikely to fail, so we just misuse ContentOther - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT:: - content_other; - } - - // TODO Are there constants defined for the schemes somewhere? - if (uri->SchemeIs("file")) { - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT:: - content_file; - } - if (uri->SchemeIs("http") || uri->SchemeIs("https")) { - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT:: - content_http_https; - } - if (uri->SchemeIs("moz-extension")) { - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT:: - content_moz_ext; - } - if (uri->SchemeIs("about")) { - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT:: - content_about; - } - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT:: - content_other; - } - case PrincipalInfo::TExpandedPrincipalInfo: - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::expanded; - default: - return Telemetry::LABELS_IDB_CUSTOM_OPEN_WITH_OPTIONS_COUNT::other; - } -} +constexpr nsLiteralCString kAccessError("The operation is insecure"); } // namespace @@ -112,11 +71,12 @@ struct IDBFactory::PendingRequestInfo { } }; -IDBFactory::IDBFactory(const IDBFactoryGuard&) +IDBFactory::IDBFactory(const IDBFactoryGuard&, bool aAllowed) : mBackgroundActor(nullptr), mInnerWindowID(0), mActiveTransactionCount(0), mActiveDatabaseCount(0), + mAllowed(aAllowed), mBackgroundActorFailed(false), mPrivateBrowsingMode(false) { AssertIsOnOwningThread(); @@ -140,16 +100,21 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForWindow( nsCOMPtr<nsIPrincipal> principal; nsresult rv = AllowedForWindowInternal(aWindow, &principal); - if (rv == NS_ERROR_DOM_NOT_SUPPORTED_ERR) { - NS_WARNING("IndexedDB is not permitted in a third-party window."); - return RefPtr<IDBFactory>{}; - } - if (NS_WARN_IF(NS_FAILED(rv))) { + if (rv == NS_ERROR_DOM_NOT_SUPPORTED_ERR) { + NS_WARNING("IndexedDB is not permitted in a third-party window."); + } + if (rv == NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR) { IDB_REPORT_INTERNAL_ERR(); } - return Err(rv); + + auto factory = + MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false); + factory->BindToOwner(aWindow->AsGlobal()); + factory->mInnerWindowID = aWindow->WindowID(); + + return factory; } MOZ_ASSERT(principal); @@ -172,7 +137,7 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForWindow( nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(aWindow); nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav); - auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{}); + auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ true); factory->mPrincipalInfo = std::move(principalInfo); factory->BindToOwner(aWindow->AsGlobal()); @@ -203,7 +168,11 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForMainThreadJS( MOZ_ASSERT(principal); bool isSystem; if (!AllowedForPrincipal(principal, &isSystem)) { - return Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + auto factory = + MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false); + factory->BindToOwner(aGlobal); + + return factory; } nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo.get()); @@ -220,14 +189,23 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForMainThreadJS( // static Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateForWorker( - nsIGlobalObject* aGlobal, const PrincipalInfo& aPrincipalInfo, + nsIGlobalObject* aGlobal, UniquePtr<PrincipalInfo>&& aPrincipalInfo, uint64_t aInnerWindowID) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aGlobal); - MOZ_ASSERT(aPrincipalInfo.type() != PrincipalInfo::T__None); - return CreateInternal(aGlobal, MakeUnique<PrincipalInfo>(aPrincipalInfo), - aInnerWindowID); + if (!aPrincipalInfo) { + auto factory = + MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false); + factory->BindToOwner(aGlobal); + factory->mInnerWindowID = aInnerWindowID; + + return factory; + } + + MOZ_ASSERT(aPrincipalInfo->type() != PrincipalInfo::T__None); + + return CreateInternal(aGlobal, std::move(aPrincipalInfo), aInnerWindowID); } // static @@ -264,10 +242,16 @@ Result<RefPtr<IDBFactory>, nsresult> IDBFactory::CreateInternal( if (aPrincipalInfo->type() != PrincipalInfo::TContentPrincipalInfo && aPrincipalInfo->type() != PrincipalInfo::TSystemPrincipalInfo) { NS_WARNING("IndexedDB not allowed for this principal!"); - return RefPtr<IDBFactory>{}; + + auto factory = + MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ false); + factory->BindToOwner(aGlobal); + factory->mInnerWindowID = aInnerWindowID; + + return factory; } - auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{}); + auto factory = MakeRefPtr<IDBFactory>(IDBFactoryGuard{}, /* aAllowed */ true); factory->mPrincipalInfo = std::move(aPrincipalInfo); factory->BindToOwner(aGlobal); factory->mEventTarget = GetCurrentSerialEventTarget(); @@ -407,6 +391,7 @@ PersistenceType IDBFactory::GetPersistenceType( void IDBFactory::UpdateActiveTransactionCount(int32_t aDelta) { AssertIsOnOwningThread(); + MOZ_ASSERT(mAllowed); MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 || (mActiveTransactionCount + aDelta) < mActiveTransactionCount); mActiveTransactionCount += aDelta; @@ -414,6 +399,7 @@ void IDBFactory::UpdateActiveTransactionCount(int32_t aDelta) { void IDBFactory::UpdateActiveDatabaseCount(int32_t aDelta) { AssertIsOnOwningThread(); + MOZ_ASSERT(mAllowed); MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 || (mActiveDatabaseCount + aDelta) < mActiveDatabaseCount); mActiveDatabaseCount += aDelta; @@ -424,6 +410,10 @@ void IDBFactory::UpdateActiveDatabaseCount(int32_t aDelta) { } bool IDBFactory::IsChrome() const { + if (!mAllowed) { + return false; // Minimal privileges + } + AssertIsOnOwningThread(); MOZ_ASSERT(mPrincipalInfo); @@ -432,39 +422,28 @@ bool IDBFactory::IsChrome() const { RefPtr<IDBOpenDBRequest> IDBFactory::Open(JSContext* aCx, const nsAString& aName, - uint64_t aVersion, + const Optional<uint64_t>& aVersion, CallerType aCallerType, ErrorResult& aRv) { - return OpenInternal(aCx, - /* aPrincipal */ nullptr, aName, - Optional<uint64_t>(aVersion), - /* aDeleting */ false, aCallerType, aRv); -} - -RefPtr<IDBOpenDBRequest> IDBFactory::Open(JSContext* aCx, - const nsAString& aName, - const IDBOpenDBOptions& aOptions, - CallerType aCallerType, - ErrorResult& aRv) { - // This overload is nonstandard, see bug 1275496. - // Ignore calls with empty options for telemetry of usage count. - // Unfortunately, we cannot distinguish between the use of the method with - // only a single argument (which actually is a standard overload we don't want - // to count) an empty dictionary passed explicitly (which is the custom - // overload we would like to count). However, we assume that the latter is so - // rare that it can be neglected. - if (aOptions.IsAnyMemberPresent()) { - Telemetry::AccumulateCategorical(IdentifyPrincipalType(*mPrincipalInfo)); + if (!mAllowed) { + aRv.ThrowSecurityError(kAccessError); + return nullptr; } return OpenInternal(aCx, - /* aPrincipal */ nullptr, aName, aOptions.mVersion, + /* aPrincipal */ nullptr, aName, aVersion, /* aDeleting */ false, aCallerType, aRv); } -RefPtr<IDBOpenDBRequest> IDBFactory::DeleteDatabase( - JSContext* aCx, const nsAString& aName, const IDBOpenDBOptions& aOptions, - CallerType aCallerType, ErrorResult& aRv) { +RefPtr<IDBOpenDBRequest> IDBFactory::DeleteDatabase(JSContext* aCx, + const nsAString& aName, + CallerType aCallerType, + ErrorResult& aRv) { + if (!mAllowed) { + aRv.ThrowSecurityError(kAccessError); + return nullptr; + } + return OpenInternal(aCx, /* aPrincipal */ nullptr, aName, Optional<uint64_t>(), /* aDeleting */ true, aCallerType, aRv); @@ -478,6 +457,10 @@ already_AddRefed<Promise> IDBFactory::Databases(JSContext* const aCx, } RefPtr<Promise> promise = Promise::CreateInfallible(GetOwnerGlobal()); + if (!mAllowed) { + promise->MaybeRejectWithSecurityError(kAccessError); + return promise.forget(); + } // Nothing can be done here if we have previously failed to create a // background actor. @@ -569,6 +552,11 @@ int16_t IDBFactory::Cmp(JSContext* aCx, JS::Handle<JS::Value> aFirst, RefPtr<IDBOpenDBRequest> IDBFactory::OpenForPrincipal( JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName, uint64_t aVersion, SystemCallerGuarantee aGuarantee, ErrorResult& aRv) { + if (!mAllowed) { + aRv.ThrowSecurityError(kAccessError); + return nullptr; + } + MOZ_ASSERT(aPrincipal); if (!NS_IsMainThread()) { MOZ_CRASH( @@ -584,6 +572,11 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenForPrincipal( JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName, const IDBOpenDBOptions& aOptions, SystemCallerGuarantee aGuarantee, ErrorResult& aRv) { + if (!mAllowed) { + aRv.ThrowSecurityError(kAccessError); + return nullptr; + } + MOZ_ASSERT(aPrincipal); if (!NS_IsMainThread()) { MOZ_CRASH( @@ -599,6 +592,11 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteForPrincipal( JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName, const IDBOpenDBOptions& aOptions, SystemCallerGuarantee aGuarantee, ErrorResult& aRv) { + if (!mAllowed) { + aRv.ThrowSecurityError(kAccessError); + return nullptr; + } + MOZ_ASSERT(aPrincipal); if (!NS_IsMainThread()) { MOZ_CRASH( @@ -611,6 +609,8 @@ RefPtr<IDBOpenDBRequest> IDBFactory::DeleteForPrincipal( } nsresult IDBFactory::EnsureBackgroundActor() { + MOZ_ASSERT(mAllowed); + if (mBackgroundActor) { return NS_OK; } @@ -671,6 +671,8 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( JSContext* aCx, nsIPrincipal* aPrincipal, const nsAString& aName, const Optional<uint64_t>& aVersion, bool aDeleting, CallerType aCallerType, ErrorResult& aRv) { + MOZ_ASSERT(mAllowed); + if (NS_WARN_IF(!GetOwnerGlobal())) { aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); return nullptr; @@ -801,6 +803,7 @@ RefPtr<IDBOpenDBRequest> IDBFactory::OpenInternal( nsresult IDBFactory::InitiateRequest( const NotNull<RefPtr<IDBOpenDBRequest>>& aRequest, const FactoryRequestParams& aParams) { + MOZ_ASSERT(mAllowed); MOZ_ASSERT(mBackgroundActor); MOZ_ASSERT(!mBackgroundActorFailed); diff --git a/dom/indexedDB/IDBFactory.h b/dom/indexedDB/IDBFactory.h index d64d571a05..08663eaec4 100644 --- a/dom/indexedDB/IDBFactory.h +++ b/dom/indexedDB/IDBFactory.h @@ -77,11 +77,16 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache { uint32_t mActiveTransactionCount; uint32_t mActiveDatabaseCount; + // When mAllowed is false we throw security errors on all operations. This is + // because although we make storage access decisions when we create the + // IDBFactory, the spec (and content) expects us to only throw if an attempt + // is made to use the resulting IDBFactory. + bool mAllowed; bool mBackgroundActorFailed; bool mPrivateBrowsingMode; public: - explicit IDBFactory(const IDBFactoryGuard&); + IDBFactory(const IDBFactoryGuard&, bool aAllowed); static Result<RefPtr<IDBFactory>, nsresult> CreateForWindow( nsPIDOMWindowInner* aWindow); @@ -89,8 +94,9 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache { static Result<RefPtr<IDBFactory>, nsresult> CreateForMainThreadJS( nsIGlobalObject* aGlobal); + // mAllowed shall be false for null aPrincipalInfo. static Result<RefPtr<IDBFactory>, nsresult> CreateForWorker( - nsIGlobalObject* aGlobal, const PrincipalInfo& aPrincipalInfo, + nsIGlobalObject* aGlobal, UniquePtr<PrincipalInfo>&& aPrincipalInfo, uint64_t aInnerWindowID); static bool AllowedForWindow(nsPIDOMWindowInner* aWindow); @@ -146,21 +152,15 @@ class IDBFactory final : public GlobalTeardownObserver, public nsWrapperCache { bool IsChrome() const; - [[nodiscard]] RefPtr<IDBOpenDBRequest> Open(JSContext* aCx, - const nsAString& aName, - uint64_t aVersion, - CallerType aCallerType, - ErrorResult& aRv); - - [[nodiscard]] RefPtr<IDBOpenDBRequest> Open(JSContext* aCx, - const nsAString& aName, - const IDBOpenDBOptions& aOptions, - CallerType aCallerType, - ErrorResult& aRv); + [[nodiscard]] RefPtr<IDBOpenDBRequest> Open( + JSContext* aCx, const nsAString& aName, + const Optional<uint64_t>& aVersion, CallerType aCallerType, + ErrorResult& aRv); - [[nodiscard]] RefPtr<IDBOpenDBRequest> DeleteDatabase( - JSContext* aCx, const nsAString& aName, const IDBOpenDBOptions& aOptions, - CallerType aCallerType, ErrorResult& aRv); + [[nodiscard]] RefPtr<IDBOpenDBRequest> DeleteDatabase(JSContext* aCx, + const nsAString& aName, + CallerType aCallerType, + ErrorResult& aRv); already_AddRefed<Promise> Databases(JSContext* aCx, ErrorResult& aRv); diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 728f10b105..401c9defb2 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -276,12 +276,22 @@ bool CopyingStructuredCloneWriteCallback(JSContext* aCx, aObj); } +void StructuredCloneErrorCallback(JSContext* aCx, uint32_t aErrorId, + void* aClosure, const char* aErrorMessage) { + // This callback is only used to prevent the default cloning TypeErrors + // from being thrown, as we will throw DataCloneErrors instead per spec. +} + nsresult GetAddInfoCallback(JSContext* aCx, void* aClosure) { static const JSStructuredCloneCallbacks kStructuredCloneCallbacks = { - nullptr /* read */, StructuredCloneWriteCallback /* write */, - nullptr /* reportError */, nullptr /* readTransfer */, - nullptr /* writeTransfer */, nullptr /* freeTransfer */, - nullptr /* canTransfer */, nullptr /* sabCloned */ + nullptr /* read */, + StructuredCloneWriteCallback /* write */, + StructuredCloneErrorCallback /* reportError */, + nullptr /* readTransfer */, + nullptr /* writeTransfer */, + nullptr /* freeTransfer */, + nullptr /* canTransfer */, + nullptr /* sabCloned */ }; MOZ_ASSERT(aCx); @@ -555,7 +565,7 @@ bool IDBObjectStore::DeserializeValue( static const JSStructuredCloneCallbacks callbacks = { StructuredCloneReadCallback<StructuredCloneReadInfoChild>, nullptr, - nullptr, + StructuredCloneErrorCallback, nullptr, nullptr, nullptr, @@ -1751,7 +1761,7 @@ bool IDBObjectStore::ValueWrapper::Clone(JSContext* aCx) { static const JSStructuredCloneCallbacks callbacks = { CopyingStructuredCloneReadCallback /* read */, CopyingStructuredCloneWriteCallback /* write */, - nullptr /* reportError */, + StructuredCloneErrorCallback /* reportError */, nullptr /* readTransfer */, nullptr /* writeTransfer */, nullptr /* freeTransfer */, diff --git a/dom/indexedDB/Key.cpp b/dom/indexedDB/Key.cpp index 7e0c607617..a6223bfc95 100644 --- a/dom/indexedDB/Key.cpp +++ b/dom/indexedDB/Key.cpp @@ -16,6 +16,7 @@ #include "js/MemoryFunctions.h" #include "js/Object.h" // JS::GetBuiltinClass #include "js/PropertyAndElement.h" // JS_DefineElement, JS_GetProperty, JS_GetPropertyById, JS_HasOwnProperty, JS_HasOwnPropertyById +#include "js/SharedArrayBuffer.h" // IsSharedArrayBufferObject #include "js/Value.h" #include "jsfriendapi.h" #include "mozilla/Casting.h" @@ -102,6 +103,84 @@ IDBResult<Ok, IDBSpecialValue::Invalid> ConvertArrayValueToKey( aPolicy.EndSubkeyList(); return Ok(); } + +bool IsDetachedBuffer(JSContext* aCx, JS::Handle<JSObject*> aJsBufferSource) { + if (JS_IsArrayBufferViewObject(aJsBufferSource)) { + bool unused = false; + JS::Rooted<JSObject*> viewedArrayBuffer( + aCx, JS_GetArrayBufferViewBuffer(aCx, aJsBufferSource, &unused)); + return JS::IsDetachedArrayBufferObject(viewedArrayBuffer); + } + + return JS::IsDetachedArrayBufferObject(aJsBufferSource); +} + +// To get a copy of the bytes held by the buffer source given a buffer source +// type instance bufferSource: +IDBResult<Span<const uint8_t>, IDBSpecialValue::Invalid> +GetByteSpanFromJSBufferSource(JSContext* aCx, + JS::Handle<JSObject*> aJsBufferSource) { + // 1. Let jsBufferSource be the result of converting bufferSource to a + // JavaScript value. + + // 2. Let jsArrayBuffer be jsBufferSource. + JS::Handle<JSObject*>& jsArrayBuffer = aJsBufferSource; + + // 3. Let offset be 0. + size_t offset = 0u; + + // 4. Let length be 0. + size_t length = 0u; + + // 8. Let bytes be a new byte sequence of length equal to length. + uint8_t* bytes = nullptr; // Note: Copy is deferred, no allocation here + + // 5. If jsBufferSource has a [[ViewedArrayBuffer]] internal slot, then: + if (JS_IsArrayBufferViewObject(aJsBufferSource)) { + // 5.1 Set jsArrayBuffer to jsBufferSource.[[ViewedArrayBuffer]]. + // 5.2 Set offset to jsBufferSource.[[ByteOffset]]. + // 5.3 Set length to jsBufferSource.[[ByteLength]]. + + // 9. For i in the range offset to offset + length − 1, inclusive, set + // bytes[i − offset] to GetValueFromBuffer(jsArrayBuffer, i, Uint8, true, + // Unordered). + (void)offset; + bool unused = false; + if (!JS_GetObjectAsArrayBufferView(jsArrayBuffer, &length, &unused, + &bytes)) { + return Err(IDBError(SpecialValues::Invalid)); + } + + // 6. Otherwise: + } else { + // 6.1 Assert: jsBufferSource is an ArrayBuffer or SharedArrayBuffer object. + MOZ_RELEASE_ASSERT(JS::IsArrayBufferObject(aJsBufferSource) || + JS::IsSharedArrayBufferObject(aJsBufferSource)); + + // 6.2 Set length to jsBufferSource.[[ArrayBufferByteLength]]. + + // 9. For i in the range offset to offset + length − 1, inclusive, set + // bytes[i − offset] to GetValueFromBuffer(jsArrayBuffer, i, Uint8, true, + // Unordered). + (void)offset; + if (!JS::GetObjectAsArrayBuffer(jsArrayBuffer, &length, &bytes)) { + return Err(IDBError(SpecialValues::Invalid)); + } + } + + // 7. If IsDetachedBuffer(jsArrayBuffer) is true, then return the empty byte + // sequence. + if (IsDetachedBuffer(aCx, aJsBufferSource)) { + // Note: As the web platform tests assume, and as has been discussed at + // https://github.com/w3c/IndexedDB/issues/417 - we are better off by + // throwing a DataCloneError. The spec language is about to be revised. + return Err(IDBError(SpecialValues::Invalid)); + } + + // 10. Return bytes. + return Span<uint8_t>{bytes, length}.AsConst(); +} + } // namespace /* @@ -435,8 +514,19 @@ IDBResult<Ok, IDBSpecialValue::Invalid> Key::EncodeJSValInternal( // 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); + // 1. Let bytes be the result of getting a copy of the bytes held by the + // buffer source input. + + auto res = GetByteSpanFromJSBufferSource(aCx, object); + + // Rethrow any exceptions. + if (res.isErr()) { + return res.propagateErr(); + } + + // 2. Return a new key with type binary and value bytes. + // Note: The copy takes place here. + return EncodeAsString(res.inspect(), eBinary + aTypeOffset); } // If IsArray(`input`) @@ -518,13 +608,14 @@ nsresult Key::DecodeJSValInternal(const EncodedDataType*& aPos, } else if (*aPos - aTypeOffset == eFloat) { aVal.setDouble(DecodeNumber(aPos, aEnd)); } else if (*aPos - aTypeOffset == eBinary) { - JSObject* binary = DecodeBinary(aPos, aEnd, aCx); - if (!binary) { + JSObject* arrayBufferObject = + GetArrayBufferObjectFromDataRange(aPos, aEnd, aCx); + if (!arrayBufferObject) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - aVal.setObject(*binary); + aVal.setObject(*arrayBufferObject); } else { MOZ_ASSERT_UNREACHABLE("Unknown key type!"); } @@ -869,28 +960,10 @@ double Key::DecodeNumber(const EncodedDataType*& aPos, return BitwiseCast<double>(bits); } -Result<Ok, nsresult> Key::EncodeBinary(JSObject* aObject, bool aIsViewObject, - uint8_t aTypeOffset) { - uint8_t* bufferData; - size_t bufferLength; - - // We must use JS::GetObjectAsArrayBuffer()/JS_GetObjectAsArrayBufferView() - // instead of js::GetArrayBufferLengthAndData(). The object might be wrapped, - // the former will handle the wrapped case, the later won't. - if (aIsViewObject) { - bool unused; - JS_GetObjectAsArrayBufferView(aObject, &bufferLength, &unused, &bufferData); - } else { - JS::GetObjectAsArrayBuffer(aObject, &bufferLength, &bufferData); - } - - return EncodeAsString(Span{bufferData, bufferLength}.AsConst(), - eBinary + aTypeOffset); -} - // static -JSObject* Key::DecodeBinary(const EncodedDataType*& aPos, - const EncodedDataType* aEnd, JSContext* aCx) { +JSObject* Key::GetArrayBufferObjectFromDataRange(const EncodedDataType*& aPos, + const EncodedDataType* aEnd, + JSContext* aCx) { JS::Rooted<JSObject*> rv(aCx); DecodeStringy<eBinary, uint8_t>( aPos, aEnd, diff --git a/dom/indexedDB/Key.h b/dom/indexedDB/Key.h index 25ddd3e0b1..f4e6570a96 100644 --- a/dom/indexedDB/Key.h +++ b/dom/indexedDB/Key.h @@ -226,9 +226,6 @@ class Key { Result<Ok, nsresult> EncodeNumber(double aFloat, uint8_t aType); - Result<Ok, nsresult> EncodeBinary(JSObject* aObject, bool aIsViewObject, - uint8_t aTypeOffset); - // Decoding functions. aPos points into mBuffer and is adjusted to point // past the consumed value. (Note: this may be beyond aEnd). static nsresult DecodeJSVal(const EncodedDataType*& aPos, @@ -241,8 +238,9 @@ class Key { static double DecodeNumber(const EncodedDataType*& aPos, const EncodedDataType* aEnd); - static JSObject* DecodeBinary(const EncodedDataType*& aPos, - const EncodedDataType* aEnd, JSContext* aCx); + static JSObject* GetArrayBufferObjectFromDataRange( + const EncodedDataType*& aPos, const EncodedDataType* aEnd, + JSContext* aCx); // Returns the size of the decoded data for stringy (string or binary), // excluding a null terminator. diff --git a/dom/indexedDB/crashtests/1499854-1.html b/dom/indexedDB/crashtests/1499854-1.html index 0e10601134..7d3a86a62f 100644 --- a/dom/indexedDB/crashtests/1499854-1.html +++ b/dom/indexedDB/crashtests/1499854-1.html @@ -6,7 +6,7 @@ o1 = new Int32Array(51488) o2 = new ArrayBuffer(13964) for (let i = 0; i < 51488; i++) o1[i] = 0x41 - const dbRequest = window.indexedDB.open('', {}) + const dbRequest = window.indexedDB.open('') dbRequest.onupgradeneeded = function(event) { const store = event.target.result.createObjectStore('IDBStore_0', {}) store.add({}, 'ObjectKey_0') diff --git a/dom/indexedDB/crashtests/1857979-1.html b/dom/indexedDB/crashtests/1857979-1.html index d97aa24562..be5a3c947c 100644 --- a/dom/indexedDB/crashtests/1857979-1.html +++ b/dom/indexedDB/crashtests/1857979-1.html @@ -1,10 +1,10 @@ <!DOCTYPE html> <script> window.addEventListener('load', async () => { - const db1 = indexedDB.open('DB_1696052013002', {}) + const db1 = indexedDB.open('DB_1696052013002') db1.onsuccess = () => window.close() const blob = new Blob(['0'], {}) - db2 = indexedDB.open('DB_1696052013003', {}) + db2 = indexedDB.open('DB_1696052013003') db2.onupgradeneeded = (e) => { const store = e.target.result.createObjectStore('IDBStore_0', { 'autoIncrement': true, diff --git a/dom/indexedDB/test/perfdocs/index.rst b/dom/indexedDB/test/perfdocs/index.rst index ec54211038..5d7d98e3de 100644 --- a/dom/indexedDB/test/perfdocs/index.rst +++ b/dom/indexedDB/test/perfdocs/index.rst @@ -51,7 +51,7 @@ How to add more tests? * Under the ``[test_name]`` section, specity the test parameters as a sequence of ``--browsertime.key=value`` arguments as a value of ``browsertime_args =`` * Under the ``[test_name]`` section, override any other values as needed -* Add test as a subtest to run for Desktop ``taskcluster/ci/test/browsertime-desktop.yml`` (maybe also for mobile) +* Add test as a subtest to run for Desktop ``taskcluster/kinds/test/browsertime-desktop.yml`` (maybe also for mobile) * Add test documentation to ``testing/raptor/raptor/perfdocs/config.yml`` * Generated files: diff --git a/dom/indexedDB/test/unit/test_invalid_version.js b/dom/indexedDB/test/unit/test_invalid_version.js index ea5e2953d0..bf745cb93a 100644 --- a/dom/indexedDB/test/unit/test_invalid_version.js +++ b/dom/indexedDB/test/unit/test_invalid_version.js @@ -26,21 +26,5 @@ function* testSteps() { is(e.name, "TypeError", "Good error name."); } - try { - indexedDB.open(name, { version: 0 }); - ok(false, "Should have thrown!"); - } catch (e) { - ok(e instanceof TypeError, "Got TypeError."); - is(e.name, "TypeError", "Good error name."); - } - - try { - indexedDB.open(name, { version: -1 }); - ok(false, "Should have thrown!"); - } catch (e) { - ok(e instanceof TypeError, "Got TypeError."); - is(e.name, "TypeError", "Good error name."); - } - finishTest(); } diff --git a/dom/indexedDB/test/unit/test_metadata2Restore.js b/dom/indexedDB/test/unit/test_metadata2Restore.js index da31eecc86..643e734e70 100644 --- a/dom/indexedDB/test/unit/test_metadata2Restore.js +++ b/dom/indexedDB/test/unit/test_metadata2Restore.js @@ -15,7 +15,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:81", dbName: "dbC", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+82^userContextId=1 @@ -25,7 +25,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:82", dbName: "dbD", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+83^userContextId=1 @@ -36,7 +36,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:83", dbName: "dbE", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+84^userContextId=1 @@ -47,7 +47,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:84", dbName: "dbF", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+85^userContextId=1 @@ -59,7 +59,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:85", dbName: "dbG", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+86^userContextId=1 @@ -72,7 +72,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:86", dbName: "dbH", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+87^userContextId=1 @@ -85,7 +85,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:87", dbName: "dbI", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+88^userContextId=1 @@ -99,7 +99,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:88", dbName: "dbJ", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+89^userContextId=1 @@ -113,7 +113,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:89", dbName: "dbK", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+90^userContextId=1 @@ -128,7 +128,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:90", dbName: "dbL", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+91^userContextId=1 @@ -144,7 +144,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:91", dbName: "dbM", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+92^userContextId=1 @@ -160,7 +160,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:92", dbName: "dbN", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+93^userContextId=1 @@ -177,7 +177,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:93", dbName: "dbO", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+94^userContextId=1 @@ -195,7 +195,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:94", dbName: "dbP", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+95^userContextId=1 @@ -213,7 +213,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:95", dbName: "dbQ", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+96^userContextId=1 @@ -232,7 +232,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:96", dbName: "dbR", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+97^userContextId=1 @@ -252,7 +252,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:97", dbName: "dbS", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+98^userContextId=1 @@ -272,7 +272,7 @@ function* testSteps() { attrs: { userContextId: 1 }, url: "http://localhost:98", dbName: "dbT", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, ]; @@ -287,10 +287,10 @@ function* testSteps() { request = indexedDB.openForPrincipal( principal, params.dbName, - params.dbOptions + params.dbVersion ); } else { - request = indexedDB.open(params.dbName, params.dbOptions); + request = indexedDB.open(params.dbName, params.dbVersion); } return request; } diff --git a/dom/indexedDB/test/unit/test_metadataRestore.js b/dom/indexedDB/test/unit/test_metadataRestore.js index 001d4da65b..302b1614d0 100644 --- a/dom/indexedDB/test/unit/test_metadataRestore.js +++ b/dom/indexedDB/test/unit/test_metadataRestore.js @@ -12,70 +12,70 @@ function* testSteps() { { url: "http://localhost:81", dbName: "dbC", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+82 { url: "http://localhost:82", dbName: "dbD", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+83 { url: "http://localhost:83", dbName: "dbE", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+84 { url: "http://localhost:84", dbName: "dbF", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+85 { url: "http://localhost:85", dbName: "dbG", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+86 { url: "http://localhost:86", dbName: "dbH", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+87 { url: "http://localhost:87", dbName: "dbI", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+88 { url: "http://localhost:88", dbName: "dbJ", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+89 { url: "http://localhost:89", dbName: "dbK", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, // This one lives in storage/default/http+++localhost+90 { url: "http://localhost:90", dbName: "dbL", - dbOptions: { version: 1, storage: "default" }, + dbVersion: 1, }, ]; @@ -90,10 +90,10 @@ function* testSteps() { request = indexedDB.openForPrincipal( principal, params.dbName, - params.dbOptions + params.dbVersion ); } else { - request = indexedDB.open(params.dbName, params.dbOptions); + request = indexedDB.open(params.dbName, params.dbVersion); } return request; } |